Project - Multisig Wallet

great. glad i could help. and no worries.

1 Like

My final version:

I’d love to have your opinion on it as I really did 100% myself without the help videos.
The differences I noticed comparing the solution from the video are the following :

  • My Double Mapping is inversed : First you provide the Transaction ID, then you can check which owners have approved it yet
  • My onlyOwner modifier structure is very different. Is it lighter/heavier to run ?
  • other differences too
//SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.7;
pragma abicoder v2;


contract MultiSigWallet {

    uint ApprovalRequired;
    address[] ArrayOwners ;
    mapping(address => uint) OwnerID ;

    constructor (address[] memory _ArrayOwners, uint _ApprovalRequired) {
        ApprovalRequired = _ApprovalRequired;
        ArrayOwners= _ArrayOwners;
        for (uint i=0;i<ArrayOwners.length;i++) {
            OwnerID[ArrayOwners[i]] = i+1 ;
        } 
    }

    event TransferRequested (uint TransferID,address RequestedBy, address TransferTo, uint amount);
    event TransferDone (uint AmountTransferred, address indexed SentTo) ;
    event DepositDone (address indexed Depositer, uint AmountDeposited);


    struct TransferRequest {
        uint TransferID ;
        address initiator ;
        address TransferTo;
        uint Amount;
        bool TransferredSuccessfully;
    }

    TransferRequest[] public ListTransferRequests ; // public so that anyone can look for a transferID and see the request

      modifier OnlyOwner {
         require (IsOwner() == true);
         _;
    }

    function IsOwner() public view returns (bool){ 
          for (uint i=0 ; i < ArrayOwners.length ; i++) {
              if (ArrayOwners[i] == msg.sender) { 
                  return true ; 
                  } 
          }
          return false;
    }

    mapping (uint => mapping (address=> bool)) ApprovalMapping ; // we provide the Transaction ID first then the owner address


    function CreateTransferRequest (uint amountToTransfer, address recipient) public OnlyOwner {
        require( balance[recipient] >= amountToTransfer, "This account doesn't have enough balance for that transfer" ); // we check it before pushing the list AND after getting the approval (in the transfer function) as a double check
        ListTransferRequests.push(TransferRequest(ListTransferRequests.length,msg.sender,recipient,amountToTransfer,false));
         ApprovalMapping[ListTransferRequests.length-1][msg.sender] = true; 
         emit TransferRequested(ListTransferRequests.length-1,msg.sender,recipient,amountToTransfer);
    }

    function AcceptTransferRequest (uint TransferID)  public OnlyOwner {
        ApprovalMapping[TransferID][msg.sender] = true;
        if (CheckApprovals(TransferID) == true && ListTransferRequests[TransferID].TransferredSuccessfully== false ) {
           TransferTo(ListTransferRequests[TransferID].Amount,ListTransferRequests[TransferID].TransferTo,TransferID ); //make transfer
        }
    }

    function CheckApprovals (uint TransferID) view public returns (bool){
        uint Approvals = 0 ;
        for (uint i=0 ; i < ArrayOwners.length ; i++) {
            if (ApprovalMapping[TransferID][ArrayOwners[i]] == true) {Approvals += 1;}
        }
        if (Approvals >= ApprovalRequired) {return (true);}
        return (false);
    }

    mapping (address => uint) balance ;

    function MakeADeposit () public payable returns (uint) {
         balance[msg.sender] = balance[msg.sender] +msg.value;
         emit DepositDone (msg.sender, msg.value);
        return balance[msg.sender] ;
    }
    
    function TransferTo (uint amountToTransfer, address recipient, uint TransferID) private returns(uint, address) {
        require( balance[recipient] >= amountToTransfer);
        uint previousBalance = balance[recipient];
        balance[recipient] -= amountToTransfer;
        payable(recipient).transfer(amountToTransfer);
        // dire que c'est sent pour pas qu'on puisse le faire à double
        ListTransferRequests[TransferID].TransferredSuccessfully = true ;
        emit TransferDone (amountToTransfer, recipient) ;
        assert(balance[recipient] == previousBalance - amountToTransfer);
        return (amountToTransfer, recipient) ;
    }

// extra helpful function
    function GetBalance(address BalanceFrom) public view returns (uint) {
        return balance[BalanceFrom] ;
    }

    function GetListOwners() public view returns ( uint, address[] memory){
           return (ArrayOwners.length, ArrayOwners);
    }
    function GetOwnerID(address ToTest) public view returns(uint IfZeroNotOwner) {
        return OwnerID[ToTest];
    }

}
1 Like

I’ve made changes in my previous solution (it was removed to prevent noise) for removing the redundant balance map within MultisigWallet contract and added the possibility to init the source wallet owner within MultisigWallet constructor.

Ownable.sol

// Ownable.sol
pragma solidity 0.8.13;

contract Ownable {
    mapping(address => bool) owners;

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

    event ownerAdded(address newOwnerId);

    function addOwner(address _newOwner) public onlyOwners {
        owners[_newOwner] = true;
        emit ownerAdded(_newOwner);
    }
}

MultisigWallet.sol

// MultisigWallet.sol
pragma solidity 0.8.13;

import "./Ownable.sol";

contract MultisigWallet is Ownable {
    struct Transfer {
        address from;
        address payable to;
        uint amount;
        mapping(address => bool) approvals;
        uint approvalsCounter;
        uint id;
    }
    
    Transfer[] transfers;
    uint approvalLimit;

    event depositDone(address sender, uint amount, uint resultBalance);
    event approvalLimitChanged(uint newApprovalLimit, address changedBy);
    event transferInitialized(address from, address to, uint amount, uint createdTransferId);
    event transferApproved(uint transferId, uint approvalsCount, uint currentApprovalLimit);
    event transferFinished(address from, address to, uint amount);

    constructor(address _sourceOwner) {
        owners[_sourceOwner] = true;
    }

    function deposit() public payable {
        emit depositDone(msg.sender, msg.value, address(this).balance);
    }
    
    function setApprovalLimit(uint _approvalLimit) public onlyOwners {
        approvalLimit = _approvalLimit;
        emit approvalLimitChanged(approvalLimit, msg.sender);
    }

    function initTransfer(address payable _to, uint _amount) public onlyOwners {
        require(address(this).balance >= _amount);

        Transfer storage newTransfer = _createTransfer(_to, _amount);
        emit transferInitialized(msg.sender, _to, _amount, newTransfer.id);
        if (approvalLimit == 0) {
            _finishTransfer(msg.sender, _to, _amount);
        }
    }

    function approveTransfer(uint _id) public onlyOwners {
        Transfer storage selectedTransfer = transfers[_id];
        require(selectedTransfer.approvalsCounter <= approvalLimit, "Required approvals already received.");
        require(selectedTransfer.approvals[msg.sender] != true, "Approve must be unique.");

        selectedTransfer.approvals[msg.sender] = true;
        selectedTransfer.approvalsCounter++;
        emit transferApproved(_id, selectedTransfer.approvalsCounter, approvalLimit);
        if (selectedTransfer.approvalsCounter >= approvalLimit) {
            _finishTransfer(selectedTransfer.from, selectedTransfer.to, selectedTransfer.amount);
        }
    }

    function _finishTransfer(address _from, address payable _to, uint _amount) private {
        require(address(this).balance >= _amount);
        // The transfer solution used because of: https://ethereum.stackexchange.com/a/19343
        (bool success, ) = _to.call{value:_amount}("");
        assert(success);

        emit transferFinished(_from, _to, _amount);     
    }

    // This function is requred because the Transfer struct contains mapping
    // and there are no possibility to construct it with empty mapping in a Transfer(args...) way
    function _createTransfer(address payable _to, uint _amount) private returns (Transfer storage) {
        Transfer storage newTransfer = transfers.push();
    
        newTransfer.from = msg.sender;
        newTransfer.to = _to;
        newTransfer.amount = _amount;
        newTransfer.id = transfers.length - 1;

        return newTransfer;
    }
}
1 Like
// SPDX-License-Identifier: MIT

pragma solidity 0.8.13;

// A multi sig wallet smart contract
// A use case might be a charity where anyone can send in funds, anyone can request funds and
// the charity trustees(referenced as owners in the smart contract) can approve or reject
// the request for funds
// This smart contract is configured for three(3) trustess with two(2) yay or nay votes
// required by the trustees to either approve or reject the request for funds
// A trustee can change their vote before the request for funds is either approved or rejected
// Once a request for funds is either approved or rejected it is marked as closed and
// voting will be closed for that particular 'transfer of funds' request

contract MultiSigWallet {
    struct TransferRequests {
        string transferReason;
        address transferTo;
        uint transferAmount;
        bool transferApproved;
        bool transferRejected;
        bool transferRequestClosed;
        mapping(uint => bytes1) vote;
    }

    mapping(uint => TransferRequests) public transferRequests;

    address[3] public owners;
    uint public approvalsRequired;
    uint private id;

    event DepositReceived(address indexed _depositor, uint _depositAmount);
    event TransferRequestReceived(
        string indexed _transferReason,
        address indexed _transferTo,
        uint _transferAmount,
        uint _assignedID
    );
    event Transferred(
        string indexed _transferReason,
        address indexed _transferTo,
        uint _transferAmount
    );
    event TransferRejected(
        string indexed _transferReason,
        address indexed _transferTo,
        uint _transferAmount
    );

    constructor(address[3] memory _addresses, uint _approvalsRequired) {
        owners = _addresses;
        approvalsRequired = _approvalsRequired;
    }

    function deposit() external payable {
        emit DepositReceived(msg.sender, msg.value);
    }

    function checkContractBalance() external view returns (uint _balance) {
        _balance = address(this).balance;
    }

    function createTransferRequest(
        string memory _transferReason,
        address _transferTo,
        uint _transferAmount
    ) public {
        TransferRequests storage newTransferRequest = transferRequests[id];
        uint _assignedID = id;
        id++;

        newTransferRequest.transferReason = _transferReason;
        newTransferRequest.transferTo = _transferTo;
        newTransferRequest.transferAmount = _transferAmount;

        emit TransferRequestReceived(
            _transferReason,
            _transferTo,
            _transferAmount,
            _assignedID
        );
    }

    function vote(uint _id, bool approve) external {
        uint voterIndex;
        bool _isInOwnerList;
        uint yesVotes;
        uint noVotes;

        for (uint i = 0; i < 3; i++) {
            if (msg.sender == owners[i]) {
                _isInOwnerList = true;
                voterIndex = i;
            }
        }

        require(_isInOwnerList, "Only owners can perform this action");
        require(
            keccak256(bytes(transferRequests[_id].transferReason)) !=
                keccak256(bytes("")),
            "The transfer request was not found for the provided ID"
        );
        require(
            transferRequests[_id].transferRequestClosed != true,
            "The transfer request is closed"
        );

        if (approve) {
            transferRequests[_id].vote[voterIndex] = "Y";
        } else {
            transferRequests[_id].vote[voterIndex] = "N";
        }

        for (uint i = 0; i < 3; i++) {
            if (transferRequests[_id].vote[i] == "Y") {
                yesVotes++;
            }
            if (transferRequests[_id].vote[i] == "N") {
                noVotes++;
            }
        }

        if (yesVotes == approvalsRequired) {
            transferRequests[_id].transferApproved = true;
            transferRequests[_id].transferRequestClosed = true;
            (bool success, ) = transferRequests[_id].transferTo.call{
                value: transferRequests[_id].transferAmount
            }("");
            require(success, "Transfer failed");
            emit Transferred(
                transferRequests[_id].transferReason,
                transferRequests[_id].transferTo,
                transferRequests[_id].transferAmount
            );
        } else if ((noVotes == approvalsRequired)) {
            transferRequests[_id].transferRejected = true;
            transferRequests[_id].transferRequestClosed = true;
            emit TransferRejected(
                transferRequests[_id].transferReason,
                transferRequests[_id].transferTo,
                transferRequests[_id].transferAmount
            );
        }
    }
}
1 Like

Help me to fix the error below!

My code was successfully compiled, but I got an error below during the deployment.

Error: expected array value (argument=null, value="", code=INVALID_ARGUMENT, version=abi/5.5.0)

This is my wallet code. I created a list of request (each request stores the amount, recipient, and list of voters). If the owner signs, the address would be added to the list of voters. The length of list means the number of approvals. Transaction will be made once the approval number is the same as the initial threshold.

pragma solidity 0.7.5;
pragma abicoder v2;

contract Wallet {
    // minimum number of approvals to make a transaction
    uint threshold;

    // return true if the owner address is saved as a key in the dict
    mapping(address => bool) isOwnerDict;

    struct Request {
        address payable recipient;
        address[] voters;
        uint amount;
    }
    Request[] requestLog;

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

    modifier stopSecondVote(uint _id) {
        bool isSecondVote = false;
        for (uint i=0; i<requestLog[_id].voters.length; i++) {
            if (requestLog[_id].voters[i] == msg.sender) {
                isSecondVote = true;
            }
        }
        require(isSecondVote = false);
        _;
    }

    // setup the initial values (owners and minimum number of approvals)
    constructor(address[] memory _ownerList, uint _threshold) {
        threshold = _threshold;
        for (uint i=0; i<_ownerList.length; i++) {
            isOwnerDict[_ownerList[i]] = true;
        }
    }

    // create a request 
    function createRequest(address payable _recipient, uint _amount) public onlyOwners {
        address[] memory emptyVoterList;
        requestLog.push(
            Request(_recipient, emptyVoterList, _amount)
        );
    }

    // approve a request and transfer the amount
    function approve(uint _id) public onlyOwners stopSecondVote(_id) {
        addVoter(_id);
        transfer(_id);
    }

    // add the owner address in the voter list
    function addVoter(uint _id) private {
        requestLog[_id].voters.push(msg.sender);
    }

    // transfer the requested asset once 
    function transfer(uint _id) private {
        require(requestLog[_id].voters.length == threshold);
        requestLog[_id].recipient.transfer(requestLog[_id].amount);
    }
}
1 Like

where does the error throw. when calling what fuctiom? im going to assume ots from your constrictot. you need to pass in an array of addresses and a threshold value before u deploy it
in remix you will see an option to input params beside the deploy tab. this is where yoy do it. arg1 should be an array of owners and arg2 should be the threshold number

1 Like

Thanks a lot. My code was working!! Just my deployment approach was wrong lol

1 Like
pragma solidity 0.8.7;
pragma abicoder v2;

contract Wallet {

    mapping(address => bool) isOwner;
    mapping(address => uint256) balance;

    uint256 approvalsNeeded;

    struct Transfer {
        uint256 transferID;
        address from;
        address to;
        uint256 transferAmount;
        address[] approvalList;
        bool approvalStatus;
    }

    Transfer[] transferList;

    event TransferSuccessful(address _from, address _to, uint256 _amount, string _message);

    constructor(
        address owner1,
        address owner2,
        address owner3,
        uint256 requiredApprovals
    ) {
        isOwner[owner1] = true;
        isOwner[owner2] = true;
        isOwner[owner3] = true;
        approvalsNeeded = requiredApprovals;
    }

    modifier onlyOwner {
        require(isOwner[msg.sender] == true, "Only an Owner can run this function!");
        _;
    }

    function checkOwner(address _addrs) public view returns (bool) {
        return isOwner[_addrs];
    }
    
    // parameter: amount to deposit
    // returns: current total amount after deposit
    function deposit() payable public {
        balance[msg.sender] += msg.value;
    }

    // balance can only be viewed by the balance holder
    function getBalance() public view returns (uint256) {
        return balance[msg.sender];
    }

    // require: owned amount to be >= amount to transfer
    function createTransfer(address _receiver, uint256 _amount) public {
        require(balance[msg.sender] >= _amount, "You cannot transfer more than what you have!");
        require(msg.sender != _receiver, "You cannot transfer to yourself!");

        uint256 id = transferList.length + 1;
        address[] memory newArray;

        Transfer memory newTransfer = Transfer(id, msg.sender, _receiver, _amount, newArray, false);
        transferList.push(newTransfer);
    }

    function seeTransfer(uint256 _index) public view onlyOwner returns (Transfer memory) {
        return transferList[_index];
    }

    // the actual transfer happens here when the approval == 2
    // then: deduct amount from sender, assert getBalance-amount, transfer
    // then: remove from transferList, move to transferApproved
    function approveTransfer(uint256 _index) public onlyOwner returns (string memory message) {
        require(transferList[_index].approvalStatus == false, "Transaction was already successful. No approvals needed.");

        uint256 approveLength = transferList[_index].approvalList.length;
        bool ownerAlreadyApproved = false;

        if (approveLength > 0) {
            for (uint i = 0; i < approveLength; i++) {
                if (transferList[_index].approvalList[i] == msg.sender) {
                    ownerAlreadyApproved = true;
                    break;
                }
            }
            require(ownerAlreadyApproved == false, "You have approved this transaction before, you can only approve once per transaction.");
            if (!ownerAlreadyApproved) {
                transferList[_index].approvalList.push(msg.sender);
            }

            if (transferList[_index].approvalList.length == approvalsNeeded) {

                address currentSender = transferList[_index].from;
                address currentReceiver = transferList[_index].to;

                uint256 currentSenderBalance = balance[transferList[_index].from];
                uint256 amountToTransfer = transferList[_index].transferAmount;

                require(currentSenderBalance >= amountToTransfer, "This sender's balance is insufficient.");

                _transfer(currentSender, currentReceiver, amountToTransfer);
                transferList[_index].approvalStatus = true;

                emit TransferSuccessful(currentSender, currentReceiver, amountToTransfer, "Transfer successful!");
                assert(balance[currentSender] == currentSenderBalance - amountToTransfer);

                message = "Transaction successful.";
                return message;
            }
            
        } else {
            transferList[_index].approvalList.push(msg.sender);
            message = "Your approval recorded.";
            return message;
        }

    }

    function _transfer(address _from, address _to, uint256 _amount) private {
        balance[_from] -= _amount;
        balance[_to] += _amount;
    }


}

Whew… tried to write 90% blind before checking… maybe 10% of that was usable. I’ll have to go through this course about 10 more times to really absorb everything, but wanted to at least try before looking it up.

The only meaningful change to the solution that I came up with that is (maybe) functional includes:

  • coding the constructor to include owner addresses in the actual code
  • omitting the “limit” variable in the constructor
  • specifying the “if” statement to >= 2, in place of the “limit” variable
pragma solidity 0.7.5;
pragma abicoder v2;

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

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

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

    Transfer[] transferRequests;

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

    modifier onlyOwners(){
        bool owner = false;
        for(uint i=0; i<owners.length;i++){
            if(owners[i] == msg.sender){
                owner = true;
            }
        }
        require(owner == true);
        _;
    }
   
    constructor (){
        address  owner;
        owner = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;
        owner = 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2;
        owner = 0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db;
        owner = msg.sender;

    }

    function deposit() public payable {}

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

    }
    
    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 >= 2){
            transferRequests[_id].hasBeenSent = true;
            transferRequests[_id].receiver.transfer(transferRequests[_id].amount);
            emit TransferApproved(_id);
        }
    }

    function getTransferRequests() public view returns (Transfer[] memory){
        return transferRequests;
    }


}
1 Like

how to fix this error

creation of Wallet errored: Error encoding arguments: Error: types/values length mismatch (count={“types”:2,“values”:5}, value={“types”:[“address[]”,“uint256”],“values”:["[","], [","], [","]",“2”]}, code=INVALID_ARGUMENT, version=abi/5.5.0)

1 Like

Are you typing in the correct constructor parameters when deploying the contract? It looks to me you are typing in empty arguments when your constructor takes 2 arguments

1 Like

when trying to deploy i input the 3 addresses in _owners using [""], and i set the _limit to 2 and i get this error everytime…

1 Like

can u share the code, hwoeevr if i was to guess you need to pass in an array of addresses into the constructor. however from the error it looks lik eyour passing in an array of arrays. so change your format of the arguments u pas into the constructor to be

["addr1", "addr2", "addr3"], 2

and not

[["addr1"], ["addr2"], ["addr3"], "2"]

if this does not work then paste your code and ill know staright away for u what the problem is

1 Like

hey @pappas_kellyn great for completing the assignment anyway and trying to do it on your own. i would not be discourage at all. this is a difficult and advanced assignment for a beginners course. solidity is very hard at the beginning i found this to be true myself when beginning also.

and yes see this is why u will do well. instead of feeling fed up and just moving on your motivated to go back and really go over things again, this is by far the harder option to convince yourself to do and its much easier to just move onto the course and convince yourself youll learn more there. so this is a brilliant decision.

if you ever have any questions im always on call so dont be shy to ping me in dm or to tag me in a post on the forums

1 Like

Work in progress so missing alot here. Dont understand whay I get the error on line 56

pragma solidity 0.8.11;

contract Multisig {

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

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

    event depositDone(uint amount, address depositedFrom);

    address payable Owner;
    address payable CoOwnerA;
    address payable CoOwnerB;

    externalTransfer[] transferRequests ;

    modifier onlyOwners(){
        require(msg.sender == Owner || msg.sender == CoOwnerA || msg.sender == CoOwnerB);
        _;
    }

    constructor () {
        Owner = payable(msg.sender);
        CoOwnerA = payable(0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2);
        CoOwnerB = payable(0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db);
        uint limit = 2; 
    }

    //Function that recives ETH into wallet
    function Deposit () public payable {
        
        emit depositDone(msg.value, msg.sender);     
    }

    //function to transfer ETH out of wallet to external adress
    function createExternalTransfer (uint _amount, address payable _receiver) public onlyOwners {
       require(address(this).balance >= _amount, "Insufficient funds");
       transferRequests.push(externalTransfer(_amount, _receiver, 0, false, transferRequests.length));
    }

    function Approve (uint _id) public onlyOwners {
        
    }

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

    function getTransferRequests () public view returns (externalTransfer[] memory) {
        return transferRequests[];
    }

}

1 Like

MultiOwnable.sol

pragma solidity 0.8.7;

contract MultiOwnable {

    //owner set
    mapping(address => bool) OwnerList;
    //storing for illustration only, else not needed for prod-env
    address public contractAddress;
  
    modifier onlyOwner {
        require(OwnerList[msg.sender] == true, "Only SC Owner can Call this");
        //run the real functions, Practically EVM will replace all of function statement here
        _; 
    }

    constructor(address owner1, address owner2, address owner3) {
        //at SC creation all owners are set in ownerlist 
        OwnerList[owner1] = true;
        OwnerList[owner2] = true;
        OwnerList[owner3] = true;
        //storing contract address
        contractAddress = address(this);
    }
    function getWalletBalance() public view returns(uint256){
        return contractAddress.balance;
    }
}

DestructMulti.sol

pragma solidity 0.8.7;

import "./MultiOwnable.sol";

contract DestructMulti is MultiOwnable {

    int8 private contractCount;

   

    constructor(address owner1, address owner2, address owner3) MultiOwnable(owner1,owner2,owner3) {

    }

    function destructContract() public onlyOwner {

        //uint256 _contractBalance = contractAddress.balance;

        //commenting below, since selfdestruct implictly does the transfer to owner

        //payable(owner).transfer(_contractBalance);

        selfdestruct(payable(msg.sender));

    }

}

MultiSigWallet.sol

pragma solidity 0.8.7;
pragma abicoder v2;

/*
    Here are the requirements of the smart contract wallet you will be building

    – 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. 
*/

//importing other contract
import "./DestructMulti.sol";


contract MultiSigWallet is DestructMulti {

    struct PendingTxn{
        address to;
        uint256 amount;
        uint8 approvalCount;
        address[3] approver;
        bool isPending;
    }


    //mapping(address => uint) balances;
    //Pending Transactions Array, Waiting for approval
    PendingTxn pendingTxnList;
    uint8 minApprover;
    
    event balanceUpdated(uint256 amount, uint256 Balance, address indexed from, address indexed to);

    modifier sufficientBalance(uint _amount){
        require(address(this).balance >= _amount,"SC Balance Not Sufficient");
        _;
    }

    constructor(address owner1, address owner2, address owner3,uint8 _approverCount) DestructMulti(owner1,owner2,owner3){
            
            //check for approverCount boundary conditions
            if(_approverCount <=3){
                minApprover = _approverCount;
            } else if(_approverCount >3)
            {
                minApprover = 3;
            } else {
                minApprover = 1;
            }

            initPendingTransaction();
    }
    
    function initPendingTransaction() private {
        pendingTxnList.to = address(0x0);
        pendingTxnList.amount = 0;
        pendingTxnList.approvalCount = 0;
        pendingTxnList.isPending = false;
        pendingTxnList.approver[0] = address(0x0);
        pendingTxnList.approver[1] = address(0x0);
        pendingTxnList.approver[2] = address(0x0);
    }

    // admin function can only be perfromed by owner of SC
    //payable allows to receive money from the caller of the function
    //the msg.value will get added to balance of the SC
    //the internal balance datastructure allows to keep track of who this money is owed to
    function deposit() public payable returns(uint256){
        
        //emit log 
        emit balanceUpdated(msg.value,address(this).balance, msg.sender, address(this));

        return address(this).balance;
    }
    
    //Create transfer Request to an Address. Registers the transaction details, wait for approval
    function transferRequest(address _recipient, uint _amount) public onlyOwner sufficientBalance(_amount) {

        //modifier onlyOwner- checks if its one of the owner
        //modifier suffieientBalance- check if balance is sufficient
        //check if recipient is not SC-Address, unnecessary operation will cost GAS
        require(address(this) != _recipient,"Transfer to Self-SC not Allowed");
        //check for Zero amount transfer
        require(_amount != 0,"Zero Amount Transfer. Ignoring");

        pendingTxnList.to = _recipient;
        pendingTxnList.amount = _amount;
        pendingTxnList.approvalCount = 0;
        pendingTxnList.isPending = true;
    }

    function getPendingTxn() public view onlyOwner returns(PendingTxn memory) {
            return pendingTxnList;        
    }

    //approve transfer, if approvalCount>=2, does transfer, reset pendingTransaction
    function approveTransfer() public onlyOwner {
        
        //check if there is a pending transfer
        require(pendingTxnList.isPending != false,"No Pending Transaction for Approval");
        
        //check, same user shouldnt be approving again
        if(msg.sender != pendingTxnList.approver[0] && msg.sender != pendingTxnList.approver[1] && msg.sender != pendingTxnList.approver[2]){
            
            //increment approved count
            pendingTxnList.approvalCount++;

            if(pendingTxnList.approvalCount++ >= minApprover) {
                //confirm transfer
                _transfer(pendingTxnList.to,pendingTxnList.amount);
                //emit log
                emit balanceUpdated(pendingTxnList.amount, address(this).balance, address(this), pendingTxnList.to);
                //init pending transaction
                initPendingTransaction();
            }
            else {
                pendingTxnList.approver[pendingTxnList.approvalCount- uint8(1)] = msg.sender;
            }
        }
        else {
            //check if there is a pending transfer
            require(false,"Same User Can't Approve Again");
            
        }
    }
    
    //function to cancel pendingTransaction
    function cancelPendingTxn() public {
        initPendingTransaction();
    }

    //private function, can be resused by others fn
    function _transfer(address _to, uint _amount) private {
        //confirm transfer
            payable(_to).transfer(_amount);
    }
    
}

I saw the assignment assistance video and one aspect i think i have not covered is multiple pending transaction. May be i will reimplement and share again.

1 Like

You are trying to return the entire array, so there is no need to specify the index of it.

Your function should work in this way:

    function getTransferRequests () public view returns (externalTransfer[] memory) {
        return transferRequests;
    }

Carlos Z

1 Like

Ahh, that fixed it right up. Thank you.

1 Like

Hey guys! Here’s my solution for the Multisig Wallet Smart contract. All feedback is more than welcome :slight_smile: Cheers!

pragma solidity ^0.7.5;
pragma abicoder v2;


contract MultisigWallet {

    mapping(address => bool) owners;
    mapping(address => mapping(uint => bool)) signedTransfers;
    uint numberOfApprovals;

    
    event FundsDeposited(address _from, uint _amount);

    Transfer[] public submittedTransfers;

    struct Transfer {
        address to;
        uint256 amount;
        uint confirmations;
        bool processed;
    }

    modifier onlyOwners {
        require(owners[msg.sender], "Not an owner of the contract");
        _;
    }

    constructor(uint _numberOfApprovals, address[] memory _owners) {
        for (uint i = 0; i < _owners.length ; i++) {
            owners[_owners[i]] = true;
        }
        numberOfApprovals = _numberOfApprovals;
    }


    function deposit() public payable {
        emit FundsDeposited(msg.sender, msg.value);
    }


    function getBalance() public view returns(uint256) {
        return address(this).balance;
    }

    function createTransfer(address _to, uint256 _amount) public onlyOwners {
        address[] memory _confirmedBy = new address[](3);
        submittedTransfers.push( Transfer(_to, _amount, 1, false));
        signedTransfers[msg.sender][submittedTransfers.length -1] = true;
    }


    function signTransfer(uint _idx) public onlyOwners {

        Transfer storage submittedTransfer = submittedTransfers[_idx];

        require(!signedTransfers[msg.sender][_idx], "This owner already signed the transfer");

        submittedTransfer.confirmations += 1;
        signedTransfers[msg.sender][_idx] = true;

        if(submittedTransfer.confirmations >= numberOfApprovals){
            require(submittedTransfer.processed==false, "Unable to process an already processed transfer");
            _processTransfer(submittedTransfer);
        }
    }

    function _processTransfer(Transfer storage _transferToProcess) private {
        address payable _recipient = payable(_transferToProcess.to);
        uint256 _amount = _transferToProcess.amount; 
        require(_amount <= address(this).balance, "Contract does not have enought funds to process transaction");
        _recipient.transfer(_amount);
        _transferToProcess.processed = true;
    }

}
1 Like

Work in progress so missing alot here. Dont understand whay I get the error on line 55: Undeclared identifier

pragma solidity 0.8.11;

contract Multisig {

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

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

    event depositDone(uint amount, address depositedFrom);

    address payable Owner;
    address payable CoOwnerA;
    address payable CoOwnerB;

    externalTransfer[] transferRequests;

    modifier onlyOwners(){
        require(msg.sender == Owner || msg.sender == CoOwnerA || msg.sender == CoOwnerB);
        _;
    }

    constructor () {
        Owner = payable(msg.sender);
        CoOwnerA = payable(0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2);
        CoOwnerB = payable(0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db);
        uint limit = 2; 
    }

    //Function that recives ETH into wallet
    function Deposit () public payable {
        
        emit depositDone(msg.value, msg.sender);     
    }

    //function to transfer ETH out of wallet to external adress
    function createExternalTransfer (uint _amount, address payable _receiver) public onlyOwners {
       require(address(this).balance >= _amount, "Insufficient funds");
       transferRequests.push(externalTransfer (_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++;

        if(transferRequests[_id].approvals >= limit){
            transferRequests[_id].hasBeenSent = true;
            transferRequests[_id].receiver.transfer(transferRequests[_id].amount);}

    }

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

    function getTransferRequests () public view returns (externalTransfer[] memory) {
        return transferRequests;
    }

}