Project - Multisig Wallet

Hey @Tomahawk

Solidity 201 and then Smart contract security.
Both of them are extremely important courses that will give you a tremendous knowledge.

Happy learning,
Dani

1 Like

Thank you @thecil ! It works as it should be.

And yes soon I will get on those courses @dan-i, probably I will start one of them today if everything goes well :slight_smile:

1 Like

I’ve tried to come as far as I could on my own, then I watched the last Filip’s video two days ago and finished writing it. I wrote under // what problems I had in understanding/writing.

  • I cannot save the .sol file in Remix to have it for later when I close and then reopen it (it’s not tere), on my laptop.
pragma solidity 0.7.5.;
pragma abicoder V2;

contract Multisig {

address[] public owners;
uint limit;

//I included the address from 1stly in the struct & haven't included te bool.
//Had difficulties understanding the bool through the contract.
struct Transfer {
address payable to;
uint amount;
uint txId;
uint approval;
bool toApprove;
}

Transfer[] transferRequests;

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

event TransferAdded(address indexed _from, address indexed _to, uint _amount, uint _txId);
event Approval(address indexed _owner, uint _approval, uint _txId);
event Approved(uint _txId);

//I wanted to include the for loop in the approveTransfer function,
//but have come to the 1st for line myself only,
//understanding this was needed but knowing not how to write it.
modifier OnlyOwners {
bool owner = false;
for (uint i = 0; i < owners.length; i++) {
        if(owners[i] == msg.sender){
        owner = true;
    }
    require(owner == true);
    _;
    }
}

//I inputted three separate addresses instead of using the msg.sender as 1st one.
constructor() {
    owners.push(0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2);
    owners.push(0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db);
    owners.push(0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB);
    limit = 2;
}

//Why is this address _to payable, we previously in the course used payable for the deposit function only,
//because we are sending money outside of the contract?
function addTransfers(address payable _to, uint _amount) public onlyOwnwers {
    emit TransferAdded(msg.sender, _to, _amount, transferRequests.length);
    Transfer NewTransfer = Transfer(_to, _amount, transferRequests.length, 0, false);
    transferRequests.push(NewTransfer);
}

//we were previously using in te course the msg.sender, balance mapping, in the deposit function,
//because we were sending to ourselves? And now we ave an empty function,
//because funds are sent from outside?
function deposit() public payable {
}

//I wanted to write a witdraw function 1stly & the approveTransfer separately & call it from here.
//function withdraw(address _to, uint _amount) public {}

//I had difficulties to assign the double mapping, I used numbers instead _txID and true instead of false 1st (require).
function approveTransfer(uint _txId) public onlyOwnwers {
    require(approvals[msg.sender][_txId] == false);
    //it says error in the next line
    require(transferRequests[_txId]).toApprove == false);
    
    approvals[msg.sender][_txId] = true;
    transferRequests[_txId].approvals++;
    
    emit Approval(msg.sender, transferRequests[_txId].approval, _txId);
    
    if transferRequests[_txId].approval >= limit{
        transferRequests[_txId].toApprove = true;
        transferRequests[_txId].to.transfer(transferRequests[_txId].amount);
        emit Approved(_txId);
    }
  
function getTransferRequests() public view returns (Transfer[] memory);  
  return transferRequests;
}
1 Like
pragma solidity 0.7.5;

contract MultisigWallet {
    
    struct TransferRequest {
        uint amount;
        address payable receiver;
        uint approvalCount;
        bool completed;
        mapping(address => bool) requestApprovals;
    }
    
    TransferRequest[] public transferRequests;
    address[] owners;
    uint numberOfApprovals;
    
    mapping(address => uint) balance;
    
    modifier onlyOwners() {
        require(msg.sender == owners[0] || msg.sender ==  owners[1] || msg.sender ==  owners[2], "Available only for Owners.");
        _;
    }
    
    constructor(address _owner1, address _owner2, address _owner3, uint _numberOfApprovals) public {
        owners.push(_owner1);
        owners.push(_owner2);
        owners.push(_owner3);
        numberOfApprovals = _numberOfApprovals;
    }
    
    function deposit() public payable returns (uint) {
        balance[msg.sender] += msg.value;
        return balance[msg.sender];
    }
    
    function createTransferRequest(uint _amount, address payable _receiver) public onlyOwners {
        TransferRequest storage newTransferRequest = transferRequests.push();
        newTransferRequest.amount = _amount;
        newTransferRequest.receiver = _receiver;
        newTransferRequest.completed = false;
        newTransferRequest.approvalCount = 0;
    }
    
    function approveTransferRequest(uint index) public onlyOwners {
        TransferRequest storage request = transferRequests[index];
        
        require(!request.requestApprovals[msg.sender], "You are already applroved.");
        
        transferRequests[index].approvalCount++;
    }
    
    function completeTransferRequest(uint index) public onlyOwners {
        TransferRequest storage request = transferRequests[index];
        
        require(!request.completed, "Request is already completed.");
        require(request.approvalCount >= 2, "Request should have equal or more than 2 approvals.");
        
        request.receiver.transfer(request.amount);
        request.completed = true;
    }
    
}

Hey @makinitnow, hope you are great.

Please explain why/what is little bit complex for you, maybe i can explain it in a simple way for you :face_with_monocle:

The loop consist on iterating over owners array, check if one of the positions is equal to msg.sender (meaning is one of the owners), if it’s equal, change the boolean owner to true so the function can continue its logic.

The payable keyword is used to assign an address to be able to receive funds, in this case, the address _to will receive funds if the request is approved, but since you already have address payable to; in your struct.

I’m having too many syntax errors in your code, basically typos errors, I suggest you to check carefully why are you have so many typos erros and repost the contract when its should work part of it properly.

image

Carlos Z

1 Like

This is my finished project, I am aware that I probably have to improve a lot of things since I didn’t watch any of the guide videos, I only watched a minute of the first one to think how to map the transaction, I ended up using my own methods.

Thank you very much for reading my code, this is an example of the transaction when we ask to see the slope and pass it an id

call
0:
uint256: pendingAprovals 3
1:
uint256: transactionId 1
2:
bool: aproved false
3:
uint256: ammount 5000000000000000000
4:
address: recipient 0xdD870fA1b7C4700F2BD7f44238821C26f7392148
5:
string: motive Company food services

CODE:

pragma solidity 0.7.5;

import "./Owneable.sol";

contract MultisignWallet is Owneable {
    address[] public walletManagers;
    uint numberOfAprovalsRequired;
    mapping(address => uint) balance;
    mapping(address => bool) admins;
    
    struct TransferRequest {
        uint pendingAprovals;
        uint transactionId;
        bool aproved;
        uint ammount;
        address payable recipient;
        string motive;
    }
    
    TransferRequest[] public transferRequest;

    constructor(){
        walletManagers = [msg.sender,0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2,0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db];
        numberOfAprovalsRequired = 3;
         admins[walletManagers[0]] = true;
         admins[walletManagers[1]] = true;
         admins[walletManagers[2]] = true;
         
         // Investigate if is posible to use for in the constructor.
        // for (uint i = 0 ; i >= walletManagers.length; i++){
        //   admins[walletManagers[i]] = true;
        // }
    }
 
    function createTransfer(uint _ammountToTransfer, address payable _recipient, string memory motive) public {
        require(address(this).balance >= _ammountToTransfer, "The contract don't have sufficient founds");
        require(address(this) != _recipient, "Sender must be different to the contract address");
        require(admins[msg.sender] == true, "Only wallet members can create transactions");
        createPendingTransfer(TransferRequest(numberOfAprovalsRequired, generateTransactionId(), false, _ammountToTransfer, _recipient, motive));
    }
       
    function generateTransactionId() internal view returns (uint) {
        return transferRequest.length + 1;
    }
    
    function aproveAndValidateTransaction(uint _id) public {
        require(admins[msg.sender] == true, "Only wallet members can aprove transactions");
        if(!transferRequest[_id].aproved){
            transferRequest[_id].pendingAprovals -=1;
            if(transferRequest[_id].pendingAprovals == 0){
                transferRequest[_id].aproved = true;
                _transfer(transferRequest[_id].ammount, transferRequest[_id].recipient);
            }
        }
    }
    
    function _transfer(uint _ammount, address payable _recipient) internal{
        _recipient.transfer(_ammount);
        balance[address(this)] -= _ammount;
        balance[_recipient] += _ammount;
        
    }
    
    function createPendingTransfer(TransferRequest memory _transferBody) internal {
        transferRequest.push(_transferBody);
    }
    
    function depositFounds() public payable {
        uint previousBalance = balance[address(this)];
        balance[address(this)] += msg.value;
        assert(balance[address(this)] == previousBalance + msg.value);
    }   
    
    function getWalletBalance() public view returns (uint) {
        return address(this).balance;
    }
    
    function getWalletInternalBalance() public view returns (uint) {
        return balance[address(this)];
    }
    
    function isMember() public view returns (bool) {
        return admins[msg.sender];
    }
    
    function getTransactionMotive(string _id) public view returns ( string){
        require(admins[msg.sender] == true, "Members only");
        return transferRequest[_id].motive;
    }
}

Now im going to see the guide videos and the full project video in order to get feedback and improve my work. I prefer to post my code before see this videos, this was a great experience and i learn a looot!! I strongly recommend to try to finish this project without guides, in order to simulate a real work environment.

Really good challenge.

1 Like

Here is my Multisig Wallet code. :slight_smile:

pragma solidity 0.7.5;

contract MultiSigWallet {
    
    constructor(address[] memory allOwners, uint approvalsRequired) {
        for (uint ownerCount=0; ownerCount<allOwners.length; ownerCount++) {
            owners[allOwners[ownerCount]] = 1;
        }
        owners[msg.sender] = 1;
        approvals = approvalsRequired;
    }
    
    //mapping of owners, 1 = owner
    mapping (address => uint) private owners;
    uint private approvals = 2;
    
    struct transferStruct {
        address to;
        uint value;
        address[] addressesThatHaveApproved;
    }
    mapping (address => transferStruct) private transfers;
    
    function deposit() public payable returns(bool) {
        return true;
    }
    
    function getBalance() public view returns(uint) {
        return address(this).balance;   
    }
    
    function transferRequest(address to, uint value) public returns(bool) {
        require(owners[msg.sender] == 1, "Only owner can use");
        transfers[msg.sender].to = to;
        transfers[msg.sender].value = value;
        transfers[msg.sender].addressesThatHaveApproved.push(msg.sender);
        return true;
    }
    
    function approveTransfer(address ownerAddress) public returns(bool) {
        require(owners[msg.sender] == 1, "Only owner can use");
        require(transfers[ownerAddress].addressesThatHaveApproved.length >= 1, "Needs to be a valid transfer that was requested");
        require(address(this).balance >= transfers[ownerAddress].value, "Balance not sufficient");
        for (uint approvedAddressesCount=0; approvedAddressesCount<transfers[ownerAddress].addressesThatHaveApproved.length; approvedAddressesCount++) {
            require(msg.sender != transfers[ownerAddress].addressesThatHaveApproved[approvedAddressesCount], "Can't approve more than once");
        }
        transfers[ownerAddress].addressesThatHaveApproved.push(msg.sender);

        if (transfers[ownerAddress].addressesThatHaveApproved.length >= approvals) {
            delete transfers[ownerAddress].addressesThatHaveApproved;
            
            (bool success,) = address(transfers[ownerAddress].to).call{value:transfers[ownerAddress].value}("");
            require(success, 'ETH transfer failed');
            
            transfers[ownerAddress].value = 0;
            return true;
        } else {
            return false;
        }
    }

}
2 Likes

Thank you for your kindness & explanations. There is no need that you explain the bool once again to me, I understand it when see the code & relistening to Filip. I’m just slow on code & will need to spend some more time on Java Script & I think I can grasp the logic better… I would like to continue learning.

I’ve reread the code, Remix throws me an error only at the last function, but sorry I do not see what is wrong. I do not get other errors by Remix. I will try to rewrite it in a new file.

pragma solidity 0.7.5.;
pragma abicoder v2;

contract Multisig {

address[] public owners;
uint limit;

struct Transfer {
address payable to;
uint amount;
uint txId;
uint approval;
bool toApprove;
}

Transfer[] transferRequests;

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

event TransferAdded(address indexed _from, address indexed _to, uint _amount, uint _txId);
event Approval(address indexed _owner, uint _approval, uint _txId);
event Approved(uint _txId);

modifier OnlyOwners {
bool owner = false;
for (uint i = 0; i < owners.length; i++) {
        if(owners[i] == msg.sender){
        owner = true;
    }
    require(owner == true);
    _;
    }
}

constructor() {
    owners.push(0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2);
    owners.push(0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db);
    owners.push(0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB);
    limit = 2;
}

function addTransfers(address payable _to, uint _amount) public onlyOwnwers {
    emit TransferAdded(msg.sender, _to, _amount, transferRequests.length);
    Transfer NewTransfer = Transfer(_to, _amount, transferRequests.length, 0, false);
    transferRequests.push(NewTransfer);
}

function deposit() public payable {
}

function approveTransfer(uint _txId) public onlyOwnwers {
    require(approvals[msg.sender][_txId] == false);
    require(transferRequests[_txId].toApprove == false);
    
    approvals[msg.sender][_txId] = true;
    transferRequests[_txId].approvals++;
    
    emit Approval(msg.sender, transferRequests[_txId].approval, _txId);
    
    if(transferRequests[_id].approval >= limit){
        transferRequests[_txId].toApprove = true;
        transferRequests[_txId].to.transfer(transferRequests[_txId].amount);
        emit Approved(_txId);
    }
// here throws an error  
function getTransferRequests() public view returns (Transfer[] memory){
        return transferRequests;
    }
1 Like

you are not closing the function approveTransfer function with the brackets {}, also the contract brackets are missing too.

Carlos Z

1 Like

Ok. Sorry for the obvious typos. I have to pay more attention. I’ve rewritten it.
Can you explain the first event, why it is included at the beginning of the function.

1 Like
pragma solidity 0.7.5;
pragma abicoder v2;

contract Multisig {
    
    address owner1;
    address owner2;
    address owner3;
    uint limit;
    uint balance;
    
    struct Transaction {
        address payable toSend;
        uint amount;
        bool approval1;
        bool approval2;
        bool approval3;
    }
    
    Transaction[] transactionList;
    
    constructor(address _owner1, address _owner2, address _owner3, uint _limit){
        owner1 = _owner1;
        owner2 = _owner2;
        owner3 = _owner3;
        limit = _limit;
    }
    
    function showBalance() public view returns(uint){
        return balance;
    }
    
    function showTransactions() public view returns(Transaction[] memory) {
        return transactionList;
    }
    
    function deposit() public payable{
        balance += msg.value;
    }
    
    function addTransaction(address payable _toSend, uint _amount) public{
        transactionList.push(Transaction(_toSend, _amount, false, false, false));
    }
    
    function sendTransaction(uint _index) private {
        require(balance >= transactionList[_index].amount);
        transactionList[_index].toSend.transfer(transactionList[_index].amount);
        balance -= transactionList[_index].amount;
        delete transactionList[_index];
    }
    
    function countApprovals(Transaction memory _transaction) private pure returns(uint){
        uint count = 0;
        if(_transaction.approval1 == true) {
            count++;
        }
        if(_transaction.approval2 == true) {
            count++;
        }
        if(_transaction.approval3 == true) {
            count++;
        }
        return count;
    }
    
    function approveTransaction(uint _index) public payable{
        if (msg.sender == owner1) {
            transactionList[_index].approval1 = true;
        }
        if (msg.sender == owner2) {
            transactionList[_index].approval2 = true;
        }
        if (msg.sender == owner3) {
            transactionList[_index].approval3 = true;
        }
        if(countApprovals(transactionList[_index]) >= limit) {
            sendTransaction(_index);
        }
    }
}
1 Like

the events should be emitted at the end of the function, not at the beginning, just think a little bit about them, when a function logic is executed, you want to transmit an event so dapps or other contracts can be aware of what is happening on your contract, so after a function logic is executed, a event can be emitted.

Carlos Z

1 Like

I feel like I was working on this for a while. Watched some other youtube videos and one was from 4 years ago! I definitely had to watch all of Philips videos because I would not have come up with double mapping on my own, although after his explanation it makes perfect sense.
I was getting really frustrated with Remix. I couldn’t figure out why my contract wouldn’t deploy. Then I watched the final video, and saw that you have to put the address array input with square brackets and double quotations. Never would have guessed that. I am enjoying deploying the contract now. It’s fun to do that transfer, as long as I remember to change back the address I am creating the transfer from!

pragma solidity 0.7.5;
pragma abicoder v2;

contract MultisigWallet {
    mapping(address => mapping(uint => bool)) approvals;
    address[] public owners;
    Transaction[] transactions;
    uint requiredApprovals;
    uint public balance = 0;
    
    struct Transaction {
        uint amount;
        address payable receiver;
        uint approvals;
        bool executed;
        uint id;
    }
    
    event depositDone(uint amount, address indexed depositedTo);
    event TransactionRequestCreated(uint _id, uint _amount, address _creator, address _receiver);
    event ApprovalReceived(uint _id, uint _approvals, address _approver);
    event TransactionApproved(uint _id);

    constructor(address[] memory _owners, uint _requiredApprovals) {
        owners = _owners;
        requiredApprovals = _requiredApprovals;
    }

    modifier onlyOwners(){
        bool owner = false;
        for (uint i=0; i < owners.length; i++) {
            if (owners[i] == msg.sender) {
                owner = true;
            }
        }
        require(owner == true);
        _;
    }
    
    function deposit() public payable returns (uint) {
        balance += msg.value;
        emit depositDone(msg.value, msg.sender);
        return balance;
    }
    
    function createTransaction(uint _amount, address payable _receiver) public onlyOwners {
        require(balance >= _amount);
        require(msg.sender != _receiver);
        
        emit TransactionRequestCreated(transactions.length, _amount, msg.sender, _receiver);
        transactions.push(Transaction(_amount, _receiver, 0, false, transactions.length));
        balance -= _amount;
    }
    
    function approve(uint _id) public onlyOwners {
        require(approvals[msg.sender][_id] == false);
        require(transactions[_id].executed == false);
        
        approvals[msg.sender][_id] = true;
        transactions[_id].approvals++;
        emit ApprovalReceived(_id, transactions[_id].approvals, msg.sender);
        
        if (transactions[_id].approvals >= requiredApprovals) {
            transactions[_id].executed = true;
            transactions[_id].receiver.transfer(transactions[_id].amount);
            emit TransactionApproved(_id);
        }
    }
    
    function getTransaction() public view returns (Transaction[] memory){
        return transactions;
    }
    
     function getBalanceOfTransactionID(uint _id) public view returns (uint){
        return transactions[_id].amount;
    }
  
}
1 Like

I forgot to post my solution here, thought I’d come back because I wanted to get my solution reviewed especially the testing part mainly as I consider myself a beginner level when it comes to testing.

I have used truffle to carry out tests, would appreciate if someone from the academy can have a look and provide me with feedback on how I can go about writing better tests in the future:

Here’s the link to my repo:
https://github.com/CatalystJesal/multisig-wallet

Thanks,

Jesal

1 Like

This is my multisigwallet if there are any mistakes please let me know.

pragma solidity 0.7.5;
pragma abicoder v2;
 
 contract wallet {
    
    mapping(address=>mapping(uint=>bool))approval;
     
     address[] public owners;
     uint Balance;
     event depositdone(address _recievedfrom , uint Amount);
     event newtransferrequest(uint _txnid, address _from, address _to, uint _Amount);
     event totalapprovals(uint _id, uint _approvals, address _approver);
     
     constructor()  {
      owners.push(0x5B38Da6a701c568545dCfcB03FcB875f56beddC4);
      owners.push(0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2);
      owners.push(0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db);
     }
     
     modifier onlyowner{
         bool owner=false;
         for(uint i=0; i<owners.length ; i++){
             
             if(msg.sender==owners[i]){
                owner= true;
             }
             require(owner==true);
             _;
         }
     }
     
     struct transfer{
       address payable to;
       uint amount;
       uint txnid;
       uint approvals;
       bool approve;
       
     }
     
     transfer[] transferrequest;
     
     function deposit() public payable  {
        Balance+=msg.value;
        
        emit depositdone(msg.sender,msg.value);
        
    }
    
    function createtransaction(address payable reciepent, uint _amount) public onlyowner   {
        require(Balance>=_amount);
        
        emit newtransferrequest(transferrequest.length, msg.sender, reciepent, _amount);
         transfer memory _transfer= transfer(reciepent,_amount,transferrequest.length,0,false);
        transferrequest.push(_transfer); 
    }
    
    function approvetransfer(uint _index) public    {
     
      require(approval[msg.sender][_index]==false);
      require(transferrequest[_index].approve==false);
     
     approval[msg.sender][_index]=true;
     transferrequest[_index].approvals++; 
     
     emit totalapprovals(_index, transferrequest[_index].approvals, msg.sender);
      
      if(transferrequest[_index].approvals>=2){  
         transferrequest[_index].approve==true;  
         transferrequest[_index].to.transfer(transferrequest[_index].amount);
         Balance-=transferrequest[_index].amount;
         delete transferrequest[_index];
     }  
    }
    
    function transferequests() public view returns(transfer[] memory) {
        return transferrequest;
    }
    
   function getbalance() public view returns(uint){
     return   Balance;
    }
          
      
 }
1 Like

Hey @Jesal_Patel, hope you are ok.

I have tested your contract, i was able to deploy it with 3 owners, 2 limit approval, then deposit 4 ethers, create a tx request from 2nd owner to 3rd owner, approved with 1st and 3rd owner (2nd owner cant approve its own transaction, nice require there) funds were sucessfully transfered to 3rd owner :muscle:

Nice job!
Carlos Z

1 Like

pragma solidity 0.7.5;

import ā€œ./Ownable.solā€;

contract Wallet is Ownable {

mapping(address => uint) balance;

uint approvalThreshold;


transactionRequest[] transactionQ;

struct transactionRequest {
    address from;
    address to;
    uint amount;
    uint numApprovals;
    bool    approved;
    uint txId;
}




constructor(address ownerone, address ownertwo, address ownerthree, uint approvalsrequired){
    //set owner addresses, and required number of approvals.
    balance[ownerone] = 0;
    balance[ownertwo] = 0;
    balance[ownerthree] = 0;
    ownerMap[ownerone] = true;
    ownerMap[ownertwo] = true;
    ownerMap[ownerthree] = true;
    approvalThreshold = approvalsrequired;
    
}


function addUser(address _newUserAddress) public onlyOwner {
    
    //this function is used by the contract owner to add addresses (ie other owners) to the "ownsers" mapping
    balance[_newUserAddress] = 0;
    ownerMap[_newUserAddress] = true;
    
}


function requestTx(address recipient, uint amount) public onlyOwner {
    //this function is used by any owner to create a transaction request and add it to the Transaction Queue
    
    
    /* 
    the function will take the following arguments: to address, amount.Each request will have their approved flag set to false.
    
    the function will create a new request object and push it onto the reqest queue. 

    */
    
    transactionRequest memory newTx = transactionRequest(msg.sender, recipient, amount, 0, false, transactionQ.length);
    transactionQ.push(newTx);
    
}


function getTransaction(uint _index) public view returns(uint, address, address, uint, uint, bool) {
    return (transactionQ[_index].txId, transactionQ[_index].from, transactionQ[_index].to, transactionQ[_index].amount, transactionQ[_index].numApprovals, transactionQ[_index].approved);
}

/*
function listTxQ() public view {

    //this function is used by any anyone to show the transaction q 
    //return txID, amount, numberofapprovals, approved
    for (uint i=0; i<transactionQ.length; i++) {
        getTransaction(i);
    }
    
    
}

*/
function approveTransaction(uint txId) public onlyOwner returns(bool) {
//this function is used by ownsers to approve a specific transaction in the Transaction Queue

    /*  
    this function will accept the following arguments: txId
    when this is invoked, the function will check whether the caller is in the owners mapping, it will check the account balance, 
    and if there's enough balance, and it will increment the approvalCount for the transaction.
    If the appvovalCount is greater than or equal to the approvalThreshold, then the transfer function will be invoked
    */
    
    
    transactionQ[txId].numApprovals += 1;
    if (transactionQ[txId].numApprovals >= approvalThreshold) {
        //here we execute the trasfer function
        transactionQ[txId].approved = true;
        transfer(txId);
    }

}

function transfer(uint txId) public {
//need error handling checks

    address _from = transactionQ[txId].from;
    address _to = transactionQ[txId].to;
    uint _amount = transactionQ[txId].amount;
    
    require(balance[_from] >= _amount);
    require(_from != _to);
    
    uint previousSenderBalance = balance[_from];

    _transfer(_from,_to,_amount);
    
    
    //GoverenmentInstance.addTransaction{value: 1 ether}(msg.sender,recipient,amount);
    //emit tranferAdded(amount, recipient);
    
    assert(balance[_from] == previousSenderBalance - _amount);
    
    //event logs and further checks
    

}

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


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


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

}

1 Like
    pragma solidity 0.8.2;
    pragma abicoder v2;
    contract Wallet {
    address[] public owners;
    uint limit;
    event CreateTransferRequest(uint amount, address from, address to);
    event ApprovalRecevied(uint id, bool);

    event TransferApproved(address _from, uint limit);
    struct Transfer{
    uint amount;
    address payable receiver;
    uint approvals;
    bool hasBeenSent;
    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){

}

//Empty function
    function deposit() public payable {}
//Create an instance of the Transfer struct and add it to the transferRequests array

    function supplyChainTransferRequests(uint _amount, address payable _receiver) public onlyOwners {
    require(msg.sender != _receiver);

    transferRequests.push(
    Transfer(_amount, _receiver, 0, false, transferRequests.length));
    emit CreateTransferRequest(_amount, msg.sender, _receiver);
}
//Set your approval for one of the tranfer 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 transfer 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;
if(transferRequests[_id].approvals>=limit){
    transferRequests[_id].hasBeenSent=true;
    transferRequests[_id].receiver.transfer(transferRequests[_id].amount);
emit ApprovalRecevied(_id, true);
emit TransferApproved(msg.sender, limit);
    }
}
//Should return all transfer requests
    function SupplytransferRequests() public view returns (Transfer[] memory){
    return transferRequests;
}

    function getBalance(uint amount) public view returns (uint) {
    return owners[amount].balance;
    }
}
1 Like

Here is my implementation of the multisig wallet.

Choices made:

  • Any number of approvers or any approval limit can be set in the constructor.
  • The transfer initiator cannot be an approver for that transfer.
  • Withdrawals can be made to any address.
  • Only owners can deposit, or interact with the contract in any way.
  • The balance is pooled, its ownership is not tracked by depositing address.
pragma solidity 0.7.5;
pragma abicoder v2;

contract Wallet {
    address[] owners;
    uint limit;
    uint balance;
    
    struct Transfer {
        address initiator;
        address payable recipient;
        uint amount;
        uint numApprovals;
        bool completed;
    }
    
    // Array of all transferRequests. The index is the transferID.
    Transfer[] transferRequests;
    
    // Double mapping of transferID to approver address to boolean approval status.
    mapping(uint => mapping(address => bool)) approvals;
    
    constructor(address[] memory _owners, uint _limit) {
        // ["0x5B38Da6a701c568545dCfcB03FcB875f56beddC4", "0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2", "0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db"]
        owners = _owners;
        limit = _limit;
    }
    
    modifier onlyOwner {
        bool isOwner = false;
        for (uint i = 0; i < owners.length; i++) {
            if (msg.sender == owners[i]) {
                isOwner = true;
            }
        }
        require(isOwner, "Not an owner");
        _;
    }
    
    function getBalance() public view returns (uint) {
        return balance;
    }
    
    function getOwners() public view returns (address[] memory) {
        return owners;
    }
    
    function getTransfer(uint transferID) public view returns (Transfer memory) {
        return transferRequests[transferID];
    }
    
    /*
     * Any of the contract `owners` can deposit to the contract balance.
     * 
     * Returns new balance.
     */
    function deposit() public payable onlyOwner returns (uint) {
        balance += msg.value;
        return balance;
    }
    
    /*
     * Initiates a transfer request, with msg.sender as the initiator.
     * 
     * Returns transferID.
     */
    function withdraw(address payable recipient, uint amount) public onlyOwner returns (uint) {
        require(amount <= balance, "Insufficient balance");
        
        // Should the withdrawer count as an approver? Assume not.
        Transfer memory transferRequest = Transfer(msg.sender, recipient, amount, 0, false);
        transferRequests.push(transferRequest);
        uint transferID = transferRequests.length - 1;
        
        return transferID;
    }
    
    /*
     * Approves a transfer request. The initiator cannot be an approver, and `limit` number
     * of approvers are required. Once the number of approvers is satisfied, the transfer
     * is made.
     *
     * Returns true if approved, false if more approvals required.
     */
    function approve(uint transferID) public onlyOwner returns (bool) {
        require(msg.sender != transferRequests[transferID].initiator, "Initiator cannot be an approver");
        require(!approvals[transferID][msg.sender], "This owner has already approved this transfer");
        
        Transfer storage transferRequest = transferRequests[transferID];
        approvals[transferID][msg.sender] = true;
        transferRequest.numApprovals += 1;
        
        if (transferRequest.numApprovals >= limit) {
            transfer(transferRequest.recipient, transferRequest.amount);
            transferRequest.completed = true;
            return true;
        }
        
        return false;
    }
    
    function transfer(address payable recipient, uint amount) private returns (uint) {
        recipient.transfer(amount);
        balance -= amount;
        return balance;
    }
    
}
1 Like

Hi Filip try the exercice project:
The transfer is not working can you check and point me to the issues?

pragma solidity 0.7.5;
pragma abicoder v2;

contract Ownable {
address owner;

constructor() {
    owner = msg.sender;
}

modifier onlyOwner() {
    require(msg.sender == owner, "Only For The Contract Owner.");
    _;
}

}

contract Wallet is Ownable {
address[] public approvers;
uint public limit;

struct Request {
    uint requestId;
    address payable recipient;
    uint256 amount;
    uint approvalsCount;
    bool funded;
   // address[] approvedBy;
}

constructor(uint _limit) {
    approvers.push(msg.sender);
    limit = _limit;
}

modifier onlyApprover() {
    bool isApprover = false;
    for (uint i = 0; i < approvers.length; i++) {
        if (msg.sender == approvers[i]) {
            isApprover = true;
        }
    }
    
    require(isApprover == true, 'Only Approvers can do that');
    _;
}


event AddedApprover(address _approver);
event RequestAdded(address _recipient, uint _amount);
event RequestApproved(address _recipient, uint _amount);

uint public requestCount = 0;
mapping(uint => Request) public requests;
mapping(address => mapping(uint => bool)) approvals;

function addApprover(address _approver) public onlyOwner{
    approvers.push(_approver);
    emit AddedApprover(_approver);
}
function addRequest(address _recipient, uint _amount) public {
    requestCount++;
    address payable recipientPayableAdr = payable(_recipient);
    requests[requestCount] = Request(requestCount, recipientPayableAdr, _amount, 0, false);
    emit RequestAdded(_recipient, _amount);
}
function approve(uint _requestID) public onlyApprover{
    require(requests[_requestID].funded == false, "Already funded");
    require(approvals[msg.sender][_requestID] == false, "Only one approbation per request.");
    approvals[msg.sender][_requestID] = true;
    requests[_requestID].approvalsCount += 1;
    if (requests[_requestID].approvalsCount == limit) {
        address payable recipientPayableAdr = payable(requests[_requestID].recipient);
        uint amountToSend = requests[_requestID].amount;
        recipientPayableAdr.transfer(amountToSend);
        requests[_requestID].funded = true;
        emit RequestApproved(recipientPayableAdr, amountToSend);
    }
}

}

1 Like