Project - Multisig Wallet

Yes, one of the simple improvements (from my personal opinion) if to automatically send the funds once it reach the approval limit, i mean, when the 2nd owner approve the transaction, it should be sent automatically to the receiver of the transaction, because that way the approval limit have a better use case, also you can save gas fees because there is no need to spent gas to send the transaction.

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

Carlos Z.

1 Like

Hey @MarcelJ, hope you are great.

Your contracts looks very good, but it have some minor issues that you should solve to made it work properly.

   function deposit(uint _amount) public payable {
       totalBalance += _amount;
   }

The deposit functions does not need to have an amount parameter, the payable keyword give the function the ability to receive funds (msg.value).

The balance of the contract can be retrieve through address(this).balance.

Overall your contract is almost very good! :nerd_face:

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

Carlos Z.

1 Like

@thecil,
that makes sense, thanks a lot!

1 Like

Hey @ElCrypto, hope you are well.

Your contract is almost great, but it have some improvements to be made.

    modifier onlyOwner{
       require(msg.sender == owner1 || msg.sender == owner2 || msg.sender == owner3);
       _; // Run the function
   }

In case any error (revert), there is no way to know if this modifier has being trigger, you should specify a error message. (google a little bit or read other students contract to get an idea :nerd_face:)

constructor() {
    ownerApproval[address(owner1)] = false;
    ownerApproval[address(owner2)] = false;
    ownerApproval[address(owner3)] = false;
}

Since contract MultiSigWallet is OwnableWallet you could just specify the variable, instead of converting it to address (because the variable has been already declared has address internal owner#.

None of the functions on contract MultiSigWallet have a access restriction (modifier like onlyOwner), so it does not matter if a non-owner approve or withdraw a transaction, they just simply can because you do not limit any of the functions to only the owners.

function addDeposit(address _from, uint _amount) external {
    depositLog.push(Deposit(_from, _amount, depositLog.length, block.timestamp));
    wallet +=_amount;
}

The addDeposit function does not need to have an amount parameter, the payable keyword give the function the ability to receive funds (msg.value). Also, _from should not be msg.sender?

The balance of the contract can be retrieve through address(this).balance.

Declaration in the interface is not the same has the contract function.
function addDeposit(address _from, uint _amount) external payable;

Your Exchange have basically the same issues has the `MultiSigWallet.

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

Carlos Z.

pragma solidity 0.7.5; 
pragma abicoder v2; 

/* 
- Anyone should be able to deposit ether into the smart contract

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

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

- Owners should be able to approve transfer requests.

- When a transfer request has the required approvals, the transfer should be sent. 
*/ 
contract MultiSigWallet { 
    address[] public approvalGivers;
    uint signaturesRequired; // number of approvals required per transfer 
    
    struct Transfer { 
        uint amount; 
        address payable receiver; 
        uint approvals; 
        bool hasBeenSent; 
        uint id; 
    } 
    
    event TransferRequestCreated(uint _id, uint _amount, address _intiator, address _receiver); 
    event ApprovalReceived(uint _id, uint _approvals, address _approver); 
    event TransferApproved(uint _id); 
    
    Transfer[] transferRequests; 
    
    // hashmap<k, hashmap<transferId, bool>>    
    mapping(address => mapping(uint => bool)) approvals; 
    
    modifier onlyOwners() { 
      bool approvalGiver = false; 
      for(uint i = 0; i < approvalGivers.length; i++){ 
        if (approvalGivers[i] == msg.sender) { 
            approvalGiver = true;   
        } 
      } 
      require(approvalGiver == true); 
      _; 
    } 
    
    constructor(address[] memory _approvalGivers, uint _signaturesRequired) { 
      approvalGivers = _approvalGivers; 
      signaturesRequired = _signaturesRequired; 
    } 
    
    function deposit() public payable {}  
    
    function createTransfer(uint _amount, address payable _receiver) public onlyOwners { 
      require(getBalance() >= _amount); 
      emit TransferRequestCreated(transferRequests.length, _amount, msg.sender, _receiver); 
      transferRequests.push( 
        Transfer(_amount, _receiver, 0, false, transferRequests.length)
      );
    } 
    
    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 >= signaturesRequired) { 
            transferRequests[_id].hasBeenSent = true; 
            transferRequests[_id].receiver.transfer(transferRequests[_id].amount); 
            emit TransferApproved(_id); 
        } 
    } 
    
    function getTransferRequests() public view returns (Transfer[] memory) { 
        return transferRequests; 
    } 
    
    function getBalance() private view returns (uint) {
        return address(this).balance; 
    } 
    
} 

1 Like

Here is the contract that met the requirements.

pragma solidity 0.7.4;

contract MultisigWallet {

   address[] approvers;
    uint approversNeeded;
    uint balance;

    struct TransactionPool {
        address[] approvers;
        address recipient;
        uint amount;
    }
    
    mapping(bytes32 => TransactionPool) transactions;

    event depositDone(uint amount, address indexed depositedTo);
    
    modifier onlyApprover {
        require(isApprover(msg.sender), "Only an approver can do a transaction");
        _;
    }
    
    constructor(address[] memory _approvers, uint _approversNeeded) {
        approvers = _approvers;
        approversNeeded = _approversNeeded;
    }
    
    function deposit() public payable returns (uint)  {
        balance += msg.value;
        emit depositDone(msg.value, msg.sender);
        return balance;
    }
    
    function getBalance() public view returns (uint){
        return balance;
    }
    
    function getApprovers() private view returns (address[] memory){
         return approvers;
    }
    
    // Request a transfer. It will try to transfer if all requirements have been met if not only approves the transaction
    function requestTransfer(address payable recipient, uint amount) public payable onlyApprover returns (uint) {
        require(balance >= amount, "Balance not sufficient");
        approveTransfer(recipient, amount);
        if (isTransferable(recipient, amount)) {
            balance -= amount;
            cleanTransactionApproval(recipient, amount); // We clean the transaction request from memory
            recipient.transfer(amount);
        }
        return balance;
    }
    
    function isApprover(address approver) private view returns (bool) {
        for (uint i=0; i<approvers.length; i++) {
          if (approvers[i] == approver) {
            return true;
          }
        }
        return false;
    }
    
    function approveTransfer(address recipient, uint amount) private {
        bytes32 transactionHash = sha256(abi.encodePacked(recipient, amount));
        TransactionPool storage transactionPoolTemp = transactions[transactionHash];
        transactionPoolTemp.approvers.push(msg.sender);
        transactions[transactionHash] = transactionPoolTemp;
    }
    
    // Check if all approvers have approved this transaction (combination of recipient and amount) 
    function isTransferable(address recipient, uint amount) private view returns (bool) {
        bytes32 transactionHash = sha256(abi.encodePacked(recipient, amount));
        TransactionPool memory _transaction = transactions[transactionHash];
        address[] memory _approvers = _transaction.approvers;
        return _approvers.length >= approversNeeded;
    }
    
    function cleanTransactionApproval(address recipient, uint amount) private {
        bytes32 transactionHash = sha256(abi.encodePacked(recipient, amount));
        delete transactions[transactionHash];
    }
}

1 Like

Here is my solution:

pragma solidity 0.7.5;
pragma abicoder v2;

import "./Ownable.sol";

contract Wallet is Ownable{
    address[] public owners;
    
    struct Transfer{
        uint transferID;
        address payable recipient;
        uint amount;
        uint noOfApprovals;
    }
    
    Transfer[] transferRequests;
    
    mapping(address => mapping(uint=>bool)) approvals; //a double mapping - remember it's similar to a dictionary!
   
    event depositDone(uint amount, address indexed depositedTo);
    event TransferRequestMade(uint amount, address indexed sentTo, uint transferID);

    uint sigsNeeded;
    uint balance;
    
    constructor(address _owner1, address _owner2, address _owner3, uint _sigsNeeded) { //gets run only the first time the contract gets run
       owners.push(_owner1); //make it possible to have as many owners as you like
       owners.push(_owner2);
       owners.push(_owner3);
       sigsNeeded = _sigsNeeded;
    }
    
    function approve (uint transferID, bool yesOrNo) public payable {
        require(msg.sender == owners[0] || msg.sender == owners[1] || msg.sender == owners[2]); //check that the approver is an owner
        approvals[msg.sender][transferID] == yesOrNo; //process the approval that's just come in
        
        uint boolAsInt = yesOrNo ? 1 : 0; //convert the boolean yesOrNo to an integer 1 or 0
        transferRequests[transferID].noOfApprovals += boolAsInt; //increase number of approvals by 1 if approved
        if (transferRequests[transferID].noOfApprovals >= sigsNeeded) { //check if enough approvals
            address payable payableRecipient = transferRequests[transferID].recipient; //make the address payable
            payableRecipient.transfer(transferRequests[transferID].amount); //makes the transfer if there are enough sigs
        }
    }
    
    function deposit () public payable returns (uint) { //this allows anyone to deposit
       balance += msg.value;
       emit depositDone(msg.value, msg.sender);
       return balance;
    }
   
    function getBalance() public view returns (uint) {
       return balance; // how do I return the total balance of the wallet??
    }
   
    function transfer (address payable _recipient, uint _amount) public {
       require(balance >= _amount, "Balance not sufficient"); // if not the case, revert happens, transaction will not happen
       require(msg.sender == owners[0] || msg.sender == owners[1] || msg.sender == owners[2]); //check that the transferer is an owner
       uint transferID = transferRequests.length;
       
       Transfer memory t; //create a Transfer struct t
       t.recipient = _recipient; //put data into t - could I do this in one line?
       t.amount = _amount;
       t.transferID = transferID;
       t.noOfApprovals = 0;
       
       transferRequests.push(t); //add t to the the array
       emit TransferRequestMade(_amount, _recipient, transferID); //emit that a transfer request has been made
       balance -= _amount; //decrease balance by the amount of the request - should this be done here? or only later when the transfer is actually made?
    }
}
1 Like

Hi Thecil,

Thank you for the feedback, much appreciated.

1 Like

Hey @jrdefideveloper, hope you are great.

I’m testing your contract, let me mention you some improvements that you could do on it to made it work properly :face_with_monocle:

    function createTransfer(uint _amount, address payable _receiver) public onlyOwners { 
      require(getBalance() >= _amount); 
      emit TransferRequestCreated(transferRequests.length, _amount, msg.sender, _receiver); 
      transferRequests.push( 
        Transfer(_amount, _receiver, 0, false, transferRequests.length)
      );
    } 

Is good to write each function or logic with the ā€œCheck - Effects - Interactionsā€ Pattern. For Example, with your function should be like this:

    function createTransfer(uint _amount, address payable _receiver) public onlyOwners { 
      //check
      require(getBalance() >= _amount); 
      //effects
      transferRequests.push( 
        Transfer(_amount, _receiver, 0, false, transferRequests.length)
      );
      //interactions
      emit TransferRequestCreated(transferRequests.length, _amount, msg.sender, _receiver); 
    } 

Steps I made:

  • deploy the contract with 3 owners Array, signaturesRequired = 2.
  • approvalGivers is available because you made it public, so owners can be verified.
  • deposit() 3 ethers from 1st owner.
  • createTransfer() 2 ether from 2nd owner to 3rd owner.
  • getTransferRequests() returns an array of pending transfer.
  • approve() with 1st owner and 2nd owner.
  • Check Balance on 3rd owner 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.

2 Likes

My solution:

pragma solidity 0.7.5;

contract MultisigWallet {

    uint public balance;
    uint numberOfApprovals;
    mapping(address => bool) owners;
    Transfer[] transfers;
    mapping(uint => address[]) approvals;
    
    modifier onlyOwners {
        require(owners[msg.sender], "Only owners can call this function");
        _;
    }
    
    struct Transfer {
        address payable recipient;
        uint amount;
    }

    constructor(address[] memory _owners, uint _numberOfApprovals) {
        numberOfApprovals = _numberOfApprovals;
        
        for (uint i = 0; i < _owners.length; i++) {
            owners[_owners[i]] = true;
        }
    }
    
    function deposit() public payable {
        balance += msg.value;
    }
    
    function transfer(address payable recipient, uint amount) public onlyOwners returns(uint) {
        require(amount <= balance, "Balance is not enough");
        
        uint transferId = transfers.length;
        
        transfers.push(Transfer(recipient, amount));
        approvals[transferId].push(msg.sender);
        
        return transferId;
    }
    
    function approve(uint transferId) public onlyOwners {
        require(transferId < transfers.length, "Transfer with this id does not exists");
        require(isApproved(transferId, msg.sender) == false, "Transaction is already approved by sender");
        
        approvals[transferId].push(msg.sender);
        
        if (approvals[transferId].length == numberOfApprovals) {
            executeTransfer(transferId);
        }
    }
    
    function isApproved(uint transferId, address checkedAddress) private view returns(bool) {
        bool result = false;
        
        for (uint i = 0; i < approvals[transferId].length; i++) {
            result = result || (checkedAddress == approvals[transferId][i]);
        }
        
        return result;
    }
    
    function executeTransfer(uint transferId) private {
        require(transfers[transferId].amount <= balance, "Balance is not enough, transfer rejected");
        
        transfers[transferId].recipient.transfer(transfers[transferId].amount);
        
        balance -= transfers[transferId].amount;
    }
    
}

2 Likes

Oke, you might call me a cheater but at least im honest about it. Definitely not becoming a developer myself an because they use pragma solidity ^0.4.24 I will actually not post the code here. But what I did is the following, I used my knowledge I retrieved from previous course here on the academy (unibright & baseline protocol). In the work flow designer you could design a multi approval workflow :wink: You see where I’m going. So yes it’s cheating but Im not and will not be a developer. Just want to understand the possibilities and how to read the given options by the developers. You get what I’m saying. So basically this is me developing something for the company that the developers can work with and code it or learn from it.

I hope you guys understand my inventive answer, definitely I play around and check all the other videos but this was my idea without watching the videos how to actually ā€œcodeā€ it myself!

1 Like

//SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.7.5;
//I haven’t watched the other videos yet, but there are other ways, to be sure. This one operates
//as I’ve told it to. I’m excited to see how others are structured
contract MultiSigWallet{

address[4] _owners;
uint currentVotes;
address most_recent_vote;
uint proposed_amount;
address proposed_recipient;

modifier auth_access(){  //modifier for implementing authrorized access functionality
    require(authorized_access());
    _;
}

function authorized_access() private view returns(bool){  //if msg.sender is an owner, isAuth = true
    bool isAuth = false;
    if(msg.sender == _owners[0]){
        isAuth = true;
        return isAuth;
    }
    if(msg.sender == _owners[1]){
        isAuth = true;
        return isAuth;
    }
    if(msg.sender == _owners[2]){
        isAuth = true;
        return isAuth;
    }
    return isAuth;
}

constructor(){ //Constructor to populate array with meaningful addresses
    _owners[0] = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;
    _owners[1] = 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2;
    _owners[2] = 0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db;
    _owners[3] = address(this);  //this address will serve for depositing to the CONTRACT and not individuals
    currentVotes = 0;            //To track how many authorized users have voted
    most_recent_vote = _owners[3];   //used later to make sure two signatures aren't from same address twice
    proposed_amount = 0;
    contract_address();
}
function contract_address() private view returns(address){
    //address contractAddress = _owners[3];
    return _owners[3];//contractAddress;
}
mapping(address => uint) contributions_and_conBal;  //tracks deposits as merely contributions
                                                    //Once funds are deposited, only the owners can withdraw
function deposit_funds() public payable{            //anyone can deposit into contract
    //what in the back end manages people trying to make larger-than-available moves?
    contributions_and_conBal[msg.sender] += msg.value;
    add_deposit_to_contract();
}
function add_deposit_to_contract() private{  //actually adds funds to contract's balance in mapping
    contributions_and_conBal[_owners[3]] += msg.value;  //the contract can potentially send to itself
}
function get_contract_balance() public view returns(uint){  //function to return current balance of contract
    return contributions_and_conBal[_owners[3]];
}
function initiate_withdraw(address _recipient, uint _amount) public auth_access returns(string memory){
    //checking for sufficient funds after all votes are made (within transfer function) allows for proposing withdrawls 
    //of greater-than-balance amounts.  I promise this is potentially useful
    //function needs to account for user's knowledge of wei vs. eth.
    string memory response;
    if(currentVotes == 0){
        //manage_signatures();
        proposed_amount = _amount;
        proposed_recipient = _recipient;
        currentVotes = 1;
        most_recent_vote = msg.sender;
        response = "your transfer has been submitted for review";
        return response; 
    }
    response = "1 transaction already pending.  Please submit vote on existing transaction first";
    return response;
}
function manage_double_signatures() private view returns(bool){
    if(msg.sender == most_recent_vote){
        return false;
    }
    else return true;
}
function submit_vote() public returns(string memory){ //calling this function constitutes a YES vote
    string memory verification_response; //dont gorget to make currentVotes 0 after execution
    if(manage_double_signatures() && currentVotes == 1){
        transfer_funds();  //how to check success?
        verification_response = "Your transfer has been submitted";
    }
    else{
        verification_response = "You already voted";
    }
    
    return verification_response;
}
function transfer_funds() private {  //check funds here first
        if(contributions_and_conBal[_owners[3]] >= proposed_amount)
        {
            proposed_recipient.call{value:proposed_amount};
            contributions_and_conBal[_owners[3]] -= proposed_amount;
            most_recent_vote = _owners[3];
            currentVotes = 0;
        }
        return;
}

}

1 Like

Okay im done, i was gonna add more stuff to it, but oof it was a lot of work already *o *

Also i have a question which im sure the answer is pretty easy, but my brain cant properly think right now, after all that work trying to get this to compile xD.

question is: i was trying to make the currentProposal function so that it didnt need input from the users, so that they didnt have to do the effort to know the index of the current proposal, but then i tried this :

function currentProposal () public view returns (//parameters){
return (proposedTx[proposedTx.length].amount, …//rest of the array)

This didnt work for me, i also tried other stuff like 0+proposedTx.length xD, but it didnt work, i knnow i should know the answer for this, but i dont D:

pragma solidity 0.7.5;

contract solidity{
    
    struct txn{
        address from; 
        address to;
        uint amount;
        uint index;
        bool tag;
        uint yes;
        uint no; 
    }
    
    address Wallet;

    uint votesNeeded;
    
    uint members;
     
    mapping (address => uint) balance;
    
    mapping(address => bool) owner;

    mapping(uint => mapping(address => bool)) Voted;
    
    txn [] proposedTx;
    
    txn [] approvedTx; 
     
    txn [] rejectedTx;
    
    address[] owners;
    
    modifier onlyOwner (){
        require(owner[msg.sender], "You are not an owner sir");
        _;
    }
    
    modifier already(uint txID) {
        require(!Voted[txID][msg.sender], "You can not vote twice"); 
        _;
    }
    
    function iamOwner ()public{
        require (members > 0, "Set members amount and votes needed first");
        require(!owner[msg.sender], "You are an owner already");
        require(owners.length < members, "We are too many!!!");
        owners.push(msg.sender);
        owner[msg.sender]= true;
    }
    
    function membersAndvotes(uint membersAmount, uint votesneeded) public{ 
        require(votesneeded > 0, "Too little votes");
        require(membersAmount >= votesneeded, "Too many votes");
        votesNeeded = votesneeded;
        members = membersAmount; 
    }
    
    function deposit (address //here goes the contract addres, not the owners) public onlyOwner() payable{
        ContractsAddress = Wallet;
        balance[Wallet] += msg.value; 
    }
    
    function getBalance ()public onlyOwner() view returns(uint){
        return balance[Wallet];
    }
    
    function propose(address recipient, uint amount) public onlyOwner() {
        require(approvedTx.length + rejectedTx.length == proposedTx.length, "Current proposal needs to be approved first");
        require(amount < balance[Wallet], "We do not have all that money");
        proposedTx.push( txn(Wallet, recipient, amount, proposedTx.length, true, 0, 0) );
    }
    
    function voteYes(uint txID)public onlyOwner() already(txID) {
         Voted[txID][msg.sender] = true; 
         proposedTx[txID].yes += 1;
         if (proposedTx[txID].yes >= votesNeeded){
             executeTx(txID);
         }
    }
    
    function voteNo(uint txID)public onlyOwner() already(txID) {
         Voted[txID][msg.sender] = true; 
         proposedTx[txID].no += 1;
         if (proposedTx[txID].no >= votesNeeded){
             rejectTx(txID); 
         }
         
    }
    
   function executeTx(uint txID)private onlyOwner()  {
       require(proposedTx[txID].yes >= votesNeeded);
       proposedTx[txID].tag = true;
       approvedTx.push(txn(proposedTx[txID].from, 
       proposedTx[txID].to, 
       proposedTx[txID].amount, 
       proposedTx[approvedTx.length].index, 
       proposedTx[txID].tag, 
       proposedTx[txID].yes, 
       proposedTx[txID].no));
       balance[Wallet] -= proposedTx[txID].amount;  
       balance[proposedTx[txID].to] += proposedTx[txID].amount;  
    }  
    
    function rejectTx(uint txID)private onlyOwner(){
       require(proposedTx[txID].no >= votesNeeded);
       proposedTx[txID].tag = false;
       rejectedTx.push(txn(proposedTx[txID].from, 
       proposedTx[txID].to, 
       proposedTx[txID].amount, 
       proposedTx[rejectedTx.length].index, 
       proposedTx[txID].tag, 
       proposedTx[txID].yes, 
       proposedTx[txID].no));
    }
    
    function currentProposal(uint txID) public onlyOwner() view returns (address from, address to, uint amount, uint index, uint yes, uint no){
        return(proposedTx[txID].from,
        proposedTx[txID].to,  
        proposedTx[txID].amount, 
        proposedTx[txID].index,
        proposedTx[txID].yes, 
        proposedTx[txID].no);  
        
    } 
    
}

1 Like

Done :); it was an amazing project; I learned a lot.


pragma solidity 0.7.5;

contract walletowenrs{
struct owner{
string name;
address ownerAddress;
}

    owner[] owners;

constructor(){
    owners.push(owner("samar",msg.sender));
    }


modifier onlyOwners() {
    uint i=0;
    bool ownerFound=false;
    while (i < owners.length) {
        if (owners[i].ownerAddress==msg.sender){
            ownerFound=true;
        }
        i++;
    }
    require(ownerFound==true, "No..No your money");
    _;
} 

function isOwner() public view returns(bool){
    uint i=0;
    bool ownerFound=false;
    while (i < owners.length) {
        if (owners[i].ownerAddress==msg.sender){
            ownerFound=true;
        }
        i++;
    }
    return(ownerFound);

} 
function addOwner (string memory _name, address _ownerAddress) public{
owners.push(owner(_name, _ownerAddress));
}

function noOfOwners() public view returns (uint){
    return owners.length;
}



function getOwner(uint _index) public view returns(string memory, address){
    return (owners[_index].name, owners[_index].ownerAddress);
}
}




pragma solidity 0.7.5;

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

contract multiSigWallet is walletowenrs{

uint walletBalance;

struct transferRecord{
    address sender;
    uint txamount; 
    bool authorizedBy2Owners;
}

transferRecord[] transfers;

function deposit(uint _amount) public payable{
   walletBalance +=_amount;
}
function getWalletBalance() public view returns (uint256){
return walletBalance;
}

function initiateTransfer(uint _amount) public onlyOwners {
require(walletBalance>=_amount, ā€œNo enough money in walletā€);
transfers.push(transferRecord(msg.sender, _amount, false));
}

function transferExecute(uint _index) public onlyOwners {
require(walletBalance>=transfers[_index].txamount, ā€œNo enough money in walletā€);
require(transfers[_index].authorizedBy2Owners, ā€œYou need 2 to approveā€);

 uint walletBalanceBefore= walletBalance;
// address(this).transfer(transfers[_index].txamount);
walletBalance -= transfers[_index].txamount;

 assert (walletBalance==(walletBalanceBefore-transfers[_index].txamount));
}

function approveTransfer(uint _index, bool approve) public onlyOwners returns(bool){
require(msg.sender!= transfers[_index].sender, ā€œYou can’t approve your own transferā€);
require(walletBalance>=transfers[_index].txamount, ā€œno enough money in walletā€);

if (approve) {
    transfers[_index].authorizedBy2Owners=true;
}

return(transfers[_index].authorizedBy2Owners=true);
}

}```
1 Like

Here’s a second stab at it. I hadn’t watched any of the follow-up videos before my first contribution. Now I’ve TRIED implementing a double mapping, but now I’m getting an opcode error, and I’d like to move on in hopes of learning enough to fix my code without needing to pull any hair out.

//SPDX-License-Identifier: GPL:3-0
pragma solidity ^0.7.5;
pragma abicoder v2;

contract multisig_wallet{
//here’s my stab at it after learning about the double-mapping
address[4] _owners;
uint mostRecentTransferNum;
mapping(address => mapping(uint => bool)) voteTracker;
mapping(address => uint) balanceAndContributions;//tracks lifetimes contributions from address, and
//current balance of the contract’s address

constructor(){
_owners[0] = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;
_owners[1] = 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2;
_owners[2] = 0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db;
_owners[3] = address(this);  //this address will serve for depositing to the CONTRACT and not individuals
mostRecentTransferNum = 0;   //0 can be first index as well
}//end Constructor

modifier is_owner(){
    require(authorized_address());
    _;
}
struct transfers{       //items on list of instantiated transfer requests
        uint transId;
        uint amount;
        address recipient;
}

transfers[] pendingTransfers;  //array to store pending transfers awaiting votes

function deposit() public payable returns(string memory){//calls private deposit function and returns verification
    string memory verResponse = "Thank you for your deposit";
    if(perform_deposit()){ return verResponse;}
    verResponse = "Something went wrong";//if TRUE not returned by if perform_deposit() then return problem
    return verResponse;
}

function perform_deposit() private returns(bool){//actually changes the values in the mapping
    balanceAndContributions[msg.sender] += msg.value;
    balanceAndContributions[_owners[3]] += msg.value;
    return true;
}

function get_contract_balance() public view returns(uint){//query for contract's current balance
    return balanceAndContributions[_owners[3]];
}

function authorized_address() private view returns(bool){
    if(msg.sender == _owners[0]){return true;}
    else if(msg.sender == _owners[1]){return true;}
    else if(msg.sender == _owners[2]){return true;}
    return false;
}
//LEFT OFF HERE 
//can I use "pendingTransfers.length"??
//below function needs a means to increase the mostRecentTransferNum value
function initiate_transfer(address _recipient, uint _amount) public payable is_owner returns(string memory){//security concerns
    address testAddress = _recipient;
    uint testAmount = _amount;
    log_transfer(testAddress, testAmount);
    map_the_transfer();//   (mostRecentTransferNum);
    string memory response = "Your transaction has been submitted for approval by peers";
    return response;
    //uint id = zombies.push(Zombie(_name, _dna)) - 1; found this on github or some MB
}

function log_transfer(address _recipient, uint _amount) private{
    address loggedAddress = _recipient;
    uint loggedAmount = _amount;
    pendingTransfers[mostRecentTransferNum].transId = mostRecentTransferNum;
    pendingTransfers[mostRecentTransferNum].amount = loggedAmount;//_amount;
    pendingTransfers[mostRecentTransferNum].recipient = loggedAddress;//_recipient;
}

function map_the_transfer() private returns(transfers memory){
    uint i = 0;
    uint index = mostRecentTransferNum;
    while(i <= 2){
        voteTracker[_owners[i]][index] = false;
        i++;
    }
    voteTracker[msg.sender][index] = true;  //initiating a transfer request constitutes a yes vote
    
    return pendingTransfers[i];//no need to check funds until transfer has been approved anyway
}

}

Hey @filip , Ive modified the multisig wallet so it allows for Dai deposits and then uses the Yearn finance interface to deposit into Yearn and earn an interest(which will also require multisig approval). Im not sure if ive used the enum correctly in the struct and the ā€˜if’ conditions contained in the functions. Im also not sure if ive written the correct code for the stakeDai and withdrawDai functions?. Im also not sure if any of these functions should be declared as external? I know that i should emit some events and preferably have another parent contract that contains the state variables to be inherited by this contract. I was hoping to get some guidance and feedback before developing this contract any further.

`pragma solidity ^0.7.5;
pragma abicoder v2;
import ā€˜@openzepplin/contracts/token/ERC20/IERC20.sol’;

interface IYDAI {
function deposit (uint _amount) external;
function withdraw (uint _shares) external;
function balanceOf (address account) external view returns (uint);
function getPricePerFullShare() external view returns (uint);
}

contract multiSigDefiWallet {

IERC20 dai = IERC20(0x6b175474e89094c44da98b954eedeac495271d0f);
IYDAI yDai = IYDAI(0xC2cB1040220768554cf699b0d863A3cd4324ce32);

address[] public Owners;
uint requiredApprovals;
uint totalEth;
uint totalDai;
uint stakedDai;

enum TransType {trans_Eth, trans_Dai, stake_Dai, withdraw_Dai};

struct Transaction {
TransType tType;
address payable to;
uint amount;
uint numApprovals;
uint Id;
bool confirmed;
bool executed;
}

Transaction[] transactions;

mapping(address => mapping(uint => bool))Approvals;
mapping(address => bool)isOwner;
mapping(address => uint)contributedEth;
mapping(address => uint)contributedDai;

modifier onlyOwners(){
require(isOwner[msg.sender]== true);
_;
}

modifier txExists(uint _txId){
require(_txId < transactions.length);
_;
}

modifier notApproved(uint _txId){
require(Approvals[msg.sender][_txId] == false);
_;
}

modifier notExecuted(uint _txId){
require(transactions[_txId].executed == false);
_;
}

modifier isConfirmed(uint _txId){
require(transactions[_txId].confirmed == true);
_;
}

constructor(address[] memory _owners, uint _requiredApprovals){
require(_owners.length > 0);
require(_requiredApprovals > 0 && _requiredApprovals <= _owners.length);
for (uint i=0; i< _owners.length; i++){
address owner = _owners[i];
require(owner!= address(0);
require(!isOwner[owner]);
isOwner[owner]= true;
Owners.push(owner);
}
requiredApprovals = _requiredApprovals;
}

function depositEth() public payable {
uint amount = msg.value;
totalEth += amount;
contributedEth[msg.sender]+= amount;
}

function depositDai() public payable {
uint amount = msg.value;
require(dai.balanceOf(msg.sender) >= amount);
totalDai += amount;
contributedDai[msg.sender]+= amount;
}

function submitTransaction (uint _amount, address payable _to, transType _tType) public onlyOwners {
if (_tType == TransType.trans_Eth) {
require(_amount <= totalEth);
Transaction memory newTransaction = Transaction (_tType, _to, _amount, 0, transactions.length, false, false);
transactions.push(newTransaction);
}
else if (_tType == TransType.trans_Dai) {
require(_amount <= totalDai);
Transaction memory newTransaction = Transaction (_tType, _to, _amount, 0, transactions.length, false, false);
transactions.push(newTransaction);
}
else if (tType == TransType.stake_Dai) {
require(_amount <= totalDai);
Transaction memory newTransaction = Transaction (_tType, _to, _amount, 0, transactions.length, false, false);
transactions.push(newTransaction);
}
else if (_tType == TransType.withdraw_Dai) {
require(_amount <= stakedDai);
Transaction memory newTransaction = Transaction (_tType, _to, _amount, 0, transactions.length, false, false);
transactions.push(newTransaction);
}
}

function confirmTransaction(uint _Id) public onlyOwners txExists(_Id) notApproved(_Id) notExecuted(_Id){
Approvals[msg.sender][_Id] = true;
transactions[_Id].numApprovals ++ ;
if(transactions[_Id].numApprovals >= requiredApprovals){
transactions[_Id].confirmed = true;
}
}

function executeTransaction(uint_Id) public onlyOwners isConfirmed(_Id){
require(Approvals[msg.sender][_Id] == true);
uint amount = transactions[_Id].amount;
address memory to = transactions[_Id].to;

if (transactions[_Id].tType == TransType.trans_Eth) {
transferEth (to , amount);
}

else if (transactions[_Id].tType == TransType.trans_Dai) {
transferDai (to , amount);
}

else if (transactions[Id].tType == TransType.stake_Dai) {
stakeDai (amount);
}
else if (_transactions[_Id].tType == TransType.withdraw_Dai) {
withdrawDai(amount);
}
transactions[_Id].executed = true;
}

function revokeConfirmation (uint_Id) public onlyOwners txExists(_Id) notExecuted(_Id){
require(Approvals[msg.sender][_Id] == true);
Approvals[msg.sender][_Id] == false;
transactions[_Id].numApprovals --;
if (transactions[_Id].numApprovals < requiredApprovals){
transactions[_Id].confirmed = false;
}

function getStakedDaiDetails() public onlyOwners view returns (uint){
uint price = yDai.getPricePerFullShare();
uint balanceShares = yDai.balanceOf(address(this));
uint totalDaiRewards = balanceShares * price;
uint reward = totalDaiRewards - stakedDai;
return (stakedDai, reward);
}

function transferEth (address _to , uint _amount) internal {
require(_amount > 0);
require(_amount <= totalEth);
_to.call.value(_amount);
totalEth -= _amount;
}

function transferDai (address _to , uint _amount) internal {
require(_amount > 0);
require(_amount <= totalDai);
dai.transfer(_to, _amount);
totalDai -= _amount;
}

function stakeDai (uint_amount) internal {
require(_amount <= totalDai);
dai.approve(address(yDai), _amount);
yDai.deposit (_amount);
totalDai -= _amount;
stakedDai += _amount;

}

function withdrawDai(uint _amount) internal {
require(_amount <= stakedDai);
uint sharePrice = yDai.getPricePerFullShare();
uint shareBalance = _amount / sharePrice;
yDai.withdraw(shareBalance);
totalDai += _amount;
stakedDai -= _amount;
}

}
`

1 Like

Hey @Melshman, hope you are great.

I try to run your contract, I was able to deposit 12 ethers to the contract and get the balance of the contract properly, I do not understand quite well how initiate_withdraw() function works, it returns a message response in case 1 transaction is pending, but i was able to set multiple transactions ( the same, the error message never appear), you dont have any require to validate if the inputed data is already set has a pending transaction or not.

Also the transaction is not saved anywhere (mapping or array), I think you need to do some improvements on the contract in order to make it work properly.

Here is my contract, please take a look to learn it properly and give you some ideas to fix yours:

my contract
//"SPDX-License-Identifier: MIT"
pragma solidity 0.7.5;
pragma abicoder v2;
contract Ownable{
  address payable private owner;

  modifier onlyOwner{
    require(msg.sender == owner,"Only contract owner allowed");
    _;
  }
  constructor(){
    owner = msg.sender;
  }
}

contract multiSig is Ownable{
    address[] private owners;
    uint limit;

    struct Transfer{
        uint amount;
        address payable receiver;
        uint approvals;
        bool hasBeenSent;
        uint id;
    }
    event depositDone(address indexed _from, uint _amount);
    event transferDone(address indexed _from, address indexed _to, uint _amount);
    event transferRequested(address indexed _from, uint _amount);
    event transferApproved(uint _id, address indexed _from, uint _amount);

    Transfer[] internal transferRequests;

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

    //Should only allow people in the owners list to continue the execution.
    modifier onlyOwners(){
      bool isOwner = false;
      for(uint _index = 0; _index < owners.length; _index++){
        if(owners[_index] == msg.sender){
          isOwner = true;
        }
      }
      require(isOwner == true, "Only multisig owners allow");
      _;
    }

    //Should initialize the owners list and the limit
    constructor(address _owner1, address _owner2, address _owner3, uint _limit) {
      require(_owner1 != _owner2 && _owner1 != _owner3 && _owner2 != _owner3, "Signatures should be different");
      owners = [_owner1, _owner2, _owner3];
      limit = _limit;
    }

    //Empty function
    function deposit() public payable {
      require(msg.value > 0, "Value should be above zero");
      emit depositDone(msg.sender, msg.value);
    }

    //Create an instance of the Transfer struct and add it to the transferRequests array
    function createTransfer(uint _amount, address payable _receiver) public onlyOwners {
      require(address(this).balance > _amount, "Insuficient Contract Funds");
      transferRequests.push(Transfer(_amount, _receiver, 0, false, transferRequests.length));
      emit transferRequested(_receiver, _amount);
    }

    //Set your approval for one of the transfer requests.
    function approve(uint _id) public onlyOwners {
      //An owner should not be able to vote on a tranfer request that has already been sent.
      require(transferRequests[_id].hasBeenSent == false, "Transfer already approved");
      //An owner should not be able to vote twice.
      require(approvals[msg.sender][_id] == false, "Transfer already signed by this account");
      //Need to update the mapping to record the approval for the msg.sender.
      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 == limit){
        transferRequests[_id].receiver.transfer(transferRequests[_id].amount);
        //Need to update the Transfer object.
        transferRequests[_id].hasBeenSent = true;
        emit transferApproved(_id, transferRequests[_id].receiver, transferRequests[_id].amount);
      }
    }

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

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

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

Carlos Z.

Hey @Carlosvh96, hope your great.

You have few issues on your contract, your deposit function have an issue with the comment you write inside its arguments.

    function deposit (address //here goes the contract addres, not the owners) public onlyOwner() payable{
        ContractsAddress = Wallet;
        balance[Wallet] += msg.value; 
    }

Also you are using incorrectly the modifier onlyOwner in your functions, check the reply above this one, to check my contract just to give you some guide lines.

You almost have it! :muscle:

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

Carlos Z.

Hey @Bdos87, hope you are ok.

Your contract looks very good, but this is out of the scope of the course and the assignment, I could suggest you to complete the assignment to review properly your code, also if your looking for more advance properties you should finish this course and go into the Ethereum
Smart Contract Programming 201.

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

Carlos Z.

We were challenged to make this contract better and share it, all im asking for is some feedback on the things i specified. Is that too much to ask brother? I have finished this course and assignment and I added to the functionality on my own.

1 Like