Project - Multisig Wallet

I gave the MultiSigWallter another go today. I am happy I could make it work.

pragma solidity 0.7.5;

//["0x5B38Da6a701c568545dCfcB03FcB875f56beddC4", "0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2", "0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db"]

contract MultiSigWallet2{
    //deployer sets owner adresses and approval limits!
    address[] owners; //storage for owners adresses
    uint limit; //storage for approval limit
    transactionRequest[] TxRequests;
    mapping (address => mapping (uint => bool)) approvals; //approvals mapping storage! address points to index points to bool
    mapping (address => uint) balances;

    
    constructor(address[] memory _owners, uint _limit){
        owners = _owners;
        limit = _limit;
    }
    
    modifier OwnersOnly(){
        bool isOwner = false;
        for (uint i=0;i<owners.length;i++){
            if(owners[i] == msg.sender){
                isOwner = true;
            }
        }
        require(isOwner = true);
        _;
    }
    
    event TxRequested(uint _id, address _sender, address _receiver, uint _amount);
    event TxApprovalReceived(uint _id, uint _approvals);
    event TxSent(uint _id);
    //we need a struct for transaction requests
    struct transactionRequest{
        uint id;
        address sender;
        address payable receiver;
        uint amount;
        uint approvals;
        bool hasBeenSent;
    }
    
    //function for creating transactionRequests
    function requestTransaction(address payable _receiver, uint _amount) OwnersOnly public {
        require(_amount <= address(this).balance, "Insufficient funds!");
        
        emit TxRequested(TxRequests.length, msg.sender, _receiver, _amount);
        
        TxRequests.push(
            transactionRequest(TxRequests.length, msg.sender, _receiver, _amount, 0, false));
        }
    
    //shows TxRequests
    function getTxRequests(uint _id) OwnersOnly public view returns (uint, address, address, uint, uint, bool){
        return (TxRequests[_id].id, TxRequests[_id].sender, TxRequests[_id].receiver, TxRequests[_id].amount, TxRequests[_id].approvals, TxRequests[_id].hasBeenSent);
    }
    
    //approve TxRequests
    function approvalTx(uint _id) OwnersOnly public{
        require(approvals[msg.sender][_id] == false, "Transaction already approved!");
        require(TxRequests[_id].hasBeenSent == false, "Transaction has been send already!");
       
        TxRequests[_id].approvals++;
        emit TxApprovalReceived(_id, TxRequests[_id].approvals);
        approvals[msg.sender][_id]  = true;
        
        if(TxRequests[_id].approvals >= limit){
            TxRequests[_id].hasBeenSent = true;
            TxRequests[_id].receiver.transfer(TxRequests[_id].amount);
            emit TxSent(_id);
            
        }
    }
    
    //deposit function
    function deposit() public payable{
        balances[msg.sender] += msg.value;
    }
    
    //show own balance
    function showMyBalance() public view returns(uint){
        return balances[msg.sender];
    }
    
    //show contract balance
    function showContractBalance() public view returns(uint){
        return address(this).balance;
    }
    
}
1 Like

One implementation that pops up in my mind is that once a transaction is approved and transfered, balances of the initiator of the transaction should be updated as well, or, balance of all owners should be updated equally - depending on how the contract is inteded to work in that regards. The contract balance is updated properly, as it is built in to the transer function.
Wow, there is so much to consider!

1 Like

This is my first initial crack at the multi-stage wallet. I have not looked at the help video yet. The first part was to create mapping and then a structure to define an owner and his particulars, address, amount, message, nonce, that would. The get owner information.
Then verify the Signatures for each owner using the keccak256, then get message Hash for each owner. When I get to the verification of signature and get message Hash I have an error related to bytes32 conversion from bytes1. This maybe the wrong direction. But I will work on the other parts of the program like transfer and approval. Please provide comments.

pragma solidity 0.7.5;
pragma abicoder v2;


contract multiSigWallet{
    
    mapping(address => uint)balance;
    
    struct Owner{
        address owner;
        bytes32 amount;   //changed from uint to allow messageHash bytes32
        string message;
        uint nonce;
    }
    Owner [] owner;
    
    function addOwner(address _owneraddress, bytes32 _owneramount, string memory _ownermessage, uint _ownernonce)public{
        
            
     Owner memory newOwner = Owner(_owneraddress, _owneramount, _ownermessage, _ownernonce);
            
            owner.push(newOwner);
        }

        
        function getOwnerinfo(uint _index)public view returns(address, bytes32, string memory, uint){
            
            Owner memory ownerinfo = owner[_index];
            
            return (ownerinfo.owner, ownerinfo.amount, ownerinfo.message, ownerinfo.nonce);
            
        }
        
        function verifySignature(uint _index)public view returns(bytes32){
           
            bytes32 messageHash[index];  //tried to do bytes32 messageHash[_index];  but this would not work either
            
            return messageHash[_index] = keccak256(abi.encodePacked(owner[_index].owner, owner[_index].amount, owner[_index].message, owner[_index].nonce));
        
            
        }
        
        
        function getEthSignedMessageHash(uint _index, bytes32 messageHash)public view returns(bytes32){
            
            bytes32 signedMessage;  //Also tried bytes32 messageHash[_index]
            
            return signedMessage[_index] = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", messageHash[_index]));
        }
        
}
1 Like

Here is my Multi signature wallet.
I have only watched the first video when I made this.
Would appreciate your comments.

pragma solidity 0.7.5;
pragma abicoder v2;

// Define contract
contract MultiSigWallet {
    
    // Creator of the contract / wallet
    address owner;
    
    // Co owners of the wallet
    address[] coOwners;
    
    // Number of signatures needed per request in order for it to be sent
    uint numberOfRequiredSignatures;
    
    // array of the pending transfer requests
    TransferRequest[] TransferRequests;
    
    // Total pending amount to be sent
    uint pendingRequestAmount;

    
    // Constructor
    // _coOwners : The co owners of the wallet that can vote on transfer requests
    // _numberOfRequiredSignatures: The number of approved voted needed for a request to be sent
    constructor(address[] memory _coOwners, uint _numberOfRequiredSignatures){
        
        // Check so that the _co owners does not contain duplicats
        require(!ContainsDuplicate(_coOwners));
        
        // Check so that the creator is not in the list of the co oweners
        require(!ContainsAddress(_coOwners, msg.sender), "The creator should not be in the list of co owners");
        
        // Check so that the required number of signatures is not greater than the number of possible signers
        require(_numberOfRequiredSignatures > 0 && _numberOfRequiredSignatures <= _coOwners.length + 1, "Not enough owners compared to required number of votes");
        
        // Assign variables
        owner = msg.sender;
        coOwners = _coOwners;
        numberOfRequiredSignatures = _numberOfRequiredSignatures;
    }
    
    // Event to be fired when a new transfer request has been created
    event OnTransferRequestCreated(uint indexed requestId, address recepient, uint amount);
    
    // Event to be fired when a request recieves a vote
    event OnTransferRequestVote(uint indexed requestId, bool approved);
    
    // Event to be fired when a request is approved and transfered
    event OnTransferRequestSuccess(uint indexed requestId, address recepient, uint amount);
    
    // Event to be fired when a request has failed due to too many rejections
    event OnTransferRequestFailed(uint indexed requestId, address recepient, uint amount);
    
    // Event to be fired when money is deposited into the wallet
    event OnDeposit(uint amount, uint balance);
    
    // Definition of a transfer request
    struct TransferRequest{
        
        // Id of the request
        uint requestId;
        
        // The recepient of the funds
        address payable recepient;
        
        // The amount to be sent
        uint amount;
        
        // The owners who have voted for the request. 
        // Note: array is used instead of mapping due to a probable small size of total number of owners
        address[] approvedByOwners;
        
        // The owners who have voted against the request. 
        // Note: array is used instead of mapping due to a probable small size of total number of owners
        address[] rejectedByOwners;
        
        // Status of the request
        // 0: pending
        // 1: sent
        // 2: rejected
        uint status;
    }
    
    // checks if a list contains duplicate entrys
    function ContainsDuplicate(address[] memory _list) private pure returns (bool){
        
        // Return false if there are less than 2 elements in the list
        if (_list.length < 2){
            return false;
        }
        
        // Loop through the elements
        for(uint i = 0; i < _list.length; i++){
            
            // Loop through each element again
            for (uint j = 0; j < _list.length; j++){
                
                // Continue of the iteration variable is the same
                if (i == j){
                  continue;  
                } 
                
                // Return true if the given element matches the compared one
                if (_list[i] == _list[j]){
                  return true;  
                } 
            }
        }
        
        // If we get here then there are no duplicates so we return false
        return false;
    }
    
    // This helper function checks if an address is present in an array
    function ContainsAddress(address[] memory _list, address _other) private pure returns (bool){
        
        // Loop through the array
        for (uint i = 0; i < _list.length; i++ ){
            
            // Return if the address is found in the list
            if (_list[i] == _other){
                return true;
            }
        }
        
        // Return false if the address was not found
        return false;
    }

    // Modifier that makes sure that a transfer request with a given id actually is present in the array
    modifier requestExists(uint requestId) {
        require(TransferRequests.length > requestId,"Request not found");
        _; 
    }  
    
    // Modifier that makes sure that a transfer request has the status pending
    modifier pendingRequest(uint requestId) {
        require(TransferRequests[requestId].status == 0,"Request not in status pending");
        _; 
    }  
    
    // Modifier that only allowes the owner or co owners to run the function
    modifier onlyOwner {
        require(msg.sender == owner || ContainsAddress(coOwners, msg.sender),"User is not authorized");
        _; 
    }
    
    // Modifier that only allowes the function to be run of the user has not already voted
    modifier notVoted(uint _requestId) {
        
        // Check if the users address exists in either that list of approvals or rejection for the given request
        bool hasVoted = ContainsAddress(TransferRequests[_requestId].approvedByOwners, msg.sender) || ContainsAddress(TransferRequests[_requestId].rejectedByOwners, msg.sender);
        
        // Require that the user has not voted already
        require(!hasVoted,"User has already voted on this request");
        _; 
    }
    
    // Creates a new transfer request and adds it to the mapping
    // _recepient: The receiving address
    // _amount: The proposed amount to send
    // Can only be called by owners
    function CreateRequest(address payable _recepient, uint _amount) public onlyOwner{
        
        // Check so that an amount is specified
        require(_amount > 0, "A request must contain an amount");
        
        // Check so that there are enough funds to send the amount taking in consideration the present requests
        require(address(this).balance >= pendingRequestAmount + _amount, "Not enough funds to process this request.");   
        
        // Define empty addresses
        address[] memory approved;
        address[] memory rejected;
        
        // Create the request and add it to the mapping
        TransferRequests.push(TransferRequest(TransferRequests.length,_recepient, _amount,approved, rejected, 0));
        
        // Increase the total requested amount
        pendingRequestAmount += _amount;
        
        // Send event
        emit OnTransferRequestCreated(TransferRequests.length -1 , _recepient, _amount);
        
        
    }
    
    // Sends a request to the recepeint and then removes it from the mapping 
    // _requestId: The id of the request to be sent
    // Can only be called by owners
    function SendRequest(uint _requestId) private onlyOwner requestExists(_requestId) pendingRequest(_requestId){
        
        // Remove the amount of the request from the sum of requested amounts
        pendingRequestAmount -= TransferRequests[_requestId].amount;
        
        // Mark the request as sent so that the function can only be called once
        TransferRequests[_requestId].status = 1;
        
        // Transfer the amount to the recepient
        TransferRequests[_requestId].recepient.transfer(TransferRequests[_requestId].amount);
        
        // Send event
        emit OnTransferRequestSuccess(_requestId, TransferRequests[_requestId].recepient, TransferRequests[_requestId].amount);
      
        // Make sure that the request can not be sent again
        assert(TransferRequests[_requestId].status == 1);
    }
    
    // Adds a vote in favor for a given request
    // _requestId: The id of the request to be approved
    // Can only be called by owners who have not already voted on pending requests
    function ApproveRequest(uint _requestId) public onlyOwner requestExists(_requestId) pendingRequest(_requestId) notVoted(_requestId)   {
        
        // Add the address of the user to the list of approvers on the request
        TransferRequests[_requestId].approvedByOwners.push(msg.sender);
        
        // Send event about the vote
        emit OnTransferRequestVote(_requestId, true);
        
        // Complete the transfer if enough votes now are present for the request
        if (TransferRequests[_requestId].approvedByOwners.length >= numberOfRequiredSignatures){
            SendRequest(_requestId);
        }
    }
    
    // Adds a vote against a given request
    // _requestId: The id of the request to be rejected
    // Can only be called by owners who have not already voted on pending requests
    function RejectRequest(uint _requestId) public onlyOwner requestExists(_requestId) pendingRequest(_requestId) notVoted(_requestId){
        
        // Make sure the request is pending
        
        require(TransferRequests[_requestId].status == 0);
        // Add the address of the user to the list of party poopers
        TransferRequests[_requestId].rejectedByOwners.push(msg.sender);
        
        // Send event about the vote
        emit OnTransferRequestVote(_requestId, true);
        
        // Delete the request if there is now not enough voters left to send the request
        if (coOwners.length + 1 - TransferRequests[_requestId].rejectedByOwners.length < numberOfRequiredSignatures){
            
            // Send event the the request has failed
            emit OnTransferRequestFailed(_requestId, TransferRequests[_requestId].recepient,TransferRequests[_requestId].amount);
            
            // mark request as rejected
            TransferRequests[_requestId].status = 2;
        }
    }
    
    // Function to recieve a deposit to the contract
    function Deposit() public payable onlyOwner {
        require(msg.value > 0, "No funds recieved");
        
        // Send event about the deposit
        emit OnDeposit(msg.value, address(this).balance);
    }

    
    // Gets the current balance of the wallet
    function GetBalance() public view returns (uint){
        return address(this).balance;
    }
    
    // Gets the current balance of the wallet
    function GetPendingRequestsSum() public view returns (uint){
        return pendingRequestAmount;
    }
    
    // Gets the pending requests
    function GetRequests() public view returns (TransferRequest[] memory){
        return TransferRequests;
    }
}
1 Like

OK I wanted to give it a crack without looking at anything. I may be way off base with what you are looking for but I got a wallet. And it has three owners that require two approvals to make an authorized transfer. And it will get the balance after the transfer is made. It will also get the owners information ex. address, balance, sender, Id, and any message. It will allow for anyone to make a deposit It will record, using boolean the owners approval and will make the transaction if approved by two Owners. It will then also show in the console if approval Authorization is true or false.


pragma solidity 0.7.5;

pragma abicoder v2;

contract multiSigWallet{
    
    mapping(uint => Owner)owners;
    
    struct Owner{
        uint  id;
        address owner;
        address recipient;
        uint balance;  
        string message;
       
    }
    
     event transferDone(uint amount, address recipeint);
    
    
    
    function addOwner(uint _id, address _owner, address _recipient, uint _balance, string memory _message)public{
        
            
     owners[_id] = Owner(_id, _owner, _recipient, _balance, _message);
            
        }

        
        function getOwnerinfo(uint _id)public view returns(uint, address, address, uint, string memory){
            
            Owner memory ownerinfo = owners[_id];
            
            return (ownerinfo.id, ownerinfo.owner, ownerinfo.recipient, ownerinfo.balance, ownerinfo.message);
            
        }
        
    event depositedTo(uint indexed balamce, address indexed depositedTo);
     
     event balanceAdded(uint balance, address depositedTo);
     
       
       function Deposit(uint _id)public payable returns(uint){
          
           owners[_id].balance += msg.value;
           
           emit depositedTo(msg.value, owners[_id].owner);
           
           return owners[_id].balance;
       }
       
   // To get balance from any account 
  // function getMoreBalance(address _to)public view returns ( uint){
       
      // return balance[ _to];
      
      //To get balance from msg.sender account
       
       function getDepositBalance(uint _id )public view returns ( uint){
           
           return owners[_id].balance;
   }
   
   //Get approvals using boolean.  
   
   bool []approval;
   
   function IsApproved (bool  _1stOwnerApproves, bool _2ndOwnerApproves, bool _3rdOwnerApproves)public {
       
       approval.push(_1stOwnerApproves);
       approval.push(_2ndOwnerApproves);
       approval.push(_3rdOwnerApproves);
   }
   
   
   function listapprovals()public view returns (bool [] memory){
       
       return approval;
   }
   
 bool approved;
   
   function Authorization()public returns (bool){
       
       if ((approval[0] = true) || (approval[1] = true) &&  (approval[2] = true)){
           
            approved = true;
       }
   else if ((approval[0] = true) || (approval[1] = true) && (approval[2])){
           
            approved = true;
      }
   
        else if ((approval[1]= true) || (approval[2]= true) && (approval[0] = true)){
       
       approved = true;
       
   }
   
   else if((approval[2]= true) || (approval[0] = true) && (approval[1] = true)){
       
       approved = true;
   }
   
   else approved = false;
   
   return approved;
   
   
   }
      //To transfer money from msg.sender to any recipient
   //And to add requirements before function is executed.
   //add event to check transferFund
   
  
   
   function transferFunds(uint _id, uint _amount) public {
      
      //call made to execute the transfer function using these parameters from "msg.sender" to "recipient" and "amount:.
       //Add code to check for balance before transfer
       
       Owner memory mytransfer = owners[_id];
           
       require(approved = true,"You dont have approval to make this transfer");

       require(owners[_id].balance >= _amount, "Your money is too short to make this transaction");
      
       require(mytransfer.owner != mytransfer.recipient,"You cannot send money to yourself that would be money laundering");  
      
       uint previousBalance = owners[_id].balance;  //determine previous balance before transfer
       
       _transfer (_id, _amount);
       
       emit transferDone(_amount, owners[_id].recipient);
       
       assert(owners[_id].balance == previousBalance - _amount); //determine balance after transfer
   }
   
   //Transfer function private.  
    //use underscore when the function is private
     function _transfer (uint id, uint amount)private {
           owners[id].balance -= amount;
         
           
       }
       
      function  getTransferBalance(uint _id)public view returns(address, uint) {
          
          
           return (owners[_id].owner, owners[_id].balance);
   
    }

}
1 Like

I think I’ve solved it without double mapping. I will watch the double mapping video though because I don’t know what that is. Also, I didn’t add much functionality to the constructor because I felt like maybe they would want to add more owners later.

pragma solidity 0.7.5;

/**Here are the requirements of the smart contract wallet you will be building

  • Anyone should be able to deposit ether into the smart contract DONE

  • The contract creator should be able to input (1): the addresses of the owners and DONE
    (2): the numbers of approvals required for a transfer, in the constructor. DONE
    For example, input 3 addresses and set the approval limit to 2.

  • Anyone of the owners should be able to create a transfer request. DONE
    The creator of the transfer request will specify what amount and to what address the transfer will be made.

  • Owners should be able to approve transfer requests.

  • When a transfer request has the required approvals, the transfer should be sent. */

// 999999999999999999 almost 1 ether
// 000000000000000000 18 zeros
// 000000000 9 zeros

contract Multisig {

uint contract_balance;
uint approval_limit = 1;
address creator;
address[] owner_list;
uint owner_number;


constructor() {
    creator = msg.sender;
    owner_list.push(creator);
}

modifier CreatorOnly {
    require(msg.sender == creator);
    _;
}

modifier OwnerOnly { //Checks to see if you are an owner
    bool is_owner = false;
    for(uint i = 0; i < owner_list.length; i++){
        if(msg.sender == owner_list[i]){
            is_owner = true;
        }
    }
    require(is_owner == true, "You aren't an owner!");
    _;
}

function AddOwner(address payable new_owner) public CreatorOnly {
    bool is_owner = false;
    for(uint i = 0; i < owner_list.length; i++){
        if(new_owner == owner_list[i]){
            is_owner = true;
        }
    }
    require(is_owner == false, "He is already an owner!");
    owner_list.push(new_owner);
    owner_number++;
}

function SetApprovalLimit(uint new_limit) public CreatorOnly {
    require(new_limit > approval_limit, "You can only raise the approval limit!");
    approval_limit = new_limit;
}

function deposit() public payable returns(uint){
    contract_balance = contract_balance + msg.value;
    return msg.value;
} 

struct TransferRequest {
    address payable to;//send money to this address
    uint value;//send this much money
    uint votes;//how many people have voted yes/no
    uint vote_yes;//how many people have voted yes
    address[] has_voted;//list of who has voted
}

TransferRequest[] requests;

function AddRequest(address payable _to, uint _value) public OwnerOnly{
    require(_value <= contract_balance, "The account doesn't have enough funds!");
    TransferRequest memory newRequest;
    requests.push(newRequest);
    requests[requests.length-1].to = _to;
    requests[requests.length-1].value = _value;
    requests[requests.length-1].votes = 1;
    requests[requests.length-1].vote_yes = 1;
    requests[requests.length-1].has_voted.push(msg.sender);
    if (approval_limit == 1){
        transact(requests.length - 1); 
    }
}

function ViewRequest(uint _index) public view returns(string memory, address, uint, uint, uint){
    require(_index >= 0);
    require(_index < requests.length);
    string memory view_message = "Addressee, transfer amount (eth), yes-votes, no-votes \n";
    return (view_message, requests[_index].to, requests[_index].value, requests[_index].vote_yes, requests[_index].votes - requests[_index].vote_yes);
}

function Vote(uint _index, uint _vote) public OwnerOnly{
    require(_index >= 0, "Choose a valid index!");
    require(_index < requests.length, "Choose a valid index!");
    require(_vote == 1 || _vote == 0, "Choose 1 for yes and 0 for no!");
    uint have_you_voted = 0;
    for(uint i = 0; i < requests[_index].has_voted.length; i++){
        if(msg.sender == requests[_index].has_voted[i]){
            have_you_voted = 1;
        }
    }
    require(have_you_voted == 0, "You have already voted!");
    requests[_index].votes++;
    requests[_index].vote_yes += _vote;
    requests[_index].has_voted.push(msg.sender);
    
    uint no_votes = requests[_index].votes - requests[_index].vote_yes;
    uint reject_limit = owner_number - approval_limit + 1;
    
    if(requests[_index].vote_yes >= approval_limit){
        transact(_index); 
    }
    else if( no_votes > reject_limit){
        deleteTransaction(_index); 
    }
    
}


function transact(uint _index) private  {
    require(contract_balance > requests[_index].value);
    contract_balance = contract_balance - requests[_index].value;
    requests[_index].to.transfer(requests[_index].value);
    deleteTransaction(_index);
}

function deleteTransaction(uint _index) private {
    requests[_index] = requests[requests.length - 1];
    requests.pop();
}

function view_owners() public view returns(address[] memory){
    //temp_array = owner_list;
    return owner_list;
}

function view_contract_balance() public view returns(uint){
    return contract_balance;
}

function view_approval_limit() public view returns(uint){
    return approval_limit;
}
    
}

Hey @bbss, hope you are great.

I have some issues when copy/paste your contract and compile it to then deploy it.

arguments on the mapping should not have a name unless you refer to a existing variable which is declared outside the mapping (like the struct for example).


Arrays as arguments must declare also the data type.

And so on…Would be great if you can review it and fix this issues so can validate that your contract does the job properly :nerd_face:

If you have any more questions, please let us know so we can help you! :slight_smile:

Carlos Z.

Adding owners to the contract throws me an error, I have manage a solution for it by would like to know you are able to fix it. :nerd_face:

If you have any more questions, please let us know so we can help you! :slight_smile:

Carlos Z.

Great Job, I tested your contract, although it works ok, it could be a little bit confusing.

Steps I made:

  • deposit() (from 1st owner) 3 ethers.
  • createTransfer() 2 ethers (from 1st owner) to 2nd owner.
  • getTransferRequests() to check transfer data.
  • approve() but with 3rd owner and 2nd owner (receiver), transaction approve and send.
  • check balance of 2nd owner.

If you have any more questions, please let us know so we can help you! :slight_smile:

Carlos Z.

Great Job, I tested your contract, although it works ok, it could be a little bit confusing.

Steps I made:

  • deposit() (from 1st owner) 3 ethers.
  • requestTransaction() 2.8 ethers (from 1st owner) to 2nd owner.
  • getTxRequests() to check transfer data.
  • approvalTx() but with 3rd owner and 2nd owner (receiver), transaction approve and send.
  • check balance of 2nd owner.

If you have any more questions, please let us know so we can help you! :slight_smile:

Carlos Z.

Hey @Imhotep, hope you are great.

I think you are not understanding bytes32 variable types that well, the first error i got by just copy/paset the contract and compile it is:

The first variable bytes32 messageHash[index]; it not declared properly, also the return statement should not be a complex operation like the one you are doing.

What i could suggest is (im just letting my mind run, so try to develop the idea by yourself): If you want to use a verified log I would use for example a double mapping for “messageHash” which goes “this address” and this “struct” should have this “bytes32” signature.

If you have any more questions, please let us know so we can help you! :slight_smile:

Carlos Z.

Hey @Capplequoppe, hope you are great.

Your contract works very great, It is a more elaborated contract than the one we teach in the course, but is a very good one, i’m very interesting on what you can program in Smart Contract Programming 201, since you have understand quite well and go far beyond :nerd_face:

If you have any more questions, please let us know so we can help you! :slight_smile:

Carlos Z.

Hi @thecil,
Thanks for checking out the contract. What improvements would you suggest?

If you find some time, could you please also have a look at this contract and help me with the question?
Earning Interest Contract

:pray:

1 Like

Hi,

Sorry for that. Indeed, the code did not compiled. I fixed it:

pragma solidity 0.7.5;

contract MultiSignWallet {
    uint transferId = 0;
    
    address[] ownwers;
    uint nrApprovals;
    address payable walletFunds;
    uint fundsAvailable;
    
    struct TransferRequest {
        uint ammount;
        address payable sendAddress;
        bool paid;
    }
    
    mapping(uint => TransferRequest) transferRequests;
    mapping(uint => address[]) signaturesPerTransferRequest;
    
    constructor(address[] memory _owners, uint _nrApprovals, address payable _walletFunds) {
        ownwers = _owners;
        nrApprovals = _nrApprovals;
        walletFunds = _walletFunds;
    }
    
    // returns transfer id
    function createTransferRequest(uint _ammount, address payable _toAddress) public payable returns (uint) {
        require(addressIsOwner(msg.sender), "A transfer request can only be performed by an owner");
        
        TransferRequest memory transfer = TransferRequest(_ammount, _toAddress, false);
        
        uint currentTransferId = transferId;
        transferRequests[currentTransferId] = transfer;
        signaturesPerTransferRequest[currentTransferId].push(msg.sender);
        if (signaturesPerTransferRequest[currentTransferId].length >= nrApprovals) {
            payTransferRequest(currentTransferId);
        }
        
        transferId++;
        
        return currentTransferId;
    }
    
    // returns true if the necessy number of sgnatures has been reached
    function signTransferRequest(uint _transferId) public payable returns(bool) {
        require(transferRequests[_transferId].ammount > 0, "Cound not find a transfer with this id.");
        require(!transferRequests[_transferId].paid, "This transfer equest is already paid.");
        require(addressIsOwner(msg.sender), "Only an owner can sign the transfer request");
        
        bool callerAlreadySigned = false;
        for(uint i = 0; i < signaturesPerTransferRequest[_transferId].length; i++) {
            if (msg.sender == signaturesPerTransferRequest[_transferId][i]) {
                callerAlreadySigned = true;
                break;
            }
        }
        
        require(!callerAlreadySigned, "A owner cannot sign a transfer request multiple times.");
        
        signaturesPerTransferRequest[_transferId].push(msg.sender);
        if (signaturesPerTransferRequest[_transferId].length >= nrApprovals) {
            payTransferRequest(_transferId);
            return true;
        }
        
        return false;
    }
    
    function addFunds(uint _amount) public payable {
        require(addressIsOwner(msg.sender), "Only an owner can add funds.");
        
        fundsAvailable += _amount;
        walletFunds.transfer(_amount);
    }
    
    function addressIsOwner(address _caller) private returns(bool) {
        for(uint i = 0; i < ownwers.length; i++) {
            if (msg.sender == ownwers[i]) {
                return true;
            }
        }
        
        return false;
    }
    
    function payTransferRequest(uint _id) public payable {
        transferRequests[_id].sendAddress.transfer(transferRequests[_id].ammount);
        fundsAvailable -= transferRequests[_id].ammount;
        transferRequests[_id].paid = true;
    }
}
1 Like

Hello thecil, You are correct I know noting about the bytes32 except that it is a data type used for hashing messages and signatures.

It looks like I was going down the wrong rabbit hole for the project. It was not about signature verification and hashing using the abicode. I will get back to this later.

The project has to do with creating a wallet that required 2/3 owner to give permission to make a transfer but each could make a deposit. So I also thought that I might have gone down the wrong rabbit hole because we were never taught about signature verification and hashing.

So I did start again going down a different rabbit hole using just the tools that we have learned in the class. I was able to create the wallet with no problem but it looks like from the template that he did it differently. He used a double mapping method. I don’t know how this works but will have to look at it. We never used a double mapping and how is this better or worse than the method I used. Can you take a look at that one for me please.

1 Like

I have fixed this function, you are not using the argument _caller on the function, so I just have to use it on the function body, also add view to the function.

    function addressIsOwner(address _caller) private view returns(bool) {
        for(uint i = 0; i < ownwers.length; i++) {
            if (_caller == ownwers[i]) {
                return true;
            }
        }
        
        return false;
    }

You should also check you addFunds() function which is almost correct, you need to add msg.value so it can receive funds. (instead of using an argument amount, use msg.value to manage the “amount” to deposit.

    function addFunds(uint _amount) public payable {
        require(addressIsOwner(msg.sender), "Only an owner can add funds.");
        
        fundsAvailable += _amount;
        walletFunds.transfer(_amount);
    }

If you have any more questions, please let us know so we can help you! :slight_smile:

Carlos Z.

Here is another reply about double mappings that I made long ago.

If you have any more questions, please let us know so we can help you! :slight_smile:

Carlos Z.

1 Like

Hi thecil,

Thank you for the patience in reviewing my code. Indeed, it’s pointless to add that _abount variable.
For the reference, I’ve added the entire solution.

pragma solidity 0.7.5;

contract MultiSignWallet {
    uint transferId = 0;
    
    address[] ownwers;
    uint nrApprovals;
    address payable walletFunds;
    uint fundsAvailable;
    
    struct TransferRequest {
        uint ammount;
        address payable sendAddress;
        bool paid;
    }
    
    mapping(uint => TransferRequest) transferRequests;
    mapping(uint => address[]) signaturesPerTransferRequest;
    
    constructor(address[] memory _owners, uint _nrApprovals, address payable _walletFunds) {
        ownwers = _owners;
        nrApprovals = _nrApprovals;
        walletFunds = _walletFunds;
    }
    
    // returns transfer id
    function createTransferRequest(uint _ammount, address payable _toAddress) public payable returns (uint) {
        require(addressIsOwner(msg.sender), "A transfer request can only be performed by an owner");
        
        TransferRequest memory transfer = TransferRequest(_ammount, _toAddress, false);
        
        uint currentTransferId = transferId;
        transferRequests[currentTransferId] = transfer;
        signaturesPerTransferRequest[currentTransferId].push(msg.sender);
        if (signaturesPerTransferRequest[currentTransferId].length >= nrApprovals) {
            payTransferRequest(currentTransferId);
        }
        
        transferId++;
        
        return currentTransferId;
    }
    
    // returns true if the necessy number of sgnatures has been reached
    function signTransferRequest(uint _transferId) public payable returns(bool) {
        require(transferRequests[_transferId].ammount > 0, "Cound not find a transfer with this id.");
        require(!transferRequests[_transferId].paid, "This transfer request is already paid.");
        require(addressIsOwner(msg.sender), "Only an owner can sign the transfer request");
        
        bool callerAlreadySigned = false;
        for(uint i = 0; i < signaturesPerTransferRequest[_transferId].length; i++) {
            if (msg.sender == signaturesPerTransferRequest[_transferId][i]) {
                callerAlreadySigned = true;
                break;
            }
        }
        
        require(!callerAlreadySigned, "A owner cannot sign a transfer request multiple times.");
        
        signaturesPerTransferRequest[_transferId].push(msg.sender);
        if (signaturesPerTransferRequest[_transferId].length >= nrApprovals) {
            payTransferRequest(_transferId);
            return true;
        }
        
        return false;
    }
    
    function addFunds() public payable {
        require(addressIsOwner(msg.sender), "Only an owner can add funds.");
        
        fundsAvailable += msg.value;
        walletFunds.transfer(msg.value);
    }
    
    function addressIsOwner(address _caller) private view returns(bool) {
        for(uint i = 0; i < ownwers.length; i++) {
            if (_caller == ownwers[i]) {
                return true;
            }
        }
        
        return false;
    }
    
    function payTransferRequest(uint _id) public payable {
        transferRequests[_id].sendAddress.transfer(transferRequests[_id].ammount);
        fundsAvailable -= transferRequests[_id].ammount;
        transferRequests[_id].paid = true;
    }
}
1 Like

This is my Answer. Please feel free to correct me if wrong.

pragma solidity 0.7.5;
pragma abicoder v2;

contract MultiSigWallet {
    
    address owner1;
    address owner2;
    address owner3;
    
    uint requireApprovals;
   
    
    constructor(address _owner1, address _owner2, address _owner3, uint _approvals) {
        owner1 = _owner1;
        owner2 = _owner2;
        owner3 = _owner3;
        
        requireApprovals = _approvals;
    }
    
    modifier onlyOwners {
        require(msg.sender == owner1 || msg.sender == owner2 || msg.sender == owner3, "Only Owner's can execute this function.");
        _;
    }
    
    struct Transfer {
        address payable receiver;
        address initiator;
        uint amount;
        uint approvals;
        bool completed;
    }
    
    mapping(address => uint) private balance;
    
    Transfer[] record;
    
     uint contractBalance;
     
    function depositMoney() public payable returns(uint) {
        contractBalance += msg.value;
        return balance[msg.sender] += msg.value;
    }
    
    function createTransfer(address payable _receiver, uint _amount) public onlyOwners {
        record.push(Transfer(_receiver, msg.sender,_amount, 1, false));
    }
    
    function viewTrasnferStatus(uint _index) public view returns (Transfer memory) {
        return record[_index];
    }
    
    function approveTransfer(uint _index, address _approver) public onlyOwners {
        require(_approver != record[_index].initiator);
        require(record[_index].completed == false);
        record[_index].approvals++;
        if (record[_index].approvals >=2) {
            record[_index].receiver.transfer(record[_index].amount);
            contractBalance -= (record[_index].amount);
            record[_index].completed = true;
        }
    }
    
    function viewContractBalance() public view returns(uint) {
        return contractBalance;
    }
}
1 Like

You still have some issues because you are using incorrectly the payable keyword.

Here is a good explanation about payable keyword: https://ethereum.stackexchange.com/questions/20874/payable-function-in-solidity#20879

My advice: first of all, only use payable on those functions that will receive funds.

In createTransferRequest() this does not have that much sense to me, because the only way the function goes into the condinitoal is by just having 1 approval limit.

        if (signaturesPerTransferRequest[currentTransferId].length >= nrApprovals) {
            payTransferRequest(currentTransferId);
        }

Maybe would have more sense inside signTransferRequest(). :face_with_monocle:

Also it would be way easier to manage the contract if you create some functions to get the contract balance for example or list the requested transfers.

If you have any more questions, please let us know so we can help you! :slight_smile:

Carlos Z.