Project - Multisig Wallet

Hey @DearPhumi, hope you are ok.

I have tested your contract, is almost great, you have one minor mistype error that is costing the entire contract to fail when trying to createTransfer, the problem is on the modifier:

modifier onlyOwners(){
bool owner = false;
for (uint i = 0 ; i < owners.length; i ++){
    if (owners[i]==msg.sender){
        owner==true; //you are not assigning a value, just comparing it. It should be '='
    }
  }
  require (owner == true) ;
  _;
}

overall you did a great job! Congratulations!

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

Carlos Z.

1 Like

Hi, why am I getting this error code when trying to ā€œcreateTransferā€ ?

transact to Wallet.createTransfer errored: VM error: invalid opcode. invalid opcode The execution might have thrown. Debug the transaction to get more information.

here is my code:

pragma solidity 0.7.5;
pragma abicoder v2;

//copy and paste these addresses for "owners" upon deployment & make limit = 2
//["0x5B38Da6a701c568545dCfcB03FcB875f56beddC4", "0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2", "0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db"]

contract Wallet {
    address[] public owners;
    uint limit;
    
    struct Transfer{
        uint amount;
        address payable receiver;
        uint approvals;
        bool hasBeenSent;
        uint id;
    }
    
    Transfer[] transferRequests;
    
    mapping(address => uint) balance;
    
    event depositDone(uint amount, address indexed depositedTo);
    
    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 (can be empty because you don't need to execute anything, just need a "payable" function)
    function deposit() public payable returns (uint){
        balance[msg.sender] += msg.value;
        //call EVENT to be logged
        emit depositDone(msg.value, msg.sender);
        return balance[msg.sender];
    }
    
    //Create an instance of the Transfer struct and add it to the transferRequests array
    function createTransfer(uint _amount, address payable _receiver) public onlyOwners {
        //Transfer struct takes arguments that coincide with all variables inside actual Transfer struct shown above^
        //then, you transferRequest.push all of this so it gets added to the pending "transfer requests" array
        transferRequests.push(
            Transfer(_amount, _receiver, 0, false, transferRequests.length)
            );
        
    }
    
    //Set your approval for one of the transfer requests.
    //Need to update the Transfer object. (set approvals to a new number, set hasbeenSet if it was sent)
    //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); //basically saying that in order for this to GET approved, the msg.sender hasn't approved it yet
        require(transferRequests[_id].hasBeenSent == false);
        
        approvals[msg.sender][_id] == true;
        transferRequests[_id].approvals += 1;
        
        if (transferRequests[_id].approvals >= limit){
            transferRequests[_id].hasBeenSent == true;
            transferRequests[_id].receiver.transfer(transferRequests[_id].amount); //.transfer is a built in function that sends spec amount
        }
        
    }
    
    //Should return all transfer requests
    function getTransferRequests() public view returns (Transfer[] memory){
        return transferRequests;
    }
    
    //get balance of msg.sender
    function getBalance() public view returns (uint){
        return balance[msg.sender];
    }
    
    
}
1 Like

You are comparing, i <= owners.length so it will iterate one more over the array. try with i < owners.length

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

Carlos Z.

Yo!! This project was HARD at the BEGINNING, but as I continued to learn more and write this project’s code, again and again, I see the errors I made from the start. The process is hard but never give up! accept the fact that you don’t know something and find a way to learn it!

Finally, it works! Finishing this Project is VERY REWARDING! I Still have a long way to go. Have fun coding everyone!!

Here’s my code, please check if there are errors or minor improvements that I can make (like a cleaner code). I added some extra features just for fun and learning :sweat_smile:

pragma solidity 0.8.3;

// ["0x5B38Da6a701c568545dCfcB03FcB875f56beddC4", "0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2", "0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db", "0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB"]
// try to add new owner 0x617F2E2fD72FD9D5503197092aC168c91465E7f2

// This MultiSig Wallet can start with 2 owners and can add after deployed up to 10 max owners


contract MultiSigWallet{
    
    event Deposit(address indexed _from, uint amount, uint balance);
    event proposedTransactions (address _from, address _to, uint amount, uint _txId);
    event signedTransactions (uint _txId, uint _signatures, address _signedBy);
    event confirmedTransactions (uint _txId);
    
    address[] public owners;
    uint public sigRequired;


    struct Transaction {
        address payable to;
        uint amount;
        uint txId;
        bool confirmed;
        uint sigNumber;
    }
    mapping(address => bool) isOwner;
    mapping(address => uint) public balance;
    mapping(uint => mapping(address => bool)) isSigned;

    Transaction[] transactions;

    modifier onlyOwners() {
        require(isOwner[msg.sender], "not owner");
        _;
    }

    constructor(address[] memory _owners, uint _sigRequired) {
        require(_sigRequired <= _owners.length && _sigRequired > 0 && _sigRequired > (_owners.length / 2), "Number of required signatures must be more than half of Owners"); // In case of multiple owners, require signatures must be > 50% of owners
        for (uint i = 0; i < _owners.length; i++) {
            address owner = _owners[i];
//this will loop around every owner in the owners array ...
            require(owner != address(0), "invalid owner");
            require(!isOwner[owner], "owner not unique");
// ... and require that the owner is not in address 0, and no owner is duplicated
            isOwner[owner] = true;
            owners.push(owner);
        }
        sigRequired = _sigRequired;
    }

    function deposit() public payable {
        balance[msg.sender] += msg.value;
        emit Deposit(msg.sender, msg.value, address(this).balance);
    }
    
    function getWalletBalance() public view returns(uint) {
        return balance[msg.sender];
    }

    function proposeTransaction(uint _amount, address payable _to) public onlyOwners {
        emit proposedTransactions(msg.sender, _to, _amount, transactions.length);
//bool set to false means: not yet confirmed
//sigNumber set to 0 means: no one has signed yet
        transactions.push(Transaction(_to, _amount, transactions.length, false, 0));
    }
  
    function signTransaction(uint _txId) public onlyOwners {
        require(isSigned[_txId][msg.sender] == false);
// set to false because: msg.sender has not yet signed (if this is true, msg.sender can vote twice)   
        Transaction storage transaction = transactions[_txId];
        transaction.sigNumber ++;
        isSigned[_txId][msg.sender] = true;
        
        emit signedTransactions(_txId, transactions[_txId].sigNumber, msg.sender);
    }
    
    function executeTransaction(uint _txId) public onlyOwners{
        require(transactions[_txId].confirmed == false);
//set to false because: transaction cannot be confirmed yet without the owners' signatures      
        if(transactions[_txId].sigNumber >= sigRequired){
            transactions[_txId].confirmed = true;
            transactions[_txId].to.transfer(transactions[_txId].amount);
            emit confirmedTransactions(_txId);
        }
    }
    
    function getTransaction() public view returns(Transaction[] memory){
        return transactions;
    }
    
    function addOwner(address _newOwner) public {
        require(_newOwner != address(0), "invalid owner");
        require(!isOwner[_newOwner], "owner not unique");
        require(owners.length < 10, "Owner slot full"); // count starts with 0

        isOwner[_newOwner] = true;
        owners.push(_newOwner);
        sigRequired++; // adds 1 to the number of signature required per new owner
/*downside: if we start with 3 owners and 2 sigRequired (needs 67% approval)
            if we add 7 new owners, total owners is 10 and sigRequired would be 9 (90% approval)
            if users reach 100 it would be a (99% approval)
            LIMIT OWNERS to 10
*/
    }
}
1 Like

Dont feel bad man i think they know its going to be to much for us but they want to male sure we are at least willing to try to figure it out because there is no way we would jave thought to use a for loop to iterate through an array because we never did that in this course or really in the java course either. I learned alot trying to figure the problem out and i think thats the whole purpose. Hang in there buddy, im doing the same.

I tried to do it without double mapping (I will consult my solution later). However I wanted to watch the part with double mapping and there is something unclear to me. Maybe I got the whole mapping wrong. Here is the part of code from Filip:

    struct Transfer{
        uint amount;
        address payable receiver;
        uint approvals;
        bool hasBeenSent;
        uint id;
    }
    
    Transfer[] transferRequests;
    
    mapping(address => mapping(uint => bool)) approvals;

    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++;
    }

My question:
How can we say that we require(approvals[msg.sender][_id] == false) without defining that there is actually sender with _id which is false? Does it mean, that everytime you create a mapping, then there is automatically default false value for whatever input?

Can you explain me? Thanks.

1 Like

Here is my first attempt. It uses an interface, which probably wasn’t the easiest way to do this.

pragma solidity 0.8.1;

//This contract creates a multi-signature wallet that allows the creater to specify the owners
//and specify the number of approvals needed for a transaction to execute.

//interface that logs all transactions
interface MultisigWalletInterface
{
    function addTransaction(address _sender, address _receiver, uint _amount, uint _approvalsNeeded, uint _numApprovals) external;
    function approvals(uint _index) external returns(bool);
    function getReceiverAddress(uint _index) external view returns(address);
    function getAmount(uint _index) external view returns(uint);
}

contract MultisigWallet
{
    uint approvalsNeeded;
    address owner;
    address[] friends;
    
    mapping(address => uint) balance;   //maps addresses to an account balance
    mapping(address => bool) approve;   //maps address to an approval(boolean) or rejection
    
    //modifier function checks for only owners to execute a function
    modifier onlyOwners
    {
        bool isOwner = false;
        for(uint i=0; i<friends.length; i++)
        {
            if(msg.sender == friends[i])
            {
                isOwner = true;
                break;
            }
        }
        require(isOwner == true, "You may not perform the transaction");
        _;  //means the modifier is done, next execute the function that called the modifier, if the require statement is true
    }
    
    //create an instance of the interface
    MultisigWalletInterface multisigWalletInstance = MultisigWalletInterface(0xA7Df470a490197Af8fdD72bDB40a68709266027b);
    
    constructor(uint _approvalsNeeded, address[] memory _friends)
    {
        owner = msg.sender;
        friends = _friends;
        friends.push(msg.sender);
        approvalsNeeded = _approvalsNeeded;
    }
    
    //deposit funds into the user account
    function DepositToUser(uint _amount) public
    {
        balance[msg.sender] = balance[msg.sender] + _amount;
    }
    
    //deposit funds into the contract
    function DepositToContract() public payable
    {
        balance[msg.sender] = balance[msg.sender] - msg.value;
    }
    
    //returns the contract balance
    function getContractBalance() public view returns(uint)
    {
        return address(this).balance;
    }
    
    //returns the user balance
    function getUserBalance() public view returns(uint)
    {
        return balance[msg.sender];
    }
    
    //enters a transfer request into the MultisigWalletLog
    function TransactionRequest(address _receiver, uint _amount) public onlyOwners
    {
        multisigWalletInstance.addTransaction(msg.sender, _receiver, _amount, approvalsNeeded, 1);      //interface
    }
    
    //Allow only owners to enter a transaction log index and approve or reject the transaction
    function ApproveRejectTransaction(uint _index, bool _approve) public onlyOwners
    {
        require(msg.sender != owner, "The owner cannot give an approval since he created the transaction request"); //prevents the owner from giving approvals
        require(approve[msg.sender] == false && _approve == true, "The user can only give one approval");           //prevents the owners from giving multiple approvals
        approve[msg.sender] = _approve;
        bool result = multisigWalletInstance.approvals(_index);     //interface call
        if (result == true)
            TransferFunds(_index);
    }
    
    //executes only when a transaction receives enough approvals
    function TransferFunds(uint _index) internal
    {
        address recAddress = multisigWalletInstance.getReceiverAddress(_index);     //interface call
        uint amount = multisigWalletInstance.getAmount(_index);                     //interface call
        payable(recAddress).transfer(amount);
    }
    
}
****Here is the contract used as an interface

pragma solidity 0.8.1;

contract MutisigWalletLog
{
    
    struct Transaction
    {
        uint transactionID;
        address sender;
        address receiver;
        uint amount;
        uint approvalsNeeded;
        uint numApprovals;
    }
    
    Transaction[] transactionLog;

    function addTransaction(address _sender, address _receiver, uint _amount, uint _approvalsNeeded, uint _numApprovals) external
    {
        Transaction memory temp = Transaction(transactionLog.length, _sender, _receiver, _amount, _approvalsNeeded, _numApprovals );
        transactionLog.push(temp);
    }
    
    function getTransaction(uint _index) public view returns(uint, address, address, uint, uint, uint)
    {
        return(transactionLog[_index].transactionID, transactionLog[_index].sender, transactionLog[_index].receiver, 
        transactionLog[_index].amount, transactionLog[_index].approvalsNeeded, transactionLog[_index].numApprovals);
    }
    
    function getReceiverAddress(uint _index) external view returns(address)
    {
        return transactionLog[_index].receiver;
    }
    
    function getAmount(uint _index) external view returns(uint)
    {
        return transactionLog[_index].amount;
    }
    
    function approvals(uint _index) external returns(bool)
    {
        transactionLog[_index].numApprovals = transactionLog[_index].numApprovals + 1;      //increment the approvals
                
        if(transactionLog[_index].numApprovals == transactionLog[_index].approvalsNeeded)   //checks to see if there are enough approvals to -
            return true;
        else
            return false;
    }
    
}

Hey @CryptoXyz, hope you are well.

I have tested your contract, it works very well.

Steps I made:

  • deploy the contract. Array of 3 owners, 2 _sigRequired
  • deposit() 5 ethers from 1st owner.
  • getWalletBalance() shows the correct amount of 5 ethers (in wei)
  • proposeTransaction() 4 ether from 1st owner to 3rd owner.
  • getTransaction() returns the values of the pending transfer (index 0).
  • signTransaction() with 1st owner and 2nd owner.
  • executeTransaction() with 2nd owner, funds are being send to 3rd owner properly, it works!
  • Check Balance on 3rd owner address and it got the ethers properly.

Overall you did a very great job, nice solution! :partying_face:

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

Carlos Z.

1 Like

Its pretty simple, all boolean variables start at false by default, its the same to specify it or just declared.

bool example; // value = false, default.
bool example2 = false; // its basically the same.

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 so much, man! This means so much, especially coming from you with your knowledge in this stuff. Much appreciated, man! :star_struck:

More success to you and the members of this academy!

Hey @rudyvasq, hope you are ok.

Im testing your contract, there are minor errors that stop me to complete the test:

You suppose to deposit funds with a user, DepositToUser does not manage funds, so i can add whatever amount i like, then DepositToContract is not being process because you are not sending funds to the balance on the first function, is just a integer number.

I advice you to do something like this:

    //deposit funds into the contract
    function DepositToContract() public payable
    {
        balance[msg.sender] = balance[msg.sender] + msg.value;
    }

Also about the interface, it should just contain the function header of your contract, not its logic nor variables like the struct of transactionLog array, so the interface should be something like:

function addTransaction(address _sender, address _receiver, uint _amount, uint _approvalsNeeded, uint _numApprovals) external;

function getTransaction(uint _index) public view returns(uint, address, address, uint, uint, uint);

// etc...

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

Carlos Z.

Hi thanks for the help.

I’m wondering why I’m having issues with these 2 things:

  1. A single owner can approve the same transferRequestID more than one time

  2. Is it normal for the transfer to subtract value from overall contract balance? When a transfer goes through, the amount of the msg.value (of the transfer) is subtracted from the overall contract balance value… why?

here is my code -->

pragma solidity 0.7.5;
pragma abicoder v2;

//copy and paste these addresses for "owners" upon deployment & make limit = 2
//["0x5B38Da6a701c568545dCfcB03FcB875f56beddC4", "0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2", "0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db"]

contract Wallet {
    address[] public owners;
    uint limit;
    
    struct Transfer{
        uint amount;
        address fromPerson;
        address payable receiver;
        uint approvals;
        bool hasBeenSent;
        uint id;
    }
    
    Transfer[] transferRequests;
    
    mapping(address => uint) balance;
    
    event depositDone(uint amount, address indexed depositedTo);
    
    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 (can be empty because you don't need to execute anything, just need a "payable" function)
    function deposit() public payable returns (uint){
        balance[msg.sender] += msg.value;
        //call EVENT to be logged
        emit depositDone(msg.value, msg.sender);
        return balance[msg.sender];
    }
    
    //Create an instance of the Transfer struct and add it to the transferRequests array
    function createTransfer(uint _amount, address payable _receiver) public {
        //Transfer struct takes arguments that coincide with all variables inside actual Transfer struct shown above^
        //then, you transferRequest.push all of this so it gets added to the pending "transfer requests" array
        transferRequests.push(
            Transfer(_amount, msg.sender, _receiver, 0, false, transferRequests.length)
            );
        
    }
    
    function _transfer(address _fromPerson, address _toPerson, uint _amount) private {
        balance[_fromPerson] -= _amount;
        balance[_toPerson] += _amount;
        
    }
    
    //Set your approval for one of the transfer requests.
    //Need to update the Transfer object. (set approvals to a new number, set hasbeenSet if it was sent)
    //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); //basically saying that in order for this to GET approved, the msg.sender hasn't approved it yet
        require(transferRequests[_id].hasBeenSent == false);
        
        approvals[msg.sender][_id] == true;
        transferRequests[_id].approvals += 1;
        
        if (transferRequests[_id].approvals >= limit){
            transferRequests[_id].hasBeenSent == true;
            transferRequests[_id].receiver.transfer(transferRequests[_id].amount); //.transfer is a built in function that sends spec amount
            _transfer(transferRequests[_id].fromPerson, transferRequests[_id].receiver, transferRequests[_id].amount);
        
        }
        
    }
    
    //Should return all transfer requests
    function getTransferRequests() public view returns (Transfer[] memory){
        return transferRequests;
    }
    
    //get balance of msg.sender
    function getUserBalance() public view returns (uint){
        return balance[msg.sender];
    }
    
    //Have a function that returns the balance of the entire contract
    function getContractBalance() public view returns (uint){
        return address(this).balance;
    }
    
    
}

//Bugs to fix:
    //owner can approve same transaction more than one time
    //transfer subtracts from overall contract value
    //FIXED// msg.sender of transfer doesn't lose balance value and recipient address balance doesn't increase in value)
    //FIXED// only the owners can send a transfer, I want EVERYONE to be able to initiate a transfer
    //FIXED// balance changes upon transfer initiaiton -> needs to wait for approval
1 Like

Thank you for the help.

1 Like

My bad, i did not test to sign twice with the same owner, but the issue is that you are NOT assigning the proper value for the mapping, you are using == which is to compare, you should use = instead. Try it and let me know if that works the error.

The contract is the one to manage the funds, the mapping is just to assign who owns how much, but the funds are kept by the contract address.

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

Carlos Z.

My take on the assigment

pragma solidity 0.7.5;
pragma abicoder v2;

contract Multisig{
    address[] private owner;
    uint internal numberOfOwners;
    uint internal numberOfApprovals;
    
    struct Transfer{
        uint amount;
        address payable receiver;
        uint approvals;
        bool hasBeenSent;
        uint id;
    }
   
    Transfer[] transferRequests;
    
    mapping (address=> uint) balance;
    mapping(address => mapping(uint => bool)) approvals;
    
    modifier onlyCreator(){
        require(msg.sender == owner[0], "You aren't the creator");
        _;
    }
    
    modifier onlyOwner(){
        bool isOwner= false;
        for (uint i=0; i<owner.length; i++){
            if(owner[i]==msg.sender){
                isOwner=true;
            }
        }
        require(isOwner==true, "You are not an owner");
        _;
    }
    
    constructor(uint _numberOfOwners, uint _numberOfApprovals) {
        owner.push() = msg.sender;
        numberOfOwners=_numberOfOwners;
        numberOfApprovals=_numberOfApprovals;
    }
    
    function createNewOwner(address payable _owner) public onlyCreator{
        uint index=owner.length;
        require(index<numberOfOwners ,"You have already filled in all owners");
        bool isAlreadyOwner=false;
        for (uint i=0; i<owner.length; i++){
            if(owner[i]==_owner){
                isAlreadyOwner=true;
            }   
        }
        if (isAlreadyOwner==false){
            owner.push()= _owner;
        }
    }
    
    function getNumberofOwners() public view returns(uint){
        return owner.length;
    }
    function numberOfUnassignedOwners() public view returns(uint){
        return numberOfOwners-owner.length;
    }
    function deposit(uint _amount) public payable{
        balance[owner[0]] += _amount;
    }
    
    function createTransfer(uint _amount, address payable _receiver) public onlyOwner {
        transferRequests.push(Transfer(_amount, _receiver, 0, false, transferRequests.length) );
    }
    
    function approve(uint _id) public onlyOwner {
        //An owner should not be able to vote twice.
        require(approvals[msg.sender][_id]==false, "You have already approved this");
        //An owner should not be able to vote on a tranfer request that has already been sent.
        require(transferRequests[_id].hasBeenSent==false, "Transfer already executed");
        
        approvals[msg.sender][_id] = true;
        transferRequests[_id].approvals++;
        
        //When the amount of approvals for a transfer has reached the limit, this function should send the transfer to the recipient.
        if(transferRequests[_id].approvals >= numberOfApprovals){
            _transfer(transferRequests[_id].receiver, owner[0], transferRequests[_id].amount);
            transferRequests[_id].hasBeenSent = true;
        }
    }
    
    function _transfer(address _to, address _from, uint _amount) private{
        balance[_from]-= _amount;
        balance[_to] += _amount;
    }
    
    function getTransferRequests() public view returns (Transfer[] memory){
        return transferRequests;
    }
    function getBalance() public view returns(uint){
        return balance[msg.sender];
    }
}
1 Like

Hi Carlos, thanks for the speedy reply… the suggestion to fix the owner only being able to approve a transaction ID once worked great, thank you.

However, once I initiate a new transfer, the owners can now no longer approve the new transfer request… It’s like they get one opportunity to approve - and when I approve something the first time, they can’t approve anymore… can you try it out and let me know?

pragma solidity 0.7.5;
pragma abicoder v2;

//copy and paste these addresses for "owners" upon deployment & make limit = 2
//["0x5B38Da6a701c568545dCfcB03FcB875f56beddC4", "0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2", "0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db"]

contract Wallet {
    address[] public owners;
    uint limit;
    
    struct Transfer{
        uint amount;
        address fromPerson;
        address payable receiver;
        uint approvals;
        bool hasBeenSent;
        uint id;
    }
    
    Transfer[] transferRequests;
    
    mapping(address => uint) balance;
    
    event depositDone(uint amount, address indexed depositedTo);
    
    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 (can be empty because you don't need to execute anything, just need a "payable" function)
    function deposit() public payable returns (uint){
        balance[msg.sender] += msg.value;
        //call EVENT to be logged
        emit depositDone(msg.value, msg.sender);
        return balance[msg.sender];
    }
    
    //Create an instance of the Transfer struct and add it to the transferRequests array
    function createTransfer(uint _amount, address payable _receiver) public {
        //Transfer struct takes arguments that coincide with all variables inside actual Transfer struct shown above^
        //then, you transferRequest.push all of this so it gets added to the pending "transfer requests" array
        transferRequests.push(
            Transfer(_amount, msg.sender, _receiver, 0, false, transferRequests.length)
            );
        
    }
    
    function _transfer(address _fromPerson, address _toPerson, uint _amount) private {
        balance[_fromPerson] -= _amount;
        balance[_toPerson] += _amount;
        
    }
    
    //Set your approval for one of the transfer requests.
    //Need to update the Transfer object. (set approvals to a new number, set hasbeenSet if it was sent)
    //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); //basically saying that in order for this to GET approved, the msg.sender hasn't approved it yet
        require(transferRequests[_id].hasBeenSent == false);
        
        approvals[msg.sender][_id] = true;
        transferRequests[_id].approvals += 1;
        
        if (transferRequests[_id].approvals >= limit){
            transferRequests[_id].hasBeenSent == true;
            transferRequests[_id].receiver.transfer(transferRequests[_id].amount); //.transfer is a built in function that sends spec amount
            _transfer(transferRequests[_id].fromPerson, transferRequests[_id].receiver, transferRequests[_id].amount);
        
        }
        
    }
    
    //Should return all transfer requests
    function getTransferRequests() public view returns (Transfer[] memory){
        return transferRequests;
    }
    
    //get balance of msg.sender
    function getUserBalance() public view returns (uint){
        return balance[msg.sender];
    }
    
    //Have a function that returns the balance of the entire contract
    function getContractBalance() public view returns (uint){
        return address(this).balance;
    }
    
    
}

//Bugs to fix:
    //FIXED//owner can approve same transaction more than one time
    //transfer subtracts from overall contract value
    //FIXED// msg.sender of transfer doesn't lose balance value and recipient address balance doesn't increase in value)
    //FIXED// only the owners can send a transfer, I want EVERYONE to be able to initiate a transfer
    //FIXED// balance changes upon transfer initiaiton -> needs to wait for approval
1 Like

Thanks, I can understand it now. I read that when mapping is initialized all possible keys already exists and they have value 0 or false. Which is something that was not in instructor video but is good to know.

What also confused me from the start is that I thought when you use mapping you actually access some existing variable for example from the struct. Later I found out you actually ā€œcreate another new tableā€ with default values 0 and false.

I will leave this here so it might help somebody who got it wrong for the first time same as me :slight_smile:

3 Likes

Here is my multisig wallet without any mapping, I was saving voters addresses into array in struct and then did ā€œrequireā€ (if they already voted) with that.

pragma solidity 0.7.5;
pragma abicoder v2;

contract multisigwallet {
    
    address[] owners;
    uint approvalsNeeded;
        
    constructor (address[] memory _owners, uint _approvalsNeeded) {
        owners = _owners;
        approvalsNeeded = _approvalsNeeded;
    }
    
    modifier onlyOwner {
        bool owner = false;
        for(uint i=0;i<owners.length;i++){
            if(owners[i] == msg.sender){
                owner = true;
            }
        }
        require(owner == true, "You are not owner");
        _;
    }
    
    event transferSent(uint amount, address indexed receiver);
    
    struct transferRequest {
        uint transferID;
        address payable to;
        uint amount;
        uint approvalsGranted;
        address[] grantedAddresses;
        bool sent;
    }
    
    transferRequest[] transferRequests;


    function deposit() payable public {}

    
    function getWalletBalance() public view returns(uint){
        return address(this).balance;
    }
    
    function createTransfer(address payable to, uint amount) onlyOwner public  {
        require(address(this).balance >= amount,  "Balance not sufficient");
        require(address(this) != to, "Don't transfer money to yourself");
        address[] memory nothing;
        transferRequests.push(transferRequest(transferRequests.length, to, amount, 0, nothing, false));
    }
    
    function getTransferRequests() onlyOwner public view returns(transferRequest[] memory) {
        return transferRequests;
    }

    function approve(uint _transferId) onlyOwner public {
        //check if already sent
        require(transferRequests[_transferId].sent == false, "Transfer already sent");
        
        //check if already voted
        bool grant = false;
        for(uint i=0;i<transferRequests[_transferId].grantedAddresses.length;i++){
            if(transferRequests[_transferId].grantedAddresses[i] == msg.sender){
                grant = true;
            }
        }
        require(grant == false, "You already voted");
        
        //approval
        transferRequests[_transferId].approvalsGranted++;
        transferRequests[_transferId].grantedAddresses.push(msg.sender);
        
        //check if transfer can be completed
        if (transferRequests[_transferId].approvalsGranted >= approvalsNeeded) {
            transferRequests[_transferId].to.transfer(transferRequests[_transferId].amount);
            transferRequests[_transferId].sent = true;
            emit transferSent(transferRequests[_transferId].amount, transferRequests[_transferId].to);
        }
    }
    
}
2 Likes

Here is multisig wallet code with double mapping:

pragma solidity 0.7.5;
pragma abicoder v2;

contract multisigwallet {
    
    address[] owners;
    uint approvalsNeeded;
        
    constructor (address[] memory _owners, uint _approvalsNeeded) {
        owners = _owners;
        approvalsNeeded = _approvalsNeeded;
    }
    
    modifier onlyOwner {
        bool owner = false;
        for(uint i=0;i<owners.length;i++){
            if(owners[i] == msg.sender){
                owner = true;
            }
        }
        require(owner == true, "You are not owner");
        _;
    }
    
    event transferSent(uint amount, address indexed receiver);
    
    struct transferRequest {
        uint transferID;
        address payable to;
        uint amount;
        uint approvalsGranted;
        bool sent;
    }
    
    transferRequest[] transferRequests;
    
    mapping(address => mapping(uint => bool)) voted;


    function deposit() payable public {}

    
    function getWalletBalance() public view returns(uint){
        return address(this).balance;
    }
    
    function createTransfer(address payable to, uint amount) onlyOwner public  {
        require(address(this).balance >= amount,  "Balance not sufficient");
        require(address(this) != to, "Don't transfer money to yourself");
        transferRequests.push(transferRequest(transferRequests.length, to, amount, 0, false));
    }
    
    function getTransferRequests() onlyOwner public view returns(transferRequest[] memory) {
        return transferRequests;
    }

    function approve(uint _transferId) onlyOwner public {
        //check if already sent
        require(transferRequests[_transferId].sent == false, "Transfer already sent");
        
        //check if already voted
        require(voted[msg.sender][_transferId] == false, "You already voted");
        
        //approval
        transferRequests[_transferId].approvalsGranted++;
        voted[msg.sender][_transferId] = true;
        
        //check if transfer can be completed
        if (transferRequests[_transferId].approvalsGranted >= approvalsNeeded) {
            transferRequests[_transferId].to.transfer(transferRequests[_transferId].amount);
            transferRequests[_transferId].sent = true;
            emit transferSent(transferRequests[_transferId].amount, transferRequests[_transferId].to);
        }
    }
    
}
1 Like

I found it very hard to build this project on my own, I had no idea where to start even with this template…
And when I saw Filip building this in the last video I feel so bad , because I don’t understand much and I would not write something like that on my own @thecil :sob:
Some easy stuff I can write, like this bank contract or similar, but here I I struggle so bad…

What should I do? Does it mean I’m not ready to continue?

2 Likes