Project - Multisig Wallet

@filip @jon_m
I decided to try this out on my own without checking for assistance in later videos. What I did was two functions; sign and action. Sign I think makes sure that the function caller is one of the three owners. Action I believe is the function that makes sure you are allowed to continue with the function if 2/3 individuals have signed. I am fairly certain I have gotten this terribly wrong but I am very open to receive critique.

In other words, roast my code please.

pragma solidity 0.7.5;

import “./Ownable.sol”;
import “./Destroyable.sol”;

interface externalInterface{
function addTransaction(address from, address to, uint amount) external;
}

contract helloworld is Ownable, Destroyable{

externalInterface InterfaceDesignation = externalInterface(0xddaAd340b0f1Ef65169Ae5E41A8b10776a75482d);

address owner1;
address owner2;
address owner3;

mapping(address => bool) signed;

mapping(address => uint256) balance;

constructor(){
    owner1 = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;
    owner2 = 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2;
    owner3 = 0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db;
}

function sign() public view{
    require(msg.sender == owner1 || msg.sender == owner2 || msg.sender == owner3);
    require(signed[msg.sender] == false);
    signed[msg.sender] == true;
}

function action() public{
    require((signed[owner1] == true && signed[owner2] == true) 
            || (signed[owner1] == true && signed[owner3] == true)
            || (signed[owner2] == true && signed[owner3] == true));
    signed[owner1] = false;
    signed[owner2] = false;
    signed[owner3] = false;
}

function deposit() public payable returns(uint){
    balance[msg.sender] += msg.value;
    return balance[msg.sender];
}

function withdraw(uint amount) public onlyOwner returns(uint){
    sign();
    action();
    msg.sender.transfer(amount);
    uint newBalance;
    newBalance = balance[msg.sender] - amount;
    return newBalance;
}

function getBalance() public view returns(uint){
    return balance[msg.sender];
}

function transfer(address recipient, uint amount) public {
    sign();
    action();
    require(balance[msg.sender] >= amount);
    require(msg.sender != recipient);
    _transfer(msg.sender, recipient, amount);
    
    InterfaceDesignation.addTransaction(msg.sender, recipient, amount);
    
    uint originalBalance = balance[msg.sender];
    
    assert(balance[msg.sender] == originalBalance - amount);
    
}

function _transfer(address from, address to, uint amount) private {
    balance[from] -= amount;
    balance[to] += amount;
}

}

1 Like

Please see below for my code submission and some notes:

  • I wrote a project overview with variables and transactions before watching the next videos

  • I watched the next videos for some tips and followed the structure of the provided template, but made some alterations

  • I created the code and tested with a few different combinations of multisig (1 of 1, 2 of 3, 3 of 5)

  • I also tested some error conditions in Remix to confirm the requirements were working

  • I haven’t added the ability to add or remove signatories, but I intend on building on this to make something I can use myself :slight_smile:

      pragma solidity 0.7.5;
      pragma abicoder v2;
    
      contract Wallet {
          
          uint requiredSigs;
          uint numTransfers;
          uint balance;
    
          struct TransferRequest {
              uint amount;  //all in wei to save gas costs. Assume UI to deal with conversion
              address payable receiver;
              uint approvals;
              bool hasBeenSent;
              uint id;
          }
    
          TransferRequest[] transferRequests;
    
          mapping(address => bool) signatories;
          mapping(address => mapping(uint => bool)) approvals; //address to id to approval
          
          event transferRequested(uint amount, address receiver, uint id);
          event transferSigned(uint id, address approver);
          event transferSent(uint id);
    
          modifier onlyOwners() {
              require(signatories[msg.sender], "Not authorized to call function");
              _;
          }
    
          constructor(address[] memory _signatories, uint _requiredSigs) {
              uint x;
              for(x=0; x < _signatories.length; x++) {
                  signatories[_signatories[x]] = true;
              }
              if (_requiredSigs <= 0) {
                  requiredSigs = 1;
              }
              else {
                  requiredSigs = _requiredSigs;
              }
              numTransfers = 0;
              balance = 0;
              
          }
    
          function deposit() public payable {
              balance += msg.value;
          }
    
          function createTransfer(uint _amount, address payable _receiver) public onlyOwners {
              uint _id = numTransfers;
              numTransfers++;
              transferRequests.push(TransferRequest(_amount, _receiver, 0, false, _id));
              emit transferRequested(_amount, _receiver, _id);
          }
    
          function approve(uint _id) public onlyOwners {
              require(transferRequests[_id].hasBeenSent == false, "Transaction has already been authorized and sent");
              require(approvals[msg.sender][_id] == false, "Sender has already authorized transaction");
              require(address(this).balance >= transferRequests[_id].amount, "Insufficient balance to send transfer");
              
              approvals[msg.sender][_id] = true;
              transferRequests[_id].approvals++;
              emit transferSigned(_id, msg.sender);
              
              if (transferRequests[_id].approvals == requiredSigs) {
                  balance -= transferRequests[_id].amount;
                  transferRequests[_id].hasBeenSent = true;
                  transferRequests[_id].receiver.transfer(transferRequests[_id].amount);
                  emit transferSent(_id);
              }
          }
    
          function getTransferRequests() public view onlyOwners returns (TransferRequest[] memory) {
              return transferRequests;
          }
          
          function getBalance() public view onlyOwners returns (uint) {
              return balance;
          }
    
      }

I gave it a try without going past the first video. It kind of works and doesnt have any fancy new stuff, kind of straightforward. I tried using require statements, perhaps someone can say why it isnt working(last part of code that commented out), so I did it with if statements. It works but transaction passes even if the withdrawal is not approved because it returns a false for the function I guess. This is probably not what you were looking for but kind of does the job anyways, (three first addresses in remix have the governing power of this contract to approved withdrawal or not:

pragma solidity 0.7.5;
pragma abicoder v2;
import “./ownable.sol”;

contract MultiSigWallet is ownable{

    mapping(address => bool) approved;
    

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

function getContractBalance()public view returns(uint){
    return address(this).balance;
}

function approve()public {
    approved[msg.sender] = true;
}

function withdraw()public onlyOwner returns(bool){
    if(approved[0x5B38Da6a701c568545dCfcB03FcB875f56beddC4] && approved[0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2]){
        
        approved[0x5B38Da6a701c568545dCfcB03FcB875f56beddC4]=false;
        approved[0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2]=false; 
        owner.transfer(address(this).balance);
        return true;
        }
        
        else if(approved[0x5B38Da6a701c568545dCfcB03FcB875f56beddC4] && approved[0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db]){
            
            approved[0x5B38Da6a701c568545dCfcB03FcB875f56beddC4]=false;
            approved[0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db]=false; 
            owner.transfer(address(this).balance);
            return true;
            
        }
        
        else if(approved[0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2] && approved[0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db]){
            
            approved[0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2]=false;
            approved[0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db]=false;
            owner.transfer(address(this).balance);
            return true;
            
        }
        
        else return false;
    }

}

/*

function withdraw()public returns(bool){
require((approved[0x5B38Da6a701c568545dCfcB03FcB875f56beddC4] && approved[0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2]) ||
approved[0x5B38Da6a701c568545dCfcB03FcB875f56beddC4] && approved[0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db]) ||
approved[0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2] && approved[0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db]),“Withdrawal has to be approved by 2 parties”);

   approved[0xdD870fA1b7C4700F2BD7f44238821C26f7392148]=false;
   approved[0x5B38Da6a701c568545dCfcB03FcB875f56beddC4]=false;
   approved[0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db]=false;
   owner.transfer(address(this).balance);
   return true;
}

*/

Guys I get this strange compiler errors

browser/MultisigWallet.sol:44:9: TypeError: Different number of arguments in return statement than in returns declaration. return voteCounter++; ^------------------^

From the following code:

//Seperate the owner from the initial contract
// SPDX-License-Identifier: MIT
pragma solidity 0.7.5;
pragma abicoder v2; // Imported to handle structs

contract WalletOwnership {

    // Main wallet owner
    address payable public walletMainOwner; // So we can add money to the contract ????
    
    mapping(address => walletUser) public walletUsers; // Adddress user from their address 
    
    struct walletUser { // struct for wallet users
        address memberAddress;
        bool voteStatus;
        bool memberStatus;
    }
    
    modifier onlyMainOwner { // Add modifier for the main owner 
        require(msg.sender == walletMainOwner);
        _;
    }
    
    // Variable with number of approvals required 
    uint public numberOfApprovalsRequired =3;
    uint public voteCounter =0;
    
    constructor(){ // Wallet governance
        walletMainOwner = msg.sender; // Adding owner to the initilizer
    }
    
    function addUserToMembers(address userAddress) public onlyMainOwner{
        walletUsers[userAddress].memberStatus = true;
    }
    
    function addUserToContract() public { // Anyone can add themself to the wallet
        walletUser storage userToAdd; 
        userToAdd.memberAddress = msg.sender;
        userToAdd.memberStatus = false;
        userToAdd.memberAddress = msg.sender;
        walletUsers[msg.sender] = userToAdd;
    }
    
    function getVoteResult(address ofMember) public  returns (bool) {
        return walletUsers[ofMember].voteStatus;
    }

}
1 Like
pragma solidity 0.7.5;
pragma abicoder v2;

contract Wallet {
    address[] private owners;
    mapping(address => mapping(uint => Transfer[])) aprovs; // all transacions history, approved or pending
        uint balance; // balance of wallet
    uint ids = 1;  // incremental int to make a unique id
    Transfer[] private transfers; //all transacions
    
    struct Transfer{
        uint id;
        uint amount;
        address payable receiver;
        uint qty; // approvals quantity
        bool pending; // true = not realized
    }
    

    constructor(address[] memory _owners) public {
       owners = _owners;
        balance = 100;
    }
    
    function getOwners() public returns(address[] memory){
        return  owners;
    }
    
        function getBalance() public returns(uint){
        return  balance;
    }
    
    function getAprovs() public returns(Transfer [] memory){
        Transfer[] memory transfers =  aprovs[msg.sender][0];
        return transfers;
    }
    

    function startTransfer(uint _amount,  address payable _receiver) public returns(Transfer memory){
        require(balance >= _amount, "Insuficient founds");
        Transfer memory transfer;
        transfer.amount = _amount;
        transfer.receiver = _receiver;
        transfer.pending = true;
        transfer.id = ids;
        ids++;
        for(uint i = 0 ; i < owners.length; i++){
            aprovs[owners[i]][0].push(transfer); //add transaction to transacions of one owner and set it to false;
        }
        transfers.push(transfer);
        return transfer;
    }
    
    modifier ownerCheck()  { // can improve?
        bool aux = false;
        for(uint i = 0 ; i < owners.length; i++)
            if(owners[i] == msg.sender){
                aux = true;
                break;
            }
            
        require(aux == true);
        _;
                
    }
    
    function approve(uint id) ownerCheck   public returns ( string memory ) {
            uint i = checkIfHasBeenApproved(id); //get index of transacion
            transfers[id - 1].qty++;
            aprovs[msg.sender][1].push( aprovs[msg.sender][0][i]);
            delete aprovs[msg.sender][0][i];
            
            if(transfers[i].qty > owners.length/2 && transfers[i].pending == true){ // if more than half approve and check if this isnt approved yet.
                balance -= transfers[i].amount;
                transfers[i].pending = false;
            }
    }
    
    function  checkIfHasBeenApproved(uint id) private returns (uint){
        bool aux = false;
        Transfer[] memory TransfersNotConfirmed = aprovs[msg.sender][0];
        uint i ;
        for( i = 0; i < TransfersNotConfirmed.length; i++)
            if(TransfersNotConfirmed[i].id == id ){
                aux = true;
                break;
            }
            
        require(aux == true);
        return i;
    }

}

Actually, I don’t know if I met all the requirements
If i miss something or if i can improve something ( like the modifier functions ) please tell me :slight_smile:

Well, here is my super rudimentary approach for now. At least I won’t feel guilty when I start watching the rest of the videos, I guess!

pragma solidity 0.7.5;
pragma abicoder v2;

contract MultiSigWallet {
    address public owner;
    Approval[2] public cosigners;
    uint256 balance;

    struct Approval {
        address approver;
        uint256 maximumAmount;
    }

    modifier onlyowner() {
        require(owner == msg.sender);
        _;
    }

    // We need to have 2 out of 3 signatures to approve the transferring of funds.
    // Assume that the owner of the contract is one of the signers, and the other
    // two must be provided to the contract's constructor.
    constructor(address cosigner1, address cosigner2) {
        owner = msg.sender;
        cosigners[0] = Approval({approver: cosigner1, maximumAmount: 0});
        cosigners[1] = Approval({approver: cosigner2, maximumAmount: 0});
    }
    
    function fund( uint256 amount) public onlyowner {
        balance += amount;
    }
    
    function getBalance() public view returns (uint256) {
        return balance;
    }
    
    // When the sender calls sign(), it will approve a maximum transfer amount,
    // which is saved to the contract.  If the transfer actually occurs, then
    // the actual transfer amount will be subtracted from this maximum amount.
    function sign( uint256 maximumAmount) public {
        // if the sender isn't one of the cosigners, bail out.
        require( msg.sender == cosigners[0].approver || msg.sender == cosigners[1].approver);
            
        if( msg.sender == cosigners[0].approver)
            cosigners[0].maximumAmount = maximumAmount;
        else
            cosigners[1].maximumAmount = maximumAmount;
    }
    
    function transfer( address payable payableTo, uint256 amount) public payable {
        require( balance >= amount && cosigners[0].maximumAmount >= amount && cosigners[1].maximumAmount >= amount);
        balance -= amount;
        cosigners[0].maximumAmount -= amount;
        cosigners[1].maximumAmount -= amount;
        payableTo.transfer(msg.value);
    }
}

I tested a few cases and it seems to work. Basically, my simple approach was to:

  • deploy the contract, specifying the 2 other signers’ addresses
  • call fund() to establish an initial contract balance
  • call sign(), passing a maximum allowable (total) transfer amount, once per signer
  • call transfer(), passing the payable address and the amount

I verified that 2 cosigners need to be filled out, and that the balances need to satisfy the requested transfer amount.

I looked over the code after posting, and it occurred to me that sign() should probably also be onlyowner, thus requiring me to pass in the signer’s address as well.

I only watched the first video because I think I understood the assignment well enough to give it a go.

I essentially created a Transaction struct that requires at least 3 “signers” before executing. Naturally not anyone can sign a transaction, so the owner has the ability to add (or remove) signers.

Normal users can deposit freely, but if they want to transfer or withdraw funds, the transaction must be signed first. So, instead of the end user calling the transfer or withdraw functions directly, they instead create a pending transaction. Once enough admins have signed on the transaction, the actual transaction executes.

Now to watch the rest of the videos and find out how inefficient I probably was :sweat_smile:

Here is my full code:

contract Ownable {
    
    address internal owner;
    
    modifier onlyOwner {
        require(msg.sender == owner, "This action only available to contract owner");
        _; // run the function
    }
    
    constructor() {
        owner = msg.sender;
    }
    
}

contract MultiSig is Ownable {
    
    modifier onlySigner {
        require(isSigner[msg.sender] == true, "Only registered signers and sign a transaction.");
        _;
    }
    
    mapping(address => bool) isSigner;
    
    function addSigner(address _signer) public onlyOwner {
        isSigner[_signer] = true;
    }
    
    function removeSigner(address _signer) public onlyOwner {
        isSigner[_signer] = false;
    }
    
    function checkSigner(address _address) public view returns(bool) {
        return isSigner[_address];
    }
    
}

contract Transactable {
    
    struct Transaction {
        uint id;
        address payer;
        address payee;
        uint amount;
        address[] signers;
        bool approved;
        bool completed;
    }
    
    Transaction[] txns;
    
    function viewTransactions() public view returns(Transaction[] memory) {
        return txns;
    }
    
}

contract Destroyable is Ownable {
    
    function destroy() public onlyOwner {
        address payable receiver = msg.sender;
        selfdestruct(receiver);
    }
    
}

interface Gov {
    function logTransaction(address _from, address _to, uint _amount) external payable;
}

contract Bank is Destroyable, Transactable, MultiSig {
    
    Gov govInstance = Gov(0xd9145CCE52D386f254917e481eB44e9943F39138);
    
    mapping(address => uint) balance;
    
    event txnRequested(uint txnId, address payer, address payee, uint amount);
    event txnSigned(uint txnId, address signer);
    event txnApproved(uint txnId);
    event deposited(uint amount, address depositiedBy);
    event transfered(uint amount, address from, address to);
    event withdrawn(uint amount, address withdrawnBy);
    
    
    function deposit() public payable {
        require(msg.value > 0, "Cannot deposit a zero amount");
        
        address[] memory signers;
        txns.push( Transaction(txns.length, msg.sender, address(this), msg.value, signers, true, true) );
        balance[msg.sender] += msg.value;
        
        emit deposited(msg.value, msg.sender);
    }
    
    function getBalance() public view returns(uint) {
        return balance[msg.sender];
    }
    
    function startTransfer(address _payee, uint _amount) public returns(uint) {
        require(balance[msg.sender] >= _amount, "Insufficient Funds");
        require(msg.sender != _payee, "Can't send to self");
        
        address[] memory signers;
        txns.push( Transaction(txns.length, msg.sender, _payee, _amount, signers, false, false) );
        
        emit txnRequested(txns.length - 1, msg.sender, _payee, _amount);
        
        return txns.length -1;
    }
    
    function startWithdrawal(uint _amount) public returns(uint) {
        require(balance[msg.sender] >= _amount, "Insufficient Funds");
        
        address[] memory signers;
        txns.push( Transaction(txns.length, address(this), msg.sender, _amount, signers, false, false) );
        
        emit txnRequested(txns.length -1, address(this), msg.sender, _amount);
        
        return txns.length - 1;
    }
    
    function signTransaction(uint _id) public onlySigner {
        require(txns[_id].approved == false, "This transaction is already approved");
        for( uint i = 0; i < txns[_id].signers.length; i++) {
            require(txns[_id].signers[i] != msg.sender, "You have already signed this transaction.");
        }
        
        txns[_id].signers.push(msg.sender);
        emit txnSigned(_id, msg.sender);
        
        if (txns[_id].signers.length > 2) {
            txns[_id].approved = true;
            emit txnApproved(_id);
            _completeTransaction(_id);
        }
    }
    
    function _completeTransaction(uint _txnId) private {
        require(txns[_txnId].approved, "Cannot complete a transaction until it is approved");
        
        if (txns[_txnId].payer == address(this)) _withdraw(_txnId);
        else _transfer(_txnId);
        
        txns[_txnId].completed = true;
        
        govInstance.logTransaction(txns[_txnId].payer, txns[_txnId].payee, txns[_txnId].amount);
    }
    
    function _withdraw(uint _txnId) private {
        require(balance[txns[_txnId].payee] >= txns[_txnId].amount, "Insufficient Funds");
        require(txns[_txnId].payer == address(this), "This transaction is not a withdrawal.");
        
        payable(txns[_txnId].payee).transfer(txns[_txnId].amount);
        balance[txns[_txnId].payee] -= txns[_txnId].amount;
        
        emit withdrawn(txns[_txnId].amount, txns[_txnId].payee);
    }
    
    function _transfer(uint _txnId) private {
        require(balance[txns[_txnId].payer] >= txns[_txnId].amount, "Insufficient Funds");
        require(txns[_txnId].payer != address(this), "This transaction is not a transfer.");
        
        balance[txns[_txnId].payer] -= txns[_txnId].amount;
        balance[txns[_txnId].payee] += txns[_txnId].amount;
        
        emit transfered(txns[_txnId].amount, txns[_txnId].payer, txns[_txnId].payee);
    }
    
}

Yeah, I had the same issue - no requirements listed in the assignment post. @filip can you please update it? Judging from the solution video, I think the comments Filip had entered above his method declarations are basically the requirements. But at this point, I think I have extracted the important information from the assignment and can move on to the game programming course.

2 Likes

Hi @gkassaras, hope you are well.

I have copy/paste your code in order to test it. I got this issues and I was able to fix them.

First Error:

That one refers to the use of memory instead of storage, since you want to add a user to the contract, you need to assign the data in a database(blockchain), the keyword storage helps us to refer that this data must be saved in the blockchain. So by switching storage to memory the functions works properly.

Next error was:

In most of the cases for a “get” function, means getting information from our contract, referred to get data from the blockchain, you should use the keyword view to ensure that the function will be a “read-only” function, meaning it will not modify any variable, just read a data and return its value.

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

Carlos Z.

1 Like

I’m overwhelmed by the effort that you are putting into this. I had forgotten to add the requirements to the actual lecture. They have been added now and will give you a better idea of what you should do. But many of you are on the right track and we will be looking through your code over the next few days and give you some feedback.

5 Likes

Currently having issues with remix. Getting out of memory errors. Is there a setting I need to change or is this just a transient remix issue?

I only watched the first video and thought it would be a nice challenge to just take it from there.

For some reason this code stopped working since yesterday. If you run addTransaction it gives out of memory error. Worked fine yesterday. Perhaps some changes in remix. I don’t know. Any suggestions?

pragma solidity 0.7.5;
pragma abicoder v2;

contract Ownable {
    // normally would be in separate file kept here fore easy copy paste into forum
     address payable owner;
     
     modifier onlyOwner {
        
        require(msg.sender == owner, "function restricted to owner");
        _; // run the function
    }
    
    constructor(){
        owner=msg.sender;
    }
    
}

contract Destroyable is Ownable {
    // normally would be in separate file kept here fore easy copy paste into forum
    
    event contractDestroyed();
    
    function close() public onlyOwner { //onlyOwner is custom modifier
    emit contractDestroyed();
    selfdestruct(owner);  // `owner` is the owners address
    }
    
}

contract MultiSignable is Ownable, Destroyable{
    // normally would be in separate file kept here fore easy copy paste into forum
     
     mapping (address => bool) private authorized;
     uint private numberOfSignatories;
     uint minimumSignatures;
     
     modifier onlySignatories {
        
        require(authorized[msg.sender] == true, "function restricted to authorized signatories");
        _; // run the function
    }
    
    constructor (address payable _signatory1, address payable _signatory2, address payable _signatory3, uint _minimumSignatures) {
        // check if signatories are not the same
        
        require ((_signatory1 != _signatory2) && (_signatory2 != _signatory3) && (_signatory3 != _signatory1), "non unique signatories");
        
        // Initialize variables
        minimumSignatures = _minimumSignatures; // in a future version there can be different minimum or dynamically changing based on number of signatories
        numberOfSignatories = 0;
        
        // Add signatories to authorized mapping
        addSignatory(_signatory1);
        addSignatory(_signatory2);
        addSignatory(_signatory3);
        

        
        // logs etc.

    }
    
    function addSignatory(address payable _signatory) internal onlyOwner{
        // checks 
        require ((authorized[_signatory]!=true), "Signatory already exists");
        
        authorized[_signatory]=true;
        numberOfSignatories++;
        
        // logs
    }
    
    function removeSignatory(address payable _signatory) internal onlyOwner{
        // This function can be l
        // checks
        require ((authorized[_signatory]==true), "Signatory already not authorized");
        require ((numberOfSignatories - 1) >= minimumSignatures, "Can't remove signatories below minimum number of signatures");
        
        authorized[_signatory]=false;
        numberOfSignatories--;
        
        // logs
    }
    
    function isAuthorized(address payable _signatory) internal view returns (bool){
        return authorized[_signatory];
    }
    
    function checkSignatures(address payable[] memory _signatures) internal view returns (bool valid){
        // if not enough signatures can return false immediately otherwise count valid signatures;
        if (_signatures.length < minimumSignatures){
            return false;
        }
        uint validsig=0;
        // traverse array check if signatures are still valid
        uint arrayLength = _signatures.length;
        for (uint i=0; i<arrayLength; i++) {
            // check signature
            if (isAuthorized(_signatures[i])){
              validsig++;
            }
        }
        if (validsig < minimumSignatures){
            return false;
        } else {
            return true;
        }
        
    }
    
    function addSignature(address payable _signatory, address payable[] storage _signatures) internal onlySignatories returns (bool added){
        // Checks if signature not already in list, if not add and return new list else return empty list
        // traverse array check if signatory hasn't signed already
        bool double = false;
        uint arrayLength = _signatures.length;
        for (uint i=0; i<arrayLength; i++) {
            // check signature
            if (_signatures[i] == _signatory){
              double = true;
              require (double==false, "Not allowed to sign twice");
            } 
        }
        if (double==false){
            _signatures.push(_signatory);
            return true;
        }
    }
}

contract MultiSigWallet is MultiSignable {
    /* 
        Going for a MVP for a MultiSigWallet for assignment
        Wallet is initialized with 2 extra signatory addresses
        Everyone can deposit
        For payment to be made from wallet at least two signatories must sign the transaction
        First payment request is queued till a second signatory signs request (code prepared for more than two signatures)
    */
    
    // Struct to hold transaction could be more elaborate with description etc
    struct Transaction {
      uint transID;
      address payable recipient;
      uint amount;
      address payable[] signaturelist;

    }
    
    Transaction[] TransactionList;
    uint transID;
   

    constructor (address payable _signatory2, address payable _signatory3, uint _minimumSignatures) MultiSignable( msg.sender, _signatory2, _signatory3, _minimumSignatures){
        transID=1;
    }
    
    event depositDone(uint amount, address indexed by);
    event amountTransfered(uint indexed transID, uint amount, address indexed transferedTo);
    event transactionAdded(uint transID, uint amount, address indexed transferedTo, address indexed addedBy);
    event transactionSigned(uint indexed transID, uint amount, address indexed transferedTo, address indexed signedBy);
    
    function deposit() public payable returns(uint){
        emit depositDone(msg.value, msg.sender);
        return address(this).balance;
        
    }
    
    
    function addTransaction(uint _amount, address payable _recipient) public onlySignatories{
        // checks
        require((_amount>0), "can't transfer zero amount");
        // Not checking for insufficient balance
        // There is no way of checking now if funds will be available when transaction is actually processed
        // Will when actually sending or let transfer function handle the exception 
        
         //Push transaction onto uncompletedTransactions
        
        address payable[] storage signatures; 
        signatures.push(msg.sender);
        
        TransactionList.push(Transaction(transID, _recipient, _amount, signatures));
        
        //TransactionList.push(transaction);
        emit transactionAdded(transID, _amount, _recipient, msg.sender);
        transID++;
        

    }
    
    function signTransaction(uint _transID, address payable _recipient) public onlySignatories returns (uint o_balance){
        
        // Get transacion data based on _transID and check if the transaction exists and _recipient matches
        
        // Lookup transaction by _transID and return transaction if found and empty transaction if not found
        uint indx = lookupTransaction(_transID);
        uint arrayindx = 0;
        bool added = false;
        
        require (indx>0, "Can't sign nonexisting transaction.");
        
        arrayindx = indx-1; // zero used to signal tx not found


        // compare address to make sure it's right transaction
        
        require(TransactionList[arrayindx].recipient == _recipient, "Transaction recipient doesn't match transaction id provided");
        added = addSignature(msg.sender, TransactionList[arrayindx].signaturelist);
        if (added){
            emit transactionSigned(_transID, TransactionList[arrayindx].amount, TransactionList[arrayindx].recipient, msg.sender);
            // check if we meet requirements to transfer
            if (checkSignatures(TransactionList[arrayindx].signaturelist)){
                //execute transaction and remove from uncompletedTransactions
                
                _recipient.transfer(TransactionList[arrayindx].amount); // we'll let transfer handle balance deficiency exceptions
                
        
                delete TransactionList[arrayindx];// more efficient delete possible by copying last transaction and reducing array length
                
                return address(this).balance;
                
            }
            
        }
        
        return address(this).balance;
        
    }
    
    function getTransactionsList() public view onlySignatories returns(Transaction[] memory) {
        // can be used by frontend to show all uncompleted transactions
        return TransactionList;
    }
    
    function getBalance() public view returns(uint){
        // mostly for testing purposes
        return address(this).balance;
        
    }
    
    
    function getTransaction(uint _transID) internal view returns(Transaction memory o_transaction){
        // Lookup transaction by _transID and return transaction if found or empty transaction if not found
        uint indx = lookupTransaction(_transID);
        uint arrayindx = 0;
        
        if (indx !=0){
            arrayindx = indx-1; // using zero to singal not found
            return TransactionList[arrayindx];
        } else{
            // if no transaction is found return empty transaction
        return o_transaction;
        }
              
    }
    
    function lookupTransaction(uint _transID) internal view returns(uint indx){
        // traverse array find transaction by _transID and return array index +1 if found and zero if not found
        uint arrayLength = TransactionList.length;
        for (uint i=0; i<arrayLength; i++) {
            // check if we've found our transaction
            if (TransactionList[i].transID==_transID){
              return i+1;
            }
        }
        // if no transaction is found return empty transaction
        return 0;
    }
    
}
1 Like

Hi @Vivek20, hope you are well.

Now apparently the issues on your addTransaction functions goes on a internal declaration of the array signatures, this variable will be created only when the function is invoked also since is set as storage could go into issues, because every time the function is invoked, it will try to create the variable.

So i just moved that variable declaration outside of the function so can be global for the entire contract (multiSigWallet), remove the storage keyword and now its looks like your entire contract is working properly.

Try to do the same and let me know how it goes :nerd_face:

Carlos Z

Thanks for your response. I was thinking if I make it global I’ll have to cleanup everytime before a transaction is added because it will have the data of the last array still there. I think I’ll put the signature as first item of the array instead of push. I’ll test and let you know.

1 Like

Hi Carlos @thecil , hope you’re doing well too. Thanks for the insight on the storage value. Very strange the code somehow did work before there must have been some change in remix. Anyway I moved the signatures global and added some initialization in the constructor and now just set the first item to msg.sender in addTransaction. Just ran some tests and everything works fine. So I would like to submit the code below for the assignment. Thanks again!

pragma solidity 0.7.5;
pragma abicoder v2;

contract Ownable {
    // normally would be in separate file kept here fore easy copy paste into forum
     address payable owner;
     
     modifier onlyOwner {
        
        require(msg.sender == owner, "function restricted to owner");
        _; // run the function
    }
    
    constructor(){
        owner=msg.sender;
    }
    
}

contract MultiSignable is Ownable {
    // normally would be in separate file kept here fore easy copy paste into forum
     
     mapping (address => bool) private authorized;
     uint private numberOfSignatories;
     uint minimumSignatures;
     
     modifier onlySignatories {
        
        require(authorized[msg.sender] == true, "function restricted to authorized signatories");
        _; // run the function
    }
    
    constructor (address payable _signatory1, address payable _signatory2, address payable _signatory3, uint _minimumSignatures) {
        // check if signatories are not the same
        
        require ((_signatory1 != _signatory2) && (_signatory2 != _signatory3) && (_signatory3 != _signatory1), "non unique signatories");
        
        // Initialize variables
        minimumSignatures = _minimumSignatures; // in a future version there can be different minimum or dynamically changing based on number of signatories
        numberOfSignatories = 0;
        
        // Add signatories to authorized mapping
        addSignatory(_signatory1);
        addSignatory(_signatory2);
        addSignatory(_signatory3);
        

        
        // logs etc.

    }
    
    function addSignatory(address payable _signatory) internal onlyOwner{
        // checks 
        require ((authorized[_signatory]!=true), "Signatory already exists");
        
        authorized[_signatory]=true;
        numberOfSignatories++;
        
        // logs
    }
    
    function removeSignatory(address payable _signatory) internal onlyOwner{
        // This function can be l
        // checks
        require ((authorized[_signatory]==true), "Signatory already not authorized");
        require ((numberOfSignatories - 1) >= minimumSignatures, "Can't remove signatories below minimum number of signatures");
        
        authorized[_signatory]=false;
        numberOfSignatories--;
        
        // logs
    }
    
    function isAuthorized(address payable _signatory) internal view returns (bool){
        return authorized[_signatory];
    }
    
    function checkSignatures(address payable[] memory _signatures) internal view returns (bool valid){
        // if not enough signatures can return false immediately otherwise count valid signatures;
        if (_signatures.length < minimumSignatures){
            return false;
        }
        uint validsig=0;
        // traverse array check if signatures are still valid
        uint arrayLength = _signatures.length;
        for (uint i=0; i<arrayLength; i++) {
            // check signature
            if (isAuthorized(_signatures[i])){
              validsig++;
            }
        }
        if (validsig < minimumSignatures){
            return false;
        } else {
            return true;
        }
        
    }
    
    function addSignature(address payable _signatory, address payable[] storage _signatures) internal onlySignatories returns (bool added){
        // Checks if signature not already in list, if not add and return new list else return empty list
        // traverse array check if signatory hasn't signed already
        bool double = false;
        uint arrayLength = _signatures.length;
        for (uint i=0; i<arrayLength; i++) {
            // check signature
            if (_signatures[i] == _signatory){
              double = true;
              require (double==false, "Not allowed to sign twice");
            } 
        }
        if (double==false){
            _signatures.push(_signatory);
            return true;
        }
    }
}

contract MultiSigWallet is MultiSignable {
    /* 
        Going for a MVP for a MultiSigWallet for assignment
        Wallet is initialized with 2 extra signatory addresses
        Everyone can deposit
        For payment to be made from wallet at least two signatories must sign the transaction
        First payment request is queued till a second signatory signs request (code prepared for more than two signatures)
    */
    
    // Struct to hold transaction could be more elaborate with description etc
    struct Transaction {
      uint transID;
      address payable recipient;
      uint amount;
      address payable[] signaturelist;

    }
    
    Transaction[] TransactionList;
    uint transID;
    
    address payable[] signatures;//holds signature for new transaction
   

    constructor (address payable _signatory2, address payable _signatory3, uint _minimumSignatures) MultiSignable( msg.sender, _signatory2, _signatory3, _minimumSignatures){
        transID=1;
        signatures.push(msg.sender);
    }
    
    event depositDone(uint amount, address indexed by);
    event amountTransfered(uint indexed transID, uint amount, address indexed transferedTo);
    event transactionAdded(uint transID, uint amount, address indexed transferedTo, address indexed addedBy);
    event transactionSigned(uint indexed transID, uint amount, address indexed transferedTo, address indexed signedBy);
    
    function deposit() public payable returns(uint){
        emit depositDone(msg.value, msg.sender);
        return address(this).balance;
        
    }
    
    
    function addTransaction(uint _amount, address payable _recipient) public onlySignatories{
        // checks
        require((_amount>0), "can't transfer zero amount");
        // Not checking for insufficient balance
        // There is no way of checking now if funds will be available when transaction is actually processed
        // Will when actually sending or let transfer function handle the exception 
        
         //Push transaction onto uncompletedTransactions
        
         
        signatures[0]=msg.sender;
        
        TransactionList.push(Transaction(transID, _recipient, _amount, signatures));
        
        //TransactionList.push(transaction);
        emit transactionAdded(transID, _amount, _recipient, msg.sender);
        transID++;
        

    }
    
    function signTransaction(uint _transID, address payable _recipient) public onlySignatories returns (uint o_balance){
        
        // Get transacion data based on _transID and check if the transaction exists and _recipient matches
        
        // Lookup transaction by _transID and return transaction if found and empty transaction if not found
        uint indx = lookupTransaction(_transID);
        uint arrayindx = 0;
        bool added = false;
        
        require (indx>0, "Can't sign nonexisting transaction.");
        
        arrayindx = indx-1; // zero used to signal tx not found


        // compare address to make sure it's right transaction
        
        require(TransactionList[arrayindx].recipient == _recipient, "Transaction recipient doesn't match transaction id provided");
        added = addSignature(msg.sender, TransactionList[arrayindx].signaturelist);
        if (added){
            emit transactionSigned(_transID, TransactionList[arrayindx].amount, TransactionList[arrayindx].recipient, msg.sender);
            // check if we meet requirements to transfer
            if (checkSignatures(TransactionList[arrayindx].signaturelist)){
                //execute transaction and remove from uncompletedTransactions
                
                _recipient.transfer(TransactionList[arrayindx].amount); // we'll let transfer handle balance deficiency exceptions
                
        
                delete TransactionList[arrayindx];// more efficient delete possible by copying last transaction and reducing array length
                
                return address(this).balance;
                
            }
            
        }
        
        return address(this).balance;
        
    }
    
    function getTransactionsList() public view onlySignatories returns(Transaction[] memory) {
        // can be used by frontend to show all uncompleted transactions
        return TransactionList;
    }
    
    function getBalance() public view returns(uint){
        // mostly for testing purposes
        return address(this).balance;
        
    }
    
    
    function getTransaction(uint _transID) internal view returns(Transaction memory o_transaction){
        // Lookup transaction by _transID and return transaction if found or empty transaction if not found
        uint indx = lookupTransaction(_transID);
        uint arrayindx = 0;
        
        if (indx !=0){
            arrayindx = indx-1; // using zero to singal not found
            return TransactionList[arrayindx];
        } else{
            // if no transaction is found return empty transaction
        return o_transaction;
        }
              
    }
    
    function lookupTransaction(uint _transID) internal view returns(uint indx){
        // traverse array find transaction by _transID and return array index +1 if found and zero if not found
        uint arrayLength = TransactionList.length;
        for (uint i=0; i<arrayLength; i++) {
            // check if we've found our transaction
            if (TransactionList[i].transID==_transID){
              return i+1;
            }
        }
        // if no transaction is found return empty transaction
        return 0;
    }
    
}
1 Like

LOL oh well made me work a bit more. Mine supports multiple multisig groups along with functions to confirm the group before accepting transactions.

1 Like

Hi again. If anyone could help, would be much appreciated.

Im trying to make it so that the owner addresses with voting power can be changed easily when the contract is deployed, by having them as arguments in the constructor.

The contract compiles but fault back with invalid op-code when deployed. I made a debugging contract with only the constructor and get the same problem. Can anyone tell me why it doesnt work?

contract debuggingContract{

address payable public owner;

modifier onlyOwner{
    require(msg.sender == owner, "Only owner has access");
    _;
}

address [] public owners;

// Setting up owner accounts

constructor (address payable _owner1, address payable _owner2, address payable _owner3){
    owners[0] = _owner1;
    owners[1] = _owner2;
    owners[2] = _owner3;
    
    owner = msg.sender;
    
    }

}

1 Like

Hi @Kraken, hope you are well.

You issue appears for a bad syntax on your owners array, indexes are not specified at the begining, so you cant set owners[0] for example at the start, you have to use push instead to bind a value on index 0.

Here is your code properly working with those few adjustments.

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

Carlos Z.

1 Like

I put this together on my own, and I haven’t watched any of the videos past the intro for the project. I suspect this is highly inefficient, but based on my testing, it does what the project requires. I will review the full code video and see how mine lines up (or doesn’t).

pragma solidity 0.7.5;

contract MultiSigWallet{
    
    mapping(address => uint) balance;
    
    event depositDone(uint amount, address indexed depositedTo);
    event transferDone(uint amount, address indexed receiver);
    
    address[] owners;
    Transfer[] transfers;
    uint approvalNum;
    
    struct Transfer {
        uint id;
        address requestor;
        address receiver;
        uint amount;
        uint approvals;
    }
    
    modifier onlyOwners{
        if(msg.sender == owners[0]){
            _;
        }
        else if(msg.sender == owners[1]){
            _;
        }
        else if(msg.sender == owners[2]){
            _;
        }
    }
    
    constructor(address one, address two, address three, uint _approvalNum){
        owners.push(one);
        owners.push(two);
        owners.push(three);
        approvalNum = _approvalNum;
    }
    
    function deposit() public payable returns(uint){
        uint prevBalance = balance[address(this)];
        balance[address(this)] += msg.value;
        emit depositDone(msg.value, msg.sender);
        assert(balance[address(this)] == prevBalance + msg.value);
        return balance[address(this)];
    }
    
    function getBalance(address _address) public view returns(uint){
        return balance[_address];
    }
    
    function createTransfer(address _receiver, uint _amount) public onlyOwners{
        transfers.push(Transfer(transfers.length, msg.sender, _receiver, _amount, 1));
    }
    
    function approveTransfer(uint _id) public payable onlyOwners{
        require(msg.sender != transfers[_id].requestor, "The Requestor Of The Transfer Votes In The Affirmative Upon Creating The Transfer Request.");
        transfers[_id].approvals++;
        if(transfers[_id].approvals >= approvalNum){
            balance[address(this)] -= transfers[_id].amount;
            balance[transfers[_id].receiver] += transfers[_id].amount;
            payable(transfers[_id].receiver).transfer(transfers[_id].amount);
            emit transferDone(transfers[_id].amount, transfers[_id].receiver);
        }
    }
    
    function getTransfers(uint _id) public view onlyOwners returns (uint id, address requestor, address receiver, uint amount, uint approvals){
            return(transfers[_id].id, transfers[_id].requestor, transfers[_id].receiver, transfers[_id].amount, transfers[_id].approvals);
    }
    
}
1 Like

Project Multisig Wallet

pragma solidity 0.7.5
contract Mysig wallet {
address private _owner ;

mapping(address => uint8) private _owner ;
uint constant MIN_SIGNATURES = 2 ;
uint private _transaction ;

struct Transaction {
address from ;
address to ;
uint amount ;
uint signatureCount ;
mapping (address => uint8) signatures ;
}
mapping (uint => Transaction) private _transaction ;

modifier isOwner( ) {
require (msg.sender == _owner) ;
_ ;
}
modifier validOwner ( ) {
require (msg.sender == _owner // _owners [msg.sender] == 1) ;
_ ;
}

event DepositFunds (address from, uint amount) ;
event TransactionCompleted (address from, address to, uint amount, uint transactionId )
event TransactionSigned (address by, uint transactionId) ;

function MysigWallet ( ) public { _owner = msg.sender ;
}
function addOwner (address owner) owner public {
_owners [owner] = 1 ;
}
function ( ) public payable {
depositFunds(msg.sender, msg.value) ;
}
function withdraw(uint amount ) valid owner public {
require (address(this) . balance >= amount ;
msg . sender . transfer (amount) ;
withdrawFunds(msg . sender, amount) ;
}
function transfer To (address to, uint amount) validOwner public {
require (address(this) . balance >= amount) ;
to _ transfer (amount) ;
Transfer Funds (msg . sender, to, amount) ;
uint transactionId = _ transactionIdx++ ;
transaction memory transaction ;
transaction . from = msg.sender ;
transaction . to = to ;
transaction , amount = amount ;
transaction . signatureCount = 0 ;
}
function walletBalnce( ) constant public returns (uint) {
return address(this) . balance ;
}
}

1 Like