Project - Multisig Wallet

Differences between mine and Filip’s:

  • -I created a “signers” mapping from addresses to bool. This moves the loop over the input signer address array from the modifier which runs constantly to the constructor that runs once.
  • -I thought it was safe to presume signers creating transfer requests intend them to be signed by themselves immediately.
  • -Added a “dissaprove()” function for signers to change their mind with (before its too late).
  • -Added a check vs. the wallet’s balance upon creating or approving transfer requests. You should never be able to request more ether be transfered than is in the wallet. And if the wallet balance drops via another transfer all existing yet unsent transfers larger than the current wallet ballance cannot be approved. Funds can be added via deposit(), or the default recieve().
  • -Added getLimit() and getBalance() hooks for chai/waffle testing.
  • -Running 0.8.0.
pragma solidity 0.8.0;
pragma abicoder v2;
import "hardhat/console.sol";
import "./inherited/Ownable.sol";

// SPDX-License-Identifier: UNLICENSED

// Multi-Signature Wallet
// For Ivan on Tech Academy
// Solidity 101 Project
// Solution by Bob Clark

contract Wallet {

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

    event RqstCreated(uint _id, uint _amount, address _initiator, address _receiver);
    event RqstApprvd(uint _id, uint _approvals, address _approver);
    event RqstUnApprvd(uint _id, uint _approvals, address _approver);
    event RqstSent(uint _id);
    
    //Should only allow people in the signers list to continue the execution.
    modifier onlySigners(){
        require(signers[msg.sender] = true, "Not a signer.");
        _;
    }
    //Should initialize the signers list and the limit 
    constructor(address[] memory _signers, uint _limit) {
        for (uint i=0; i<_signers.length; i++) {
            signers[_signers[i]] = true;
        }
        limit = _limit;
    }
    
    //Empty function
    function deposit() public payable {}
    
    //Create an instance of the Transfer struct and add it to the transferRequests array
    function createTransfer(uint _amount, address payable _receiver) public onlySigners {
        require(_amount <= address(this).balance,"Insufficient Funds.");
        uint id = transferRequests.length;
        transferRequests.push(Transfer(_amount, _receiver, 1, false, id));
        approvals[msg.sender][id] = true;
        emit RqstCreated(transferRequests.length, _amount, msg.sender, _receiver);
    }
    
    //Set your approval for one of the transfer requests.
    //Need to update the Transfer object.
    //Need to update the mapping to record the approval for the msg.sender.
    //When the amount of approvals for a transfer has reached the limit, this function should send the transfer to the recipient.
    //An owner should not be able to vote twice.
    //An owner should not be able to vote on a tranfer request that has already been sent.
    function approve(uint _id) public onlySigners {
        require(approvals[msg.sender][_id] == false,"Already approved.");
        require(transferRequests[_id].hasBeenSent == false, "Already Sent.");
        require(transferRequests[_id].amount <= address(this).balance, "Insufficient Funds.");
        approvals[msg.sender][_id] = true;
        transferRequests[_id].approvals++;
        emit RqstApprvd(_id, transferRequests[_id].approvals, msg.sender);

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

    // Undo!
    function disapprove(uint _id) public onlySigners {
        require(approvals[msg.sender][_id] == true,"Already unapproved.");
        require(transferRequests[_id].hasBeenSent == false, "Already Sent.");
        approvals[msg.sender][_id] = false;
        transferRequests[_id].approvals--;
        emit RqstUnApprvd(_id, transferRequests[_id].approvals, msg.sender);
    }
    
    //Should return all transfer requests
    function getTransferRequests() public view returns (Transfer[] memory){
         return transferRequests;
    } 
    
    // Testing hook.  
    // Note: signer addresses cannot be recovered from mapping
    function getLimit() public view returns (uint) {
        return limit;
    }

    // Testing hook.
    function getBalance() public view returns(uint) {
        return address(this).balance;
    }
}
1 Like

Hey @KZF, hope you are ok.

I tested your contract, its looks very good by now.

Steps I made:

  • deploy the contract with 3 owners, _NUMBEROFAUTHORIZATIONS = 2.
  • viewapprovers() to validate owners.
  • deposit() 3 ethers from 1st owner.
  • Viewbalance() to check balance of 1st owner.
  • Initiatetransfer() 2.5 ether from 1st owner to 2nd owner.
  • Viewpendingtransfers() returns an array of pending transfer.
  • Confirmtransfer() with 1st owner (the one who create the tx requrest).
  • viewapprovals() id = 0, check that 1st owner has approve the tx.
  • Confirmtransfer() with 2nd owner
  • Viewbalance() 2nd owner, it have 2.5 ethers.

Overall you did a very great job, nice solution!

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

Carlos Z.

Hey @marsrvr, hope you are great!

I’m testing your contract, its looks veeeery great but you miss one little (and big) issue :nerd_face:

    modifier onlySigners(){
        require(signers[msg.sender] = true, "Not a signer.");
        _;
    }

I think is pretty obvious for you, I was able to create a transfer from one of the owners, but I approve it with an account which should be able to do anything (cuz is not a signer).

Overall Im very impress, I could suggest you the new property indexed for some address in your events, which later will help you track each event that an address has made, like having an indexded log about all events made from an user :face_with_monocle:

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

Carlos Z.

Preformatted textSo I want to make it simpler first, but understand how do it later the other way. But first way I need a value:

  • address owner; then
  • an address[] public owners;

The owner set in the constructor is the msg.sender. This is the current address used in the compiler - 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4.

I write a function addOwners {… to add the addresses into the array, I specify: I’ll post what I’ve written so far:

pragma solidity 0.7.5.;
pragma abicoder V2;

contract Multisig {

address owner;
address[] public owners;
uint limit;

struct Transfer {
address from;
address to;
uint amount;
uint txId;
}

Transfer[] transferRequests;

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

constructor() {
owner = msg.sender;
}

function addOwners(address) public OnlyOwner {
address firstAddress = msg.sender;
address secondAddress = 0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db;
address thirdAddress = 0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB;
owners.push(firstAddress);
owners.push(secondAddress);
owners.push(thirdAddress);
}

function addTransfers(address _from, address _to, uint _amount) public {
Transfer NewTransfer = Transfer(_from, _to, _amount, transferRequests.length);
transferRequests.push(NewTransfer);
}

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

1 Like

Your going in the right direction :nerd_face:

Now I could suggest you something:

function addOwners(address) public OnlyOwner {
address firstAddress = msg.sender;
address secondAddress = 0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db;
address thirdAddress = 0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB;
owners.push(firstAddress);
owners.push(secondAddress);
owners.push(thirdAddress);
}

Since you just need to save you time while coding, you can just set the following in your constructor, that way every time you initialize your contract, your owners deploys automatically.

owners.push(msg.sender);
owners.push("0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db");
owners.push("0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB");

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

Carlos Z.

1 Like

You can use the “Preformatted Text” Button to encapsulate any kind of code you want to show.


function formatText(){

let words = “I’m a preformatted Text box, Please use me wisely!”

}

prefromatted_text-animated

Carlos Z.

here’s my attempt at the assignment:

pragma solidity 0.7.5;
pragma abicoder v2;

contract MultiSigWallet {
    mapping(address=>bool) owners;
    uint numApprovalsRequiredForTransfer;
    struct TransferRequest {
        address destination;
        uint amount;
        uint txId;
        uint numApprovals;
        bool waitingApproval;
    }
    mapping(uint=>TransferRequest) transferRequests;
    mapping(uint=>mapping(address=>bool)) transferRequestsApproved;
    uint transferId;
    
    event transferRequested(address by, address destination, uint amount, uint txId);
    event transferApproved(address by, uint txId);
    event transferred(address by, address destination, uint amount, uint txId);
    
    modifier onlyOwners {
        require(owners[msg.sender] == true);
        _; // runs the function
    }
    
    constructor(address[] memory _owners, uint _numApprovalsRequiredForTransfer) {
        for (uint i = 0; i < _owners.length; i++) {
            owners[_owners[i]] = true;
        }
        numApprovalsRequiredForTransfer = _numApprovalsRequiredForTransfer;
    }
    
    function deposit() public payable returns(uint) {
        return address(this).balance;
    }
    
    function createTransferRequest(address _destination, uint _amount) public onlyOwners returns(TransferRequest memory) {
        require(address(this).balance >= _amount, "Insufficient funds");
        TransferRequest memory tr = TransferRequest(_destination, _amount, transferId++, 0, true);
        transferRequests[tr.txId] = tr;
        emit transferRequested(msg.sender, tr.destination, tr.amount, tr.txId);
        return tr;
    }
    
    function approveTransferRequest(uint _transferId) public onlyOwners returns(TransferRequest memory) {
        TransferRequest storage tr = transferRequests[_transferId];
        if (tr.waitingApproval)
        {
            if (transferRequestsApproved[tr.txId][msg.sender] == false) {
                transferRequestsApproved[tr.txId][msg.sender] = true;
                tr.numApprovals++;
                emit transferApproved(msg.sender, tr.txId);
            }
            if (tr.numApprovals >= numApprovalsRequiredForTransfer) {
                require(address(this).balance >= tr.amount, "Insufficient funds");
                tr.waitingApproval = false;
                payable(tr.destination).transfer(tr.amount);
                emit transferred(msg.sender, tr.destination, tr.amount, tr.txId);
            }
        }
        return tr;
    }
}
1 Like

Thanks Carlos,

I appreciate your input.

  1. I need to limit the number of owners to three.
    2)For the second comment the reasoning is that any two “true” gives “true” and all three give “true” to approved function… And if “true” then the require should be if approved == “true”, then that is the condition for authorization and perform the transfer.
    “If I add the 3 first elements of the approval array has (true, true, true), this function will always return true on any of the conditions.” yes three “true” should mean approval on any condition and also any two true should give approval on any condition. I don’t see the problem with that. Except this only works if I can limit the owners to three in the array. If more owners are added only the first three are considered for the approval under the authorization function. Does this make sense to you?

  2. Add the transferred balance to the receiver. Yes I forgot to add transfer balance to the receiver.

Anyway I am going to look at the video and do it using the double mapping but I wanted to see how I could do it without any help first.

Thanks Carlos.

1 Like

Hey thecil, thanks for running my contract! I have a few questions, especially about the array that shows the “pending transfers”. While coding, I searched a little bit about how to filter an array, and just get back a subset of data. This is really easy in python( with “pandas” for example), but here it seems to be not so easy. Is that correct, simply that Solidity focusses on something else? How would you solve, showing the owner the subset of transfers that is still pending, or for instance that has already been approoved? Without using lots of Ifs and Fors and reconstructing new arrays… Thanks!!

1 Like

This is my first try, without peeking at the other two videos. Maybe it doesn’t work, but I wanted to at least try for myself. Pls give feedback on what’s wrong.

pragma solidity 0.7.5;
pragma abicoder v2;

contract MSWallet {
    
    address[] private owners;
    uint private approvalLimit;
    //Transfer request objects
    struct TransferRequest {
        address recipient;
        uint amount;
        uint approveCount;
    }
    
    //Array of transfer objects
    TransferRequest[] transferRequests;
    
    constructor(address[] memory _owners, uint _approvalLimit) {
        owners = _owners;
        approvalLimit = _approvalLimit;
    }
    
    function deposit() public payable {
        //This actually works. Since function is payable, msg.value will be added to address(this).balance.
    }
    
    //The transfer function
    function transfer(address payable _address, uint _amount, uint _index) public {
        require(address(this).balance >= _amount);
        require(msg.sender != _address);
        require(transferRequests[_index].approveCount >= approvalLimit);
        _address.transfer(_amount);
    }
    
    //Creates a transfer object and adds it to the array of transfer objects
    function createTransferRequest(address _owner, address _recipient, uint _amount) private {
        if(checkOwner(_owner)) {
            TransferRequest memory newTransferRequest = TransferRequest(_recipient, _amount, 0);
            transferRequests.push(newTransferRequest);
        }
    }
    
    //Approves a transfer request
    function approveRequest(uint _index, address _owner) public {
        if (transferRequests.length != 0) {
            if (checkOwner(_owner)) {
                transferRequests[_index].approveCount += 1;
            } 
        }
    }
    
    //Helper function to check if the address creating a request is an owner
    function checkOwner(address _address) private view returns(bool _bool) {
        for (uint i=0; i<owners.length; i++) {
            if (owners[i] == _address) {
                return true;
            } else {
                return false;
            }
        }
    }
}
1 Like

Im sure Filip mention it on one of the videos, instead of using Arrays or a function to iterate an Array and get the results, you can use events instead.

I mean, instead of using Arrays, i would use mappings instead, because its the most powerful tool in solidity to track key=>values, also is cheaper, so for example, a double mapping can almost do the same than dimensional array.

Then you should use events, they will help you track which account has done what, each time you create a new transferRequest for example, you can emit an event that will be used to log the data, because all this data will be stored in the blockchain, you just need a way to track each time someone use a function from your contract, events help us solve the tracking issue.

Carlos Z

Hey @LaszloD, hope you are well.

I tried to run your contract, I was able to deposit, I created a transfer and the I approveRequest many times with the 1st owner, but the funds were never send to the address I specified.

    //Approves a transfer request
    function approveRequest(uint _index, address _owner) public {
        if (transferRequests.length != 0) {
            if (checkOwner(_owner)) {
                transferRequests[_index].approveCount += 1;
            } 
        }
    }

You should change the name of this function, because it can be confusing when it reach to the last step _address.transfer(_amount);.

    //The transfer function
    function transfer(address payable _address, uint _amount, uint _index) public {
        require(address(this).balance >= _amount);
        require(msg.sender != _address);
        require(transferRequests[_index].approveCount >= approvalLimit);
        _address.transfer(_amount);
    }

This one is private, so it cant be accessed through a user.

    //Creates a transfer object and adds it to the array of transfer objects
    function createTransferRequest(address _owner, address _recipient, uint _amount) private {
        if(checkOwner(_owner)) {
            TransferRequest memory newTransferRequest = TransferRequest(_recipient, _amount, 0);
            transferRequests.push(newTransferRequest);
        }
    }

You almost have it, just need to improve some few points and your almost done!

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

Carlos Z.

Hello,
I am having some trouble on this project as I have been stuck on it for a while,
I have only watched up to the first double mapping video and I’m unsure where to go from here,
I would like to get some hints on how to do the approvals or assign the transactions transaction numbers,
here is what I have so far:

pragma solidity 0.7.5;

contract multisigwallet{

uint limit;

uint i;

int[] numbers;
//maybe do transfer logging like this?

 mapping (address => mapping(uint => bool)) approvals;
 //going to need to use this to set numbers to a transfer and number of approvals
 mapping(address => uint) balance;
 //easy way to call Balance
 address owner;
//sets the owner 


 event depositD(uint amount, address indexed depositedTo);

//need for deposit function

struct transnum {
    uint transferNum;
    uint approvals;
    //make a struct for approvals amount and transfer number
}
         function deposit() public payable returns(uint) {
    balance[msg.sender] += msg.value;
    emit depositD(msg.value, msg.sender);
    return balance[msg.sender];
    // people can deposit in the wallet
     }
     
   function approve(uint _approvals)  public returns(bool){
       //going to try to make approvals a number per trans number
   } 
    
        
            
    
    
   function transfer(address recipient, uint amount, int _number) public payable {
    numbers.push(_number); 
    //maybe get the transaction a number each time one is requested, but how do i get the transaction to pend instead of instantly revert 
    require(balance[msg.sender] >= amount, "Balance not sufficient");
    require(msg.sender != recipient, "dont tranfer money to urself");
    //transfer function need to add require approvals

   }
   
   function getNumbers() public view returns(int[] memory){
       return numbers;
   }
            
    //this is how i will call the transaction number
    
    function _transfer(address from, address to, uint amount) private{
        balance [from] -= amount;
        balance [to] +=amount;
        // general transfer rule
    }
        
    }
1 Like

Hey @Bogdan_Manzar, hope you are ok.

Your contract looks very good, you are in the right direction.

You could check all the contracts from other students in this same topic, you will have a very nice idea to complete the transfer and approve functions. Also if you still do not understand quite well something just ask us :nerd_face:

Carlos Z

Hello,
Here is my approach:

Owners.sol

pragma solidity ^0.8.2;

contract Owners {
    address[] private owners;
    
    event OwnerSet(address indexed newOwner);
    
    modifier isOwner {
        require(_isOwner(msg.sender), "Caller is not owner");
        _;
    }
    
    constructor(address[] memory _owners) public {
        owners = _owners;
        for (uint i = 0; i < owners.length; i++) {
            emit OwnerSet(owners[i]);
        }
    }
    
    function _isOwner(address _addr) private view returns (bool) {
        for(uint i = 0; i < owners.length; i++) {
            if (owners[i] == _addr) {
                return true;
            }
        }
        return false;
    }
}

Wallet.sol

pragma solidity ^0.8.2;

contract Wallet {
    
    mapping(address => uint) public deposits;
    
    function getBalance() external view returns (uint) {
        return address(this).balance;
    }
    
    function deposit() external payable {
        deposits[msg.sender] += msg.value;
    }
    
}

MultiSigWallet.sol

pragma solidity ^0.8.2;

import "./Owners.sol";
import "./Wallet.sol";

contract MultiSigWallet is Wallet, Owners {
    
    event TransactionApproved(address approver, uint transactionId);
    event TransactionCreated(address creator, uint transactionId);
    event TransactionSent(address sender, uint transactionId);
    
    struct Transaction {
        address payable to;
        uint amount;
        bool approved;
        bool sent;
    }
    
    Transaction[] private transactions;
    mapping(uint => address[]) private transactionApprovals;
    uint8 private approvalLimit;
    
    constructor(address[] memory _owners, uint8 _approvalLimit) public Owners(_owners) {
        require(_approvalLimit <= _owners.length, "Approval limit must be smaller or equal to number of owners");
        approvalLimit = _approvalLimit;
    }
    
    function _updateTransaction(uint _transactionId, address adr) private {
        transactionApprovals[_transactionId].push(adr);
        transactions[_transactionId].approved = transactionApprovals[_transactionId].length >= approvalLimit;
    }
    
    function createTransaction(address payable _to, uint _amount) external isOwner returns (uint) {
        Transaction memory transaction = Transaction(_to, _amount, false, false);
        transactions.push(transaction);
        uint transactionId = transactions.length - 1;
        _updateTransaction(transactionId, msg.sender);
        emit TransactionCreated(msg.sender, transactionId);
        return transactionId;
    }
    
    function approveTransaction(uint _transactionId) external isOwner {
        if (transactions[_transactionId].approved) {
            return;
        }
        for (uint i = 0; i < transactionApprovals[_transactionId].length; i++) {
            if (transactionApprovals[_transactionId][i] == msg.sender) {
                return;
            }
        }
        _updateTransaction(_transactionId, msg.sender);
        emit TransactionApproved(msg.sender, _transactionId);
    }

    function sendTransaction(uint _transactionId) external isOwner returns (bool) {
        Transaction storage transaction = transactions[_transactionId];
        if (transaction.amount <= address(this).balance && transaction.approved) {
            transaction.to.transfer(transaction.amount);
            transaction.sent = true;
            emit TransactionSent(msg.sender, _transactionId);
            return true;
        }
        return false;
    }
    
    function isApproved(uint _transactionId) external view isOwner returns (bool) {
        return transactions[_transactionId].approved;
    }
    
    function isSent(uint _transactionId) external view isOwner returns (bool) {
        return transactions[_transactionId].sent;
    }
    
    function getTransactions() external view isOwner returns (Transaction[] memory) {
        return transactions;
    }
    
    function getApprovals(uint _transactionId) external view isOwner returns(address[] memory) {
        return transactionApprovals[_transactionId];
    }
    
}

Hopefully that makes sense :slight_smile:

1 Like

Hey @loki , hope you are ok.

I tested your contract, it does what the assignment ask.

Steps I made:

  • deploy the contract with 3 owners Array, _approvalLimit = 2.
  • deposit() 3 ethers from 1st owner.
  • getBalance() to check balance of 1st owner.
  • createTransaction() 2 ether from 3rd owner to 2nd owner.
  • getTransactions() returns an array of pending transfer.
  • approveTransaction() with 1st owner and 2nd owner.
  • isApproved() returns true.
  • sendTransaction() with 3rd owner (the one who create the transaction)
  • Check Balance on 2nd 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.

Hi @thecil,

Thanks for the feedback!

I have just made a few minor tweaks, mainly regarding the visibility of some methods, i.e changed public to external if function is not used by the contract to decrease the gas cost.
Do you think there could be some further optimizations?

Cheers

1 Like

Answer to “project - Multisig Wallet”

pragma solidity 0.7.5;
pragma abicoder v2;


contract FriendBank {

    struct transfer {
        uint amount;
        uint totalApproved;
        bool isProcessed;
        address payable receiver;
        uint id;
    }
    
    uint approveLimit;
    uint totalBalance;
    
    address[] public owners;
    transfer[] transferRequests;
    
    mapping (address => mapping(uint => bool)) pendingTransfers;
   
    constructor(address[] memory _owners, uint _approveLimit) {
        owners = _owners;
        approveLimit = _approveLimit;
    }
    
    modifier onlyOwner() {
        bool isValid = false;
        for (uint i = 0; i < owners.length; i++) {
            if (owners[i] == msg.sender ) {
                isValid = true;
                break;
            }
        }
    
        require(isValid, "Only owner function");
        _;
    }

    function deposit(uint _amount) public payable {
        totalBalance += _amount;
    }
  
    function createTransfer(uint _amount, address payable _receiver) public payable onlyOwner {
       require(totalBalance >= _amount, "Insufficient balance to process transaction");
       transferRequests.push(transfer(_amount, 0, false,  _receiver, transferRequests.length));
    } 
 
    function getTotalBalance() public view onlyOwner returns(uint) {
        return totalBalance;
    }
   
    function approve(uint _id) public onlyOwner {
        require(pendingTransfers[msg.sender][_id] == false);
        require(transferRequests[_id].isProcessed == false); 
    
        transferRequests[_id].totalApproved++;
        pendingTransfers[msg.sender][_id] = true;
        
        if (transferRequests[_id].totalApproved >= approveLimit && 
            totalBalance >= transferRequests[_id].amount) 
        {
            transferRequests[_id].receiver.transfer(transferRequests[_id].amount);
            totalBalance -= transferRequests[_id].amount;
        }
    }
    
    function getTransferRequest() public view onlyOwner returns (transfer[] memory) {
       return transferRequests;
    }
     
}

I had to peek into the code from lasrt movie unfortunately. But i wonder why a boolean is needed to set it to isProcessed to true. Why not just delete the record from the array? Is it too expensive to do because of reindexing the array?

Regards,

Marcel

1 Like

I was not able to transfer to the contract itself by using values like msg.value and transfer(). I did was not able to get any help, and I have been waiting almost a week for an answer on the forum, so I decided to create the wallet balance as a variable instead. Haven’t watched the next video yet.

pragma solidity 0.7.5;

import “./OwnableWallet.sol”;

interface MultiSigWalletInterface {
function addDeposit(address _from, uint _amount) external payable;
function withdrawFromWallet(address _addressTo, uint _amount) external returns(uint);
function getWalletAddress() external view returns (address payable);
function getWalletOwnerAddresses() external view returns (address, address, address);
}

contract Exchange is OwnableWallet {

MultiSigWalletInterface multisigWalletInstance = MultiSigWalletInterface(0x4379a367558591f4e52B6D4FF2bBF69625975d1e);

address payable walletAddress;

constructor() {
    walletAddress = multisigWalletInstance.getWalletAddress();
}

mapping(address => uint) balances;

function withDrawFromWalletE(address _addressTo, uint _amount) external onlyOwner {
    multisigWalletInstance.withdrawFromWallet(_addressTo, _amount);
    balances[_addressTo] += _amount;
}

function depositToWalletE(uint _amount) payable external {
    require(balances[address(msg.sender)] >= _amount);
    balances[address(msg.sender)] -= _amount;
    multisigWalletInstance.addDeposit(address(msg.sender), _amount);
}

function getWalletOwners() external view returns(address, address, address) {
    return multisigWalletInstance.getWalletOwnerAddresses();
}

// Get own wallet balance
function getBalance() public view returns (uint) {
    return balances[address(msg.sender)];
}

function deposit(uint _amount) public {
   
    balances[msg.sender] += _amount;
}

function getWalletSize() external view returns (uint) {
    return address(this).balance;
}

}

pragma solidity 0.7.5;

import “./OwnableWallet.sol”;

contract MultiSigWallet is OwnableWallet {
//MultiSigWallet
uint wallet;
//Keeps count of nu,ber of approvals
uint walletApprovalCount;

mapping(address => bool) ownerApproval; 

struct Deposit {
    address _from;
    uint _amount;
    uint _txId;
    uint _date;
}

Deposit[] depositLog;

struct Withdraw {
    address _to;
    uint amount;
    uint txId;
    uint _date;
}
Withdraw[] withdrawLog;

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

//Function for owner to approve of the withdrawal
function Approve() external {ownerApproval[address(msg.sender)] = true;}

//Function for owner to approve of the withdrawal
function Deny() external {ownerApproval[address(msg.sender)] = false;}

function getWalletApproveCount() public {
   if(ownerApproval[owner1] == true) {walletApprovalCount++;}
   if(ownerApproval[owner2] == true) {walletApprovalCount++;}
   if(ownerApproval[owner3] == true) {walletApprovalCount++;}
}

/*
Withdraws from wallet, checks if there's enough approvals. 
Adds to transaction log.
*/
function withdrawFromWallet(address _addressTo, uint _amount) external returns(uint) {
    getWalletApproveCount();
    require(walletApprovalCount >= 2 && wallet >= _amount);
    wallet -= _amount;
    withdrawLog.push(Withdraw(_addressTo, _amount, withdrawLog.length, block.timestamp));
    return _amount;
}

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

function getWalletAddress() external view returns (address) {return address(this);}

function getWalletSize() internal view returns (uint) {return wallet;}

}

contract OwnableWallet {
     address internal owner1;
     address internal owner2;
     address internal owner3;
     
    modifier onlyOwner{
       require(msg.sender == owner1 || msg.sender == owner2 || msg.sender == owner3);
       _; // Run the function
   }
   
   
      constructor(){
         owner1 = 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2;
         owner2 = 0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db;
         owner3 = 0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB;
   }
}
1 Like

This is my solution which I made with the help of the template - I accidentally found it early, and it helped a lot.

I’ve tested a transfer successfully. I did seem to have issues when there was only 1 transfer request.

pragma solidity 0.7.5;
pragma abicoder v2;


contract MyMultiSigWallet {
    address[] public owners;
    uint limit;
    
    struct Transfer{
        uint amount;
        address payable receiver;
        uint approvals;
        bool hasBeenSent;
        uint id;
    }
    
    event transferRequestEvent(address indexed _receiver, uint _amount);
    
    event transferApproval(address indexed _approver, uint _id);
    
    event transferSent(address indexed _receiver, uint _amount);
    
    Transfer[] 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 i=0;i<owners.length;i++){
            if (owners[i] == msg.sender){
                isOwner = true;
                break;
            }
        }
        require(isOwner == true, "You are not an owner");
        _;
    }
    //Should initialize the owners list and the limit 
    constructor(address _owner1, address _owner2, address _owner3, uint _limit) {
        owners.push(_owner1);
        owners.push(_owner2);
        owners.push(_owner3);
        limit = _limit;
    }
    
    //Empty function
    function deposit() public payable {}
    
    //Create an instance of the Transfer struct and add it to the transferRequests array
    function createTransfer(uint _amount, address payable _receiver) public onlyOwners {
        transferRequests.push(Transfer(_amount, _receiver,0,false,transferRequests.length));
        emit transferRequestEvent(_receiver, _amount);
    }
    
    //Set your approval for one of the transfer requests.
    function approve(uint _id) public onlyOwners {
              
              require(_id < transferRequests.length, "No Transfer Request with that ID found.");
              //An owner should not be able to vote on a tranfer request that has already been sent.
              require(!transferRequests[_id].hasBeenSent, "Transfer already sent");

              //An owner should not be able to vote twice.
              require(approvals[msg.sender][_id] != true, "You cannot vote twice");

              //As long as the contract has enough funds?
              require(transferRequests[_id].amount <= address(this).balance, "Insufficient Balance to send Transfer");

              //Need to update the Transfer object.
              transferRequests[_id].approvals += 1;
              emit transferApproval(msg.sender,_id);
              //Need to update the mapping to record the approval for the msg.sender.
              approvals[msg.sender][_id] = true;
              //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){
                  createTransfer(transferRequests[_id].amount, transferRequests[_id].receiver);
                  _send(transferRequests[_id].receiver,transferRequests[_id].amount);
                  transferRequests[_id].hasBeenSent = true;
              }

          }
        
    
        function _send(address payable _to, uint _amount) private{
            assert(address(this).balance >= _amount);
            uint b1 = address(this).balance;
            _to.transfer(_amount);
            assert(address(this).balance == (b1 - _amount));
            emit transferSent(_to, _amount);
        }

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

Also, I modified the constructor to accept 3 addresses seperately, rather than as an array, as I couldn’t get remix to accept them as an array. Any input on the correct way of doing that would be awesome.