Project - Multisig Wallet

Hello all,

Here is my code for the Multisig Wallet Project.

pragma solidity 0.7.5; 
pragma abicoder v2; 

contract Wallet {
    
    address[] public owners;
    uint limit;
    
    modifier onlyOwners(){
        uint counter = 0;
        for (uint i = 0; i < owners.length; i++){
            if (msg.sender == owners[i]){
                counter += 1;
            }
        }
        require(counter > 0, "Only owners can perform this function");
        _;
    }
    
    constructor(address[] memory _owners, uint _limit){
        owners = _owners;
        limit = _limit;
    }
    
    mapping (address => mapping(uint => bool)) approvals;
    
    struct Transfer {
        uint amount; 
        address payable receiver; 
        uint approvals;
        bool hasBeenSent; 
        uint id;
    }
    
    event transferRequestCreated(uint _id, uint _amount, address initiator, address _receiver);
    event approvalReceived(uint _id, uint _approvals, address approver);
    event transferApproved(uint _id); 
    
    Transfer[] transferRequests;
    
    function deposit() public payable {
     
    }
    
    function createTransferRequest(uint _amount, address payable _receiver) public onlyOwners {
       Transfer memory newTransfer = Transfer(_amount, _receiver, 0, false, transferRequests.length);
       emit transferRequestCreated(transferRequests.length, _amount, msg.sender, _receiver);
       transferRequests.push(newTransfer); 
    }
    
    function approve(uint _id) public onlyOwners {
        if (transferRequests[_id].hasBeenSent == true){
            revert("You can not approve a request that has already been transfered");
        }
        if (approvals[msg.sender][_id] == true){
            revert("Owners can not approve a single transfer request more than once");
        }
        transferRequests[_id].approvals += 1;
        approvals[msg.sender][_id] = true;
        emit approvalReceived(_id, transferRequests[_id].approvals, msg.sender);
        if (transferRequests[_id].approvals == limit){
            payable(transferRequests[_id].receiver).transfer(transferRequests[_id].amount);
            transferRequests[_id].hasBeenSent = true;
            emit transferApproved(_id);
        }
    }
    
    function getTransferRequests() public view returns (Transfer[] memory) {
        return transferRequests;
    }
    
    function getContractBalance() public view returns(uint){
        return address(this).balance;
    }
}
1 Like

Hey @Kacper,. hope you are well.

I did not understand quite well your question.

The contracts always contain an owner (the account used to deploy the contract).
Then after deployment, the contract itself will have an address (address(this)).

The participants (3 accounts added as owners) are not the owner of the contract, their just participants for the multisig funds.

Hope I explained my self clear, if not, let me know :nerd_face:

Carlos Z

so we have 4 addresses:

  • address of the wallet - this
  • address of the owner
  • 2 addresses of the other approvals
    is that correct?
1 Like

Exactly, taking in consideration that also the owner is 1 of 3 participans (addresses of the other approvals).

Address of the contract :nerd_face:

Carlos Z

hey there, im getting some errors when mi setting a transaction, can you see if you can find the issue

`pragma solidity >=0.6.16 <0.9.0;
contract Ownable{
    
 address owner;
    
    mapping(address => uint) balance;
     
     modifier onlyOwner{
        require(msg.sender == owner);
        _; //run the function
    }
    /*MyStruct storage myStruct = myStructs[key];
myStruct.someProperty1 = 1337;
myStruct.someProperty2 = 6464;

or, for just one property

myStructs[key].someProperty1 = 1337;
*/
    
   // modifier notOwner{
     //   require(msg.sender != creator);
       // _; //run the function
    //}
     //    constructor(){
       //     Owner = msg.sender;
      //  }
 
 /*
 - struct for owner
 - owner struct to array
 */
    struct transaction{
        address creator;
        address receiver;
        uint amount;
        bool approvals;
        uint Approvals;
    }
    struct Owner{
        string name;
        address addressofowner;
    }
    
    Owner[] owners;
    
    transaction[] transactions;
    
      function settransaction(address creator,address _receiver, uint _amount)public payable onlyOwner{
            transaction memory newtransaction = transaction(creator, _receiver, _amount, false, 0);
            transactions.push(newtransaction);
        }
    
    function addowner(string memory name, address addressofowner) public {
        Owner memory newowner = Owner(name, addressofowner);
        addressofowner = owner;
       owners.push(newowner);
    }
        
   
            function ApproveAndSend(uint _index, uint amount, address approver, address _creator) public onlyOwner returns(address,address, uint, bool, uint)  {
                 require (balance[msg.sender] >= amount);
                transaction memory transationToReturn = transactions[_index];
                transaction storage transactions = transactions[_index];
                transationToReturn.creator = _creator;
                require (_creator != approver);
transactions.Approvals = 2;
transactions.approvals = true;
balance[transactions.receiver] += amount;
                return (transationToReturn.creator, transationToReturn.receiver, transationToReturn.amount, transationToReturn.approvals, transationToReturn.Approvals);
            }
            
            
            
            function deposit(uint amount)public payable onlyOwner returns(uint){
                require (balance[msg.sender] >= amount);
                balance[msg.sender] += amount;
                return balance[msg.sender];
                
            }
            
 
    
    
    
}`
1 Like

Hey @Bogdan_Manzar, hope you are well.

You have declared two variables with the same name, which is not permitted.

image

Carlos Z

Hi all,

I paste below my code for MultiSignWallet:
I have a few problems with it:
1.OnlyOwners Modifiers does not work
2.diffrentOwners Modifiers does not work
3. getContractBalance does not update after transfer
How could I fix this problems?

// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.7.5;
pragma abicoder v2;

contract MultiSignWallet {
    address[] public owners;
    mapping(address => uint) balance;
    
    uint Approvlimit;
    struct Transfer{
        uint id;
        uint amount;
        address payable receiver;
        uint approvalsNeed;
        bool sent;
        
    }
    
    Transfer[] transferRequests;
    mapping(address => mapping(uint => bool)) approvals;
    
    event transferRequeststDone(uint id, uint amount, address initiator, address indexed requestedTo);
    event approvalDone(uint id, uint approvalsNeed, address approver);
    event transferDone(uint id, uint amount,address lastApprover, address senTo);
    //make sure only owner - addresse in owners array will call a certain function
    modifier onlyOwners{
        bool addressallowed = false;
        uint len = owners.length;
        //check if msg.sender is an owner.
        for(uint i=0; i<len; i++){
            if (msg.sender == owners[i]) addressallowed = true;
        }
        require(addressallowed = true, "You are not an owner of this wallet");
        _;
    }
    
    modifier differentOwners{
        uint len = owners.length;
        bool ownersNotSame = true;
        for(uint i=0; i<len; i++){
            for(uint j=i+1; j<len; j++){
                if (owners[i] == owners[j]) ownersNotSame = false;
            }
        }
        require(ownersNotSame = true);
        _;
    }
    
    constructor(address[] memory _owners, uint _limit) differentOwners {
        uint len = owners.length;
        bool ownersNotSame = true;
        for(uint i=0; i<len; i++){
            for(uint j=i+1; j<len; j++){
                if (owners[i] == owners[j]) ownersNotSame = false;
            }
        }
        require(ownersNotSame = true);
        Approvlimit = _limit;
        owners = _owners;
    }
    
    function deposit() public payable {
    }
    
    function getBalance() public view returns(uint) {
        return address(this).balance;
    }
    function getOwners() public view returns(address[] memory) {
        return owners;
    }
    function createTransfer(uint _amount, address payable _receiver) public onlyOwners {
        require(address(this).balance >= _amount, "Balance not sufficient");
        transferRequests.push(Transfer(transferRequests.length, _amount, _receiver, 0, false));
    }
    
    function approve(uint _id) public onlyOwners {
        approvals[msg.sender][_id] == true;
        transferRequests[_id].approvalsNeed--;
        emit approvalDone(_id, transferRequests[_id].approvalsNeed, msg.sender);
        if(transferRequests[_id].approvalsNeed == 0){
            transferRequests[_id].receiver.transfer(transferRequests[_id].amount);
            emit transferDone(_id, transferRequests[_id].amount, msg.sender, transferRequests[_id].receiver);
            //address(this).balance -= _amount; 
        }
    }
    function getTransferRequests() public view returns(Transfer[] memory){
        return transferRequests;
    }
    
}

Thanks for help,
Kacper Pajak

1 Like

hey @Kacper. Ok so there is a few things here. I have fixed your code but there is still a lot you should do and change. I will leave that up to you but i will give you some notes or tips on the things you can improve on.

First lets look at why your code above did not work. It had nothing to do with you only owners modifier. The way you could have known this before hand is because you were able to successfully call the createTransfer function which also was dependant on this modifier. The problem with your logic lied with the first line of code in your apprive function. Namely

function approve(uint _id) public onlyOwners {
       ` approvals[msg.sender][_id] == true;`
        .........
        ........

This is not an assinment but rather an assertion or check. And if you did want to assert this you would need to either use an if statment or the assert() function. So you should change this to

 approvals[msg.sender][_id] = true;

However i do not like this approach for handling approvals it is a bit counter intuitive, especially since you set the needed approvals to 0 in your transfer struct and then deincrement it each time in your original code? For an outside reader Its a bit hard to understand. So i changed this attribute in your transfer struct to be equal to the approval limit which you define in the constructor.
I increment its value each time the approve function is called and then i increment the approvals attribute for your tansfer

 approvals[msg.sender][_id] += 1;
transferRequests[_id].approvalsNeed++;   //changed from transferRequests[_id].approvalsNeed--;

The last change i amde was in your if statement. I check if the transfer approval count is equal to the limit if it is we execute the transfer

if(transferRequests[_id].approvalsNeed == approvalCount){
            transferRequests[_id].receiver.transfer(transferRequests[_id].amount);
            emit transferDone(_id, transferRequests[_id].amount, msg.sender, transferRequests[_id].receiver);
            //address(this).balance -= _amount; 
        }

Your code now works. But there is many things you should fix. Firstly in your createTransfer Function you should require that

require(balance[msg.sender] >= _amount, "Balance not sufficient");

and not

require(address(this).balance >= _amount, "Balance not sufficient");

This is because the contract balance stores the total funds deposited into the smart contract from every and any user who uses it. Where as our balance mapping stores individual user balances.

Another thing is in your constructor. You should not pass in the limit but rather dynamically calculate it based on the length of your user array. The way you currently have it is that you pass it in. However theere is nothing stopping you from passing in a limit that is greater than the length of your owners array which in combination with your only owner modifier restriction would break your code. In your constructor smething like this would suffice

Approvlimit = owners.length -1;

There are a few other things you could change to really make this a kickass contract. I suggest reading up a good bitin the forums and see what other people are doing the forums are really great resources to learn.

Other than that really well done man this is a great attempt and it really looks like your own solution and not the one from the vide so hats off because i rememberd struggling when doing this assignemnt. Anyways congrats for finishing the course and see u in the next one.

Happy coding,
Evan

also here is the snippet of code i edited iin remix to get it working. I only made as litle changes to get it working rather than grealty improving it

// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.4;
pragma abicoder v2;
//["0x5B38Da6a701c568545dCfcB03FcB875f56beddC4", "0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2", "0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c"], 2
contract MultiSignWallet {
    address[] public owners;
    mapping(address => uint) balance;
    uint public id = 0;
    uint Approvlimit;
    struct Transfer{
        uint id;
        uint amount;
        address payable receiver;
        uint approvals;
        bool sent;
        
    }
    
    Transfer[] transferRequests;
    mapping(address => mapping(uint => uint)) approvals;
    
    event transferRequeststDone(uint id, uint amount, address initiator, address indexed requestedTo);
    event approvalDone(uint id, uint approvalsNeed, address approver);
    event transferDone(uint id, uint amount,address lastApprover, address senTo);
    //make sure only owner - addresse in owners array will call a certain function
    modifier onlyOwners{
        bool addressallowed = false;
        uint len = owners.length;
        //check if msg.sender is an owner.
        for(uint i=0; i<len; i++){
            if (owners[i] == msg.sender) {
                addressallowed = true;
            }
        }
        require(addressallowed = true, "You are not an owner of this wallet");
        _;
    }
    
    modifier differentOwners{
        uint len = owners.length;
        bool ownersNotSame = true;
        for(uint i=0; i<len; i++){
            for(uint j=i+1; j<len; j++){
                if (owners[i] == owners[j]) ownersNotSame = false;
            }
        }
        require(ownersNotSame = true);
        _;
    }
    
    constructor(address[] memory _owners,) differentOwners {
        uint len = owners.length;
        bool ownersNotSame = true;
        for(uint i=0; i<len; i++){
            for(uint j=i+1; j<len; j++){
                if (owners[i] == owners[j]) ownersNotSame = false;
            }
        }
        require(ownersNotSame = true);
        Approvlimit = owners.length -1;
        owners = _owners;
    }
    
    function deposit() public payable {
        balance[msg.sender] += msg.value;
    }
    
    function getLimit() public view returns (uint) {
        return Approvlimit;
    }
    
    function getBalance() public view returns(uint) {
        return balance[msg.sender];
    }
    
    function getContractBalance() public view returns(uint) {
        return address(this).balance;
    }
    function getOwners() public view returns(address[] memory) {
        return owners;
    }
    function createTransfer(uint _amount, address payable _receiver) public onlyOwners {
        require(address(this).balance >= _amount, "Balance not sufficient");
        transferRequests.push(Transfer(id, _amount, _receiver, 0, false));
        id++;
    }
    
    function approve(uint _id) public onlyOwners {
        approvals[msg.sender][_id] += 1;
        transferRequests[_id].approvals++;
        emit approvalDone(_id, transferRequests[_id].approvals, msg.sender);
        if(transferRequests[_id].approvals == 2){
            transferRequests[_id].receiver.transfer(transferRequests[_id].amount);
            emit transferDone(_id, transferRequests[_id].amount, msg.sender, transferRequests[_id].receiver);
            //address(this).balance -= _amount; 
        }
    }
    function getTransferRequests() public view returns(Transfer[] memory){
        return transferRequests;
    }
    
}
1 Like

Watched the Project introduction video, and had a crack at it myself
Didn’t need to watch the extra help videos for succesfully completing the assignment. Yay, proud of myself :wink:

Decided to split the contract in 2 part, the ownable.sol is responsible for adding owners, changing required approval amounts and the onlyOwners modifier.
transfer.sol is responsible for all the transferlogic, adding a transferRequest, Approving a Transfer request, depositing.

Required features:
Only owners should be able to create a transferReq
Only owners should be able to approve a transferReq
A unique owner address should only be able to approve a transferReq once
Upon recieving reqApproval, approves transfer and sends it
everyone is able to deposit
Only owners can add new owners
Only owners can change requiredApproval amount

Try’d it out and it works as intended.
Hope it’s right, looking forward to a reply :smiley:

Ownable.sol

pragma solidity 0.7.5;

contract Ownable {
    uint amountApproval;
    
    address[] addressList;
    mapping(address => bool) owners;
    
    constructor(uint _approval){
        owners[msg.sender] = true;
        addressList.push(msg.sender);
        amountApproval = _approval;
    }
    
    modifier onlyOwners {
        require(owners[msg.sender], "This is for owners only!");
        _;
    }
    
    function displayOwners() public view returns(address[] memory){
        return addressList;
    }
    
    function displayReqApproval() public view returns(uint){
        return amountApproval;
    }
    
    function changeReqApproval(uint _approval) onlyOwners public{
        require(_approval != 0, "Need atleast one approval.");
        
        amountApproval = _approval;
    }
    
    function addOwner(address _ownerToAdd) onlyOwners public {
        require(!owners[_ownerToAdd], "Owner already in the list!");
        owners[_ownerToAdd] = true;
        addressList.push(_ownerToAdd);
    }
}

transfer.sol

pragma solidity 0.7.5;
pragma abicoder v2;

import "./ownable.sol";

contract transferLogic is Ownable {
    constructor(uint _approval) Ownable(_approval){}
    
    uint totalBalance;
    mapping(address => mapping(uint => bool)) approved;
    
    struct transferReq{
        address transferTo;
        uint amount;
        uint approvals;
        bool transferSent;
    }
    
    transferReq[] pendingTransfer;
    
    event succesfullTransfer(uint id, uint amount, uint approvalAmount, uint reqApproval);
    event approvedBy(address approvee);
    event approvalNeeded(uint id, uint approvalAmount, uint reqApproval);
    event transferCompleted(uint amount, address transferTo, uint prev, uint total);
    
    //anyone is able to deposit
    function deposit() public payable{
        totalBalance += msg.value;
    }
    
    //Get balance of total account
    function getTotalBalance() view public returns(uint){
        return totalBalance;
    }
    
     //check for pending transfers
    function getPending() view public returns(transferReq[] memory){
        return pendingTransfer;
    } 
    
    //addTransfers that need to be approved
    function addTransfer(address _transferTo, uint _amount) onlyOwners public{
        require(totalBalance >= _amount, "Total account balance needs to be higher or equal to transfer amount");
        pendingTransfer.push(transferReq(_transferTo, _amount, 0, false));
    }
    
    //Check currentApprovalRate
    function approveTransfer(uint _id) onlyOwners public{
        //Owner can not approve more then once (would be waste of gas)
        //Owner can not approve a transaction that has already been sent
        require(!approved[msg.sender][_id], "Can not approve multiple times with same wallet");
        require(!pendingTransfer[_id].transferSent, "Can not vote for an already sent transaction");
        
        //updating mapping of the approval
        approved[msg.sender][_id] = true;
        //updating pendingTransfer array approvals
        pendingTransfer[_id].approvals++;
        
        emit approvedBy(msg.sender);
        
        //uppon meeting minimum required approvals transfer the request
        if(pendingTransfer[_id].approvals >= amountApproval){
            //send transfer
            _transfer(pendingTransfer[_id].amount, pendingTransfer[_id].transferTo);
            //update pendingTransfer.transferSent to true so that an already sent req cannot be sent again
            pendingTransfer[_id].transferSent = true;
            
            emit succesfullTransfer(_id, pendingTransfer[_id].amount, pendingTransfer[_id].approvals, amountApproval);
            
        } else {
            emit approvalNeeded(_id, pendingTransfer[_id].approvals, amountApproval);
        }
        
    }
    
    //transfer function
    function _transfer(uint amount, address reciever) internal{
        uint prev = totalBalance;
        payable(reciever).transfer(amount);
        totalBalance -= amount;   
        
        emit transferCompleted(amount, reciever, prev, totalBalance);
        assert(totalBalance == prev - amount);
    }
    
    //withdrawal as a emergency button
    function _withdraw(uint amount) public returns(uint){
        require(totalBalance >= amount, "Total account balance needs to be higher or equal to transfer amount");
        uint prev = totalBalance;

        msg.sender.transfer(amount);
        totalBalance -= amount;   
        
        assert(totalBalance == prev - amount);
        
        return totalBalance;
    }
    
}
1 Like

MultiSig Full project with User Interface DEMO

Links to the video demo
https://clipchamp.com/watch/G8V46kdEjXB
Hey everyone so i have been working on a DApp for the last 3 weeks for my mutlisig contract that i developed back when taking this course. Below are some screenshots of the UI and also a link to a video demo of the DApp itself. I have wrote quite a robust multisig contract which you are free to take a look at in the github link below.

Full project video walkthrough coming soon
I want to record a full walkthrough from start to finish to post here in the forum to show anyone who is intrested how to code this exact project. There is a lot of code and a lot of logic especially in the front end so it will take me another week to make. But my plan is that hopefully everyone can learn some cool new skills that they can then put into practice when making DApps of your own. Below is a list of the things that will be covered in the project walkthrough

Topics Covered

  1. Writing a more robust multisig contract

  2. Setting up truffle and ganache to start developing locally

  3. Testing smart contracts

  4. Using the SCSS scripting language to code maintainable HTML webpages (sometimes regular CSS can be a nightmare to maintain as the project grows)

  5. Using Web3.js and Metamask to connect front end to the blockchain

  6. Intricate DOM manipulation for dynamic HTML tables in vanilla js

  7. Using web3.js events library to query the blockchain to extract information about transactions such as gas used, block number, block hash etc (all useful wallet information)

  8. Linking DApp Transactions to live Etherscan links

  9. Using openzeppelin library to include the ability for ERC20 token transfers aswell as ether

  10. Using a factory contract and web3.js contract.deploy() to create new wallet instances to handle user login etc

There is a lot to cover as the project is quite large but im confident i can make everything super easy to understand. I am currently just finishing the ERC20 token transfer integration and also finishing the handling of deploying contract instances. Once this is done i will just refactor the code to make it more neat and easier to follow for doing the tutorial. Im excited to get this done and out here on the forum.

Screenshots of the UI

mutlisig1.PNG

mutlisig2.PNG

multisig3

Links to the video demo
https://clipchamp.com/watch/G8V46kdEjXB

Link to latest github code (not latest version)
https://github.com/mcgraneder/Etherem-MultiSigWallet-Dapp

3 Likes

Thank you very much for these amendments and tips.
I certainly will search forum for implementation ideas.
I change the limit assignment to:

        if(_owners.length = 1) Approvlimit = 1;
        else  Approvlimit = _owners.length;

Because if we would have just one one the limit would be 0.

I have a question about your correction, quoted below
Why we should check the msg.sender balance not the contract balance?
If it is common wallet of 3 persons and it requires 2 approvals out of 3 owner, I think we should spend the common money - the money in the contract.
If i create a transaction request and I pay out of my funds, then, why should I need other guy approval for it?

1 Like

brilliant @Kacper no worries at all. Actually on your point about the contract balance. Thast a very good point. I never thought of it like that but that actually makes sense to me. Its funny because as i read the forums when ppl have eissues and give replies some people have there own preferances about various functionality of the wallet and for some things it does come down to personal opinion. But this does make a lot of sense i am actually going to make this change to my own contract code.

1 Like

You are just nailing it man! OMG!! :muscle:

True Congrats man!!.

Carlos Z

1 Like

Hey @thecil Thank you for your kind words. Yeah i took a small break from the courses here as i wanted to build some of my own things and i have been doing a lot of learning the past month wanted to play around with other technologies like polkadots BC language substrate and cardanos plutus language. I am excited to get back to some of the other courses now on the academy such as SC security and the DApp one which i plan on starting both by the end of this week

Evan

1 Like

Amazing multisig wallet dapp demo!
Thanks for sharing!

2 Likes

hey @jimmy_humania thank u very much

pragma solidity 0.7.5;
pragma abicoder v2;

contract MutlisigWallet {

event Confirmation(address indexed sender, uint indexed transactionId);


constructor() {
    address[] owners;
    uint required; 
    
}

mapping (uint => mapping (address => bool)) public confirmations;
mapping (address => uint) balance;


modifier notConfirmed(uint transactionId, address owner) {
    require(confirmations[transactionId][owner]);
    _;
}


address public owner;



struct Transfer {
    address to;
    uint amount;
    bool executed;
    uint approvals;
    
}



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




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


function approve(uint transactionId) public notConfirmed(transactionId, msg.sender) {
    confirmations[transactionId][msg.sender] = true;
    Confirmation(msg.sender, transactionId);
    Transfer.approvals++;
}

function transfer(address recipient, uint amount) public {
    require(balance[msg.sender] >= amount, "Balance not sufficient");
    require(msg.sender != recipient, "Don't transfer money to yourself");
    require(Transfer.approvals >= 2);
    
    uint previousSenderBalance = balance[msg.sender];
    
    _transfer(msg.sender, recipient, amount);
    
    assert(balance[msg.sender] == previousSenderBalance - amount);
}

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

}

2 Likes

I have same questions on @filip’s solution.

function deposit() public payable {}

How does this work without a function body? Is it just a function name that Solidity recognizes and automatically executes what is needed or?

And the next piece of code you check if the mapping of the transaction id is set to false. are booleans initialized as false by default?

require(approvals[msg.sender][_id] == false);

I don’t understand how and when data is added to the approvals mapping.

Here’s a copy of the full code:

pragma solidity 0.7.5;
pragma abicoder v2;

contract Wallet {
address[] public owners;
uint limit;

struct Transfer{
    uint amount;
    address payable receiver;
    uint approvals;
    bool hasBeenSent;
    uint id;
}

event TransferRequestCreated(uint _id, uint _amount, address _initiator, address _receiver);
event ApprovalReceived(uint _id, uint _approvals, address _approver);
event TransferApproved(uint _id);

Transfer[] transferRequests;

mapping(address => mapping(uint => bool)) approvals;

//Should only allow people in the owners list to continue the execution.
modifier onlyOwners(){
    bool owner = false;
    for(uint i=0; i<owners.length;i++){
        if(owners[i] == msg.sender){
            owner = true;
        }
    }
    require(owner == true);
    _;
}
//Should initialize the owners list and the limit 
constructor(address[] memory _owners, uint _limit) {
    owners = _owners;
    limit = _limit;
}

//Empty function
function deposit() public payable {}

//Create an instance of the Transfer struct and add it to the transferRequests array
function createTransfer(uint _amount, address payable _receiver) public onlyOwners {
    emit TransferRequestCreated(transferRequests.length, _amount, msg.sender, _receiver);
    transferRequests.push(
        Transfer(_amount, _receiver, 0, false, transferRequests.length)
    );
    
}

//Set your approval for one of the transfer requests.
//Need to update the Transfer object.
//Need to update the mapping to record the approval for the msg.sender.
//When the amount of approvals for a transfer has reached the limit, this function should send the transfer to the recipient.
//An owner should not be able to vote twice.
//An owner should not be able to vote on a tranfer request that has already been sent.
function approve(uint _id) public onlyOwners {
    require(approvals[msg.sender][_id] == false);
    require(transferRequests[_id].hasBeenSent == false);
    
    approvals[msg.sender][_id] = true;
    transferRequests[_id].approvals++;
    
    emit ApprovalReceived(_id, transferRequests[_id].approvals, msg.sender);
    
    if(transferRequests[_id].approvals >= limit){
        transferRequests[_id].hasBeenSent = true;
        transferRequests[_id].receiver.transfer(transferRequests[_id].amount);
        emit TransferApproved(_id);
    }
}

//Should return all transfer requests
function getTransferRequests() public view returns (Transfer[] memory){
    return transferRequests;
}

}

1 Like

Here is my attempt… :smile:

// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.7.5;
pragma abicoder v2;

// Remix constructor 
// ["0x5B38Da6a701c568545dCfcB03FcB875f56beddC4","0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2","0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c"],2

// Test Target Wallet / Amount
// 0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db,5000000000000000000

contract MultisigWallet {
    
    address[] private owners;
    uint private approvalsRequired;
    
    mapping(address => uint) private deposits;
    mapping(uint => Transaction) private txQueue;
    
    // Initalise transaction id counter 
    uint private txId = 1000;
    
    struct Transaction {
        uint txId;
        address from;
        address to;
        uint amount;
        address[] approvals;
        bool isProcessed;
    }
    
    modifier onlyOwner {
        require(in_arrray(msg.sender, owners), "Operation can only be called by an owner!");
        _;
    }
    
    constructor(address[] memory _owners, uint _approvalsRequired) {
        owners = _owners;
        approvalsRequired = _approvalsRequired;
    }
    
    // Deposit value to the contract    
    function deposit() public payable returns (uint) {
        deposits[msg.sender] += msg.value;
        return deposits[msg.sender];
    }
    
    // Initiate transfer request for approval
    function transfer(address _to, uint _amount) public onlyOwner returns (uint) {
        
        // Check contract has a suffcient balance for the transaction
        require(getBalance() >= _amount, "Insufficient contract balance to create transaction!");
        
        txId++;
        
        // Note: approvals array needs to be initialised in memory and then set in storage
        txQueue[txId] = Transaction(txId, msg.sender, _to, _amount, new address[](0), false);
        txQueue[txId].approvals.push(msg.sender);
        
        return txId;
    }
    
    // Confirm approval and send transaction
    function approve(uint _txId) public onlyOwner payable returns (bool) {
        
        Transaction storage transaction = txQueue[_txId];
    
        // Check transaction has not been processed
        require(!transaction.isProcessed, "Transaction already approved and processed!");
        
        // Check approver has not already previously approved this transaction
        require(!in_arrray(msg.sender, transaction.approvals), "Transaction already approved by this account!");
        
        // Check contract has a suffcient balance for the transaction
        require(getBalance() >= transaction.amount, "Insufficient balance for transaction!");
        
        // Add approver address to approvals array
        transaction.approvals.push(msg.sender);
        
        if (transaction.approvals.length >= approvalsRequired) {
            
            // Process the transaction
            (bool success,) = transaction.to.call{value: transaction.amount}("");
            require(success, "Transfer failed.");
            
            transaction.isProcessed = true;
            return true;
        }
        
        return false;
    }
    
    // Return the Transaction
    function getTransferRequest(uint _txId) public view returns (Transaction memory) {
        return txQueue[_txId];
    }
    
    // Return the balance of the contract
    function getBalance() public view returns (uint) {
        return address(this).balance;
    }
    
    // Checks for the existence of an address in an array of addresses
    function in_arrray(address needle, address[] memory haystack) internal pure returns (bool) {
        for (uint i; i < haystack.length; i++) {
            if (needle == haystack[i])
                return true;
        }
        
        return false;
    }
}
1 Like

Here is my code:

pragma solidity ^0.7.5;
pragma abicoder v2;

contract MultiSigWallet{
    
    // approvers and # of reuiered approvals
    address[] public owners;
    uint approvalsNeeded;
    
    
    //mapping owners to transactions to status
    mapping(address => mapping(uint => bool)) approvals;
    
    // modfier to validate if the tx request is made by an owner of the contract
    modifier onlyOwners(){
        bool owner = false; // set initial state
        
        for(uint i=0;i<owners.length; i++) { 
            
            // search if msg.sender is owner
            if(owners[i]==msg.sender)
                {
                    owner=true;
                    
                }
        }
        require(owner == true, "msg.sender not a owner.");
        _;
    }
    
    struct Transfer{
        uint amount;
        address payable to;
        uint approvals;
        bool sent;
        uint id;
    }

    Transfer[] transfersRequest;
    event newDeposit(address from, uint amount);
    
    event newTransferRequest(uint _txId,address payable _to, uint amount, address _requester);

    event newTransferSent(uint _txId);    
    
    constructor(address[] memory _owners, uint _approvalsNeeded){
        owners = _owners;
        approvalsNeeded = _approvalsNeeded;
    }
    
    //Deposit fuction, receives from anyone emit event newDeposit
    
    function deposit() public payable{
        
        emit newDeposit(msg.sender, msg.value);
        
    }
    
    
    // Transfer object creation 
    function createTransfer(uint _amount, address payable _to) public onlyOwners {
        uint txId = transfersRequest.length;
        
        emit newTransferRequest(txId,_to, _amount,msg.sender);
        
        transfersRequest.push(Transfer(_amount,_to,0,false,txId));
    }
    
    //get all tx
    
    function getAllTx() public view returns(Transfer[] memory){
        
       return transfersRequest;
    }
    
    //perform all unsent approved transfers
    function performApprovedTx() payable public onlyOwners {
        
        for(uint i=0;i<=transfersRequest.length;i++){
            if(transfersRequest[i].approvals >= approvalsNeeded){
                if(transfersRequest[i].sent == false){
                    sendTx(i);
                }
            }
        
        }
    }
    
    function sendTx(uint _id)  internal  {
         transfersRequest[_id].sent == true;
         transfersRequest[_id].to.transfer(transfersRequest[_id].amount);
         emit newTransferSent(transfersRequest[_id].id);
    }
    
    
    function approve(uint _id) payable public onlyOwners{
        //validate if msg.send alredy voted
        require(approvals[msg.sender][_id] == false, "Owner already approved.");
        //validate if tx is already sent
        require(transfersRequest[_id].sent == false,"Transaction already sent.");
        
        approvals[msg.sender][_id] = true;
        transfersRequest[_id].approvals++;
        
        if(transfersRequest[_id].approvals>= approvalsNeeded){
            sendTx(_id);
        }
        
        
    } 
    
}

But I have the following question, I wanted to create a dynamic array inside a function, but I wasn’t able to, this is what I wanted to achieve:

     function getAllTxUnSent() public view returns(Transfer[] memory){
        
       Transfer[] memory unsent;
       for(uint i=0;i<=transfersRequest.length;i++){
           if(transfersRequest[i].approvals >= approvalsNeeded){
                if(transfersRequest[i].sent == false){
                    unsent.push(transfersRequest[i]);
                }
            }
        
        }
       
    }

MultiSigWallet.sol:95:21: TypeError: Member “push” is not available in struct MultiSigWallet.Transfer memory[] memory outside of storage.
unsent.push(transfersRequest[i]);

So, basically, I cannot do a push to a dynamic array stored in memory. So, the question is, how can I return a dynamic array in a function? is this a Solidity limitation?

1 Like