Project - Multisig Wallet

I used the template to do this assignment. Honestly, I had trouble with the approve function and the modifier I’ve watched the last video to implement that function.

pragma solidity 0.7.5;
pragma abicoder v2;

contract Wallet {

event Deposits(uint indexed _amount, address indexed _receiver);


address[] public owners;
uint limit;

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



Transfer[] transferRequests;

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

mapping(address => uint)balance;


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[] memory _owners, uint _limit) {
    owners = _owners;
    limit = _limit;
}


function deposit() public payable {}


function createTransfer(uint _amount, address payable _receiver) public onlyOwners {
    emit Deposits(_amount, _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++;
    
    if(transferRequests[_id].approvals >= limit){
        transferRequests[_id].hasBeenSent = true;
        transferRequests[_id].receiver.transfer(transferRequests[_id].amount);
    }
}


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

//Get the Balance

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

}

1 Like

Hey @Epicdisaster, hope you are well.

I have tested your contract and it does works good, which is exactly the error you are facing? :face_with_monocle:

pragma solidity 0.7.5;
pragma abicoder v2;
// SPDX-License-Identifier: UNLICENSED

// ["0x5B38Da6a701c568545dCfcB03FcB875f56beddC4", "0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2", "0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c"]
contract Wallet {

event Deposits(uint indexed _amount, address indexed _receiver);


address[] public owners;
uint limit;

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



Transfer[] transferRequests;

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

mapping(address => uint)balance;


modifier onlyOwners(){
    bool owner;
    for(uint i =0; i< owners.length;i++){
        if(owners[i] == msg.sender){
            owner = true;
        }
    }
    require(owner == true, "onlyOwners: not a valid owner");
    _;
}

constructor(address[] memory _owners, uint _limit) {
    owners = _owners;
    limit = _limit;
}


function deposit() public payable {}


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


function approve(uint _id) public onlyOwners {
    require(approvals[msg.sender][_id] == false, "approve: approvals[msg.sender][_id] == false");
    require(transferRequests[_id].hasBeenSent == false, "approve: ansferRequests[_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 getTransferRequests() public view returns (Transfer[] memory){
    return transferRequests;
}

//Get the Balance

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

Carlos Z

I haven’t watch the next video for hints, only search on google for how-to’s for each component of the code.

I try to make it flexible so that you can add as initialize any amount of owners and can have any amount of signatures required.

Only have the bare minimum code for just depositing, requesting transfer, and approving transfers.

pragma solidity 0.7.5;

contract Multisig {
    
    address[] owners;
    mapping(address => bool) ownerExists;
    uint signaturesRequired;
    TransferRequest currentTransferRequest;
    address[] approvedOwners;
    mapping(address => bool) approvedOwnerExists;
    
    struct TransferRequest {
        address recipient;
        uint amount;
    }
    
    modifier onlyOwners() {
        require(ownerExists[msg.sender] == true);
        _;
    }
    
    event deposited(address depositFrom, uint amount);
    event requested(address requestAddress, address recipient, uint amount);
    event approved(address approvedAddress, address recipient, uint amount);
    event transferred(address recipient, uint amount);
    
    constructor(address[] memory otherOwners, uint approvalsRequired) {
        owners = otherOwners;
        owners.push(msg.sender);
        for(uint i = 0; i < owners.length; i++) {
            ownerExists[owners[i]] = true;
        }
        signaturesRequired = approvalsRequired;
    }

    function deposit() public payable {
        emit deposited(msg.sender, msg.value);
    }
    
    function requestTransfer(address recipient, uint amount) public onlyOwners {
        require(address(this).balance >= amount);
        emit requested(msg.sender, recipient, amount);
        currentTransferRequest = TransferRequest(recipient, amount);
        delete approvedOwners;
        approvedOwners.push(msg.sender);
        approvedOwnerExists[msg.sender] = true;
    }
    
    function approve() public onlyOwners {
        if(approvedOwnerExists[msg.sender] == false) {
            emit approved(msg.sender, currentTransferRequest.recipient, currentTransferRequest.amount);
            approvedOwners.push(msg.sender);
            approvedOwnerExists[msg.sender] = true;
        }

        if(approvedOwners.length >= signaturesRequired) {
            emit transferred(currentTransferRequest.recipient, currentTransferRequest.amount);
            payable(currentTransferRequest.recipient).transfer(currentTransferRequest.amount);
            delete currentTransferRequest;
            delete approvedOwners;
        }
    }
}

Still feel a bit messy, wish there is Array.contains in Solidity.

1 Like

I need a little help on how to inherit correctly in this case, not sure what needs to be fixed. An error saying "FinalProject/validOwner.sol:3:24: TypeError: Definition of base has to precede definition of derived contract
contract validOwner is Wallet{
^----^ "

main.sol:

pragma solidity 0.7.5;
pragma abicoder v2;
import "./validOwner.sol";

contract Wallet is validOwner{
    address[] public owners;
    uint limit;
    
    event txReqCreated(uint _amount, uint _id, address _requester, address _receiver);
    event approval_Successful(uint _id, uint _approvals, address _approver);
    event txApproved(uint _id);
    
    struct transaction{
        uint id;
        address payable receiver;
        uint amount;
        uint approvals;
        bool hasBeenSent;
    }
    
    transaction[] txReq;
    
    mapping(address => mapping(uint => bool)) approvals;
    
    constructor(address[] memory _owners, uint _limit) {
        owners = _owners;
        limit = _limit;
    }
    
    function deposit() public payable {}
    
    function createTX(uint _amount, address payable _receiver) public validOwner {
        emit txReqCreated(txReq.length, _amount, msg.sender, _receiver);
        transferRequests.push(transfer(_amount, _receiver, 0, false, txReq.length));
        //we are pushing this transaction info according to its array number
    }
    
    function approve(uint _id) public validOwner {
        require(approvals[msg.sender][_id] == false);
        require(txReq[_id].hasBeenSent == false);
        
        approvals[msg.sender][_id] = true;
        txReq[_id].approvals++;
        
        emit approval_Successful(_id, txReq[_id].approvals, msg.sender);
        
        if(txReq[_id].approvals >= limit){
            txReq[_id].hasBeenSent = true;
            txReq[_id].receiver.transfer(txReq);
            emit txApproved(_id);
        }
    }
    
    function getTXReq() public view returns (Transfer[] memory){
        
    }
}

validOwner.sol:

pragma solidity 0.7.5;
import "./main.sol";
contract validOwner is Wallet{
    
    modifier validOwner {
    bool owner = false;
    for(uint i=0; i<owners.length;i++){
        if(owners[i] == msg.sender){
            owner = true;
        }
        
    }
    require(owner == true);
    _;
    }
}

Hey @Denzel, hope you are well.

There are some few errors in your contract, mostly on inheritance.

One of the contract should inherit the other, not both.
Think about it, how needs to access to who. Who will be the main Contract (parent), which are the childs.

For your case, Wallet is the main contract (parent), which will require additional features provided by validOwner.

Also, all contracts file name and the contract name should start with capital letter (Wallet.sol for the Wallet contract).

Should be Wallet.sol

Should both (filename and contract name) be ValidOwner (ValidOwner.sol for the filename).

Hope it helps. :nerd_face:

Carlos Z

1 Like

I went ahead and tried to accomplish this without reading ahead, and without importing other contracts. I struggled a little and believe I threw a bunch of best practices out the window. I have it working in remix now, but It can likely be gobs more efficient.

pragma solidity 0.7.5;

contract Wallet {
    uint approvalsRequired;
    uint balance;
    mapping(address => bool) owner;

    struct TransferRequest {
        uint amount;
        address[] approvedBy;
    }

    mapping(address => TransferRequest) transferRequests;

    modifier onlyOwner {
        require(owner[msg.sender] == true);
        _; //runs the modified function
    }

    constructor(address[] memory _owners, uint _approvals){
        approvalsRequired = _approvals;
        //is specifying _owners.length as a memory value lest costly than checking _owners.length every iteration
        for(uint i=0;i<_owners.length;i++){
            owner[_owners[i]] = true;
        }
    }

    event depositDone(uint amount, address indexed depositedTo);

    function createTransferRequest(address payable _transferTo, uint amount) public onlyOwner {
        for(uint i=0;i<transferRequests[_transferTo].approvedBy.length;i++){
            assert(transferRequests[_transferTo].approvedBy[i] != msg.sender);
        }

        if(transferRequests[_transferTo].amount == amount){
            transferRequests[_transferTo].approvedBy.push(msg.sender);
        }else{
            setTransferRequest(_transferTo, amount);
        }

        if(transferRequests[_transferTo].approvedBy.length >=approvalsRequired){
            _transfer(_transferTo, amount);
        }
    }

    function setTransferRequest(address payable _transferTo, uint amount) private onlyOwner {
        address[] storage newArr;
        newArr.push(_transferTo);
        TransferRequest memory newTransfer = TransferRequest(amount, newArr) ;
        transferRequests[_transferTo] = newTransfer;
    }

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

    function _transfer(address payable transferTo, uint amount) private onlyOwner returns (uint){
        require(balance >= amount, "Insufficient funds.");
        uint previousSenderBalance = balance;
        balance -= amount;
        transferTo.transfer(amount);
        assert(balance == previousSenderBalance - amount);
        return balance;
    }

    function getBalance() public view returns (uint){
        return balance;
    }
}
1 Like

This is what I’ve put together:

pragma solidity 0.7.5;
pragma abicoder v2;

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

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

    Transfer[] transferRequests;

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

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

    constructor(address[] memory _owners, uint _limit) {
        owners = _owners;
        limit = _limit;
    }

    event depositDone(uint amount, address indexed depositedBy);
    function deposit() public payable returns(uint){
        emit depositDone(msg.value, msg.sender);
        return address(this).balance;
    }

    function createTransfer(uint _amount, address payable _receiver) public onlyOwners {
        transferRequests.push(Transfer(_amount, _receiver, 0, false, transferRequests.length));
    }
    
    function approve(uint _id) public onlyOwners {
        assert(approvals[msg.sender][_id]!=true); 
//is an undefined key-value pair in a struct/map truthy? 
        assert(!transferRequests[_id].hasBeenSent);
        transferRequests[_id].approvals++;
        approvals[msg.sender][_id]=true;
        if(transferRequests[_id].approvals>=limit){
            transferRequests[_id].receiver.transfer(transferRequests[_id].amount);
            transferRequests[_id].hasBeenSent=true;
        }
    }

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

I have managed to get this far without the additional videos.
1 Owner can be added, but the 2nd generates the following error:
image
I cant find the fix and I am pretty confused by the error message for a addOwner Function.
Feedback would be appreciated! :slight_smile:

pragma solidity 0.7.5;

contract MyContract {
    
    //owners+trxRequests are added manually and saved in Array
    
    Owners [] public owner;
    Transfer[] trxRequests;
    //2/3 Owners have to confirm a transaction
    uint public numConfirmationsRequired = 2;
    uint public numConfirmations = 0;
    
    address payable internal allowed;
    mapping(address => uint) balance;
    
    //only owners have certain permissions
    modifier onlyAllowed() {
        require(msg.sender == allowed);
        _;
    }
    
    constructor(){
        allowed = msg.sender;
    }
    
    struct Owners {
        string _OwnerAddress;
    }
    
    struct Transfer{
        uint amount;
        address payable recipient;
        uint approvals;
        uint id;
    }
    //Everyone can deposit funds
    function deposit() public payable returns (uint) {
        balance[msg.sender] += msg.value;
        return balance[msg.sender];
    }
    
    //array is dynamic, DONT ADD MORE THAN 3 OWNERS!
    function addOwners(string calldata _OwnerAddress) 
    public 
    onlyAllowed
    {
        owner.push(
            Owners(_OwnerAddress)
        );
        /*
        for(uint i = 0; i < Owners.length; i++){
            Owners[i]
            break;
        }*/
    }
    
    function submitTrxRequest(uint amount, address payable recipient) 
    public 
    onlyAllowed 
    {
        trxRequests.push(
            Transfer(amount, recipient, 0, trxRequests.length)
        );
    }
    
    function trxApproval(uint _id)
    public 
    onlyAllowed
    {
        numConfirmations++;
       
        if(numConfirmations >= numConfirmationsRequired){
            trxRequests[_id].recipient.transfer(trxRequests[_id].amount);
            //uint numConfirmations = 0;
        }
    }
    
    
}

Hey @Konzaih, hope you are well.
Based on your screenshot of the console. The problem might be a require statement that is being triggered on your addOwners function. Although this one does not contain any, the modifier does.

I advice you to use a revert message on each of your require(s), that way you can easily know which revert is triggered and why.
For Example:
`require(msg.sender == allowed, “only allowed user accepted”);

Carlos Z

I felt I needed a bit more tips so I watched also the Project Assistance video. Then I created my first Smart Contract in Solidity!!! :slight_smile:
P.S. I did not use the double mapping, infact then I watched the video to understand it :grimacing:
BTW, this is it:

pragma solidity 0.7.5;

contract Wallet {

address[] public owners;

uint public requiredApprovers;

function initiateContract (address owner1, address owner2, address owner3, uint _requiredApprovers) public {
    require((_requiredApprovers<=3 && _requiredApprovers>=2), "The required approvers should be 2 or 3!!!");
    owners.push(owner1);
    owners.push(owner2);
    owners.push(owner3);
    requiredApprovers=_requiredApprovers;
}

struct transferRequest{
    address recipient;
    uint amount;
    bool executed;
}

transferRequest[] transferRequests;

struct approval{
    bool approved0;
    bool approved1;
    bool approved2;
}

approval[] approvalsLog;

mapping (address=> uint) balance;

function depositInContract () public payable {
    balance[address(this)] += msg.value;
}

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

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

function transferFromContract(address recipient, uint amount) public payable {
    require(balance[address(this)]>=amount, "Wallet balance not sufficient!");
    transferRequests.push(transferRequest(recipient, amount, false));
    approvalsLog.push(approval(false, false, false));
}

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

function approveTransfer(uint transferID) public {
    require(msg.sender==owners[0] || msg.sender==owners[1] || msg.sender==owners[2], "Approval denied, you are not an owner of this wallet!!!");

    if(msg.sender==owners[0]){
        require(approvalsLog[transferID].approved0==false, "Approval denied, you already approved this transfer!!!");
        approvalsLog[transferID].approved0=true;
    } else if(msg.sender==owners[1]){
        require(approvalsLog[transferID].approved1==false, "Approval denied, you already approved this transfer!!!");
        approvalsLog[transferID].approved1=true;
    } else {
        require(approvalsLog[transferID].approved2==false, "Approval denied, you already approved this transfer!!!");
        approvalsLog[transferID].approved2=true;
    }
    
    if(requiredApprovers==2){
    
    if (
       ((approvalsLog[transferID].approved0==true && approvalsLog[transferID].approved1==true)
       ||
       (approvalsLog[transferID].approved0==true && approvalsLog[transferID].approved2==true)
       ||
       (approvalsLog[transferID].approved1==true && approvalsLog[transferID].approved2==true))
       && transferRequests[transferID].executed==false){
        _transfer(transferRequests[transferID].recipient, transferRequests[transferID].amount);
        transferRequests[transferID].executed=true;
    }} else{
    
    if (
       ((approvalsLog[transferID].approved0==true && approvalsLog[transferID].approved1==true && approvalsLog[transferID].approved2==true))
       && transferRequests[transferID].executed==false){
        _transfer(transferRequests[transferID].recipient, transferRequests[transferID].amount);
        transferRequests[transferID].executed=true;
    }}
}

function checkApproval(uint transferID) public view returns(bool, bool, bool){
    return(approvalsLog[transferID].approved0, approvalsLog[transferID].approved1, approvalsLog[transferID].approved2);
}

function checkTransfer(uint transferID) public view returns(address, uint, bool){
    return(transferRequests[transferID].recipient, transferRequests[transferID].amount, transferRequests[transferID].executed);
}    

}

Thanks a lot for the great learinings!!! :star_struck:

1 Like

Revised version after watching the Full Project Code Video:

pragma solidity 0.7.5;

contract Wallet {

address[] public owners;

uint public requiredApprovers;

constructor(address owner1, address owner2, address owner3, uint _requiredApprovers){
  require((_requiredApprovers<=3 && _requiredApprovers>=2), "The required approvers should be 2 or 3!!!");
    owners.push(owner1);
    owners.push(owner2);
    owners.push(owner3);
    requiredApprovers=_requiredApprovers;  
}

struct transferRequest{
    address payable recipient;
    uint amount;
    bool executed;
}

transferRequest[] transferRequests;

struct approval{
    bool approved0;
    bool approved1;
    bool approved2;
}

approval[] approvalsLog;

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

function depositInContract () public payable {
}

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

function transferFromContract(address payable recipient, uint amount) public payable {
    require(msg.sender==owners[0] || msg.sender==owners[1] || msg.sender==owners[2], "Transfer request denied, you are not an owner of this wallet!!!");
    require(address(this).balance>=amount, "Wallet balance not sufficient!");
    emit TransferRequestCreated(transferRequests.length, amount, msg.sender, recipient);
    transferRequests.push(transferRequest(recipient, amount, false));
    approvalsLog.push(approval(false, false, false));
}

function approveTransfer(uint transferID) public {
    require(msg.sender==owners[0] || msg.sender==owners[1] || msg.sender==owners[2], "Approval denied, you are not an owner of this wallet!!!");

    if(msg.sender==owners[0]){
        require(approvalsLog[transferID].approved0==false, "Approval denied, you already approved this transfer!!!");
        approvalsLog[transferID].approved0=true;
        emit ApprovalReceived(transferID, msg.sender);
    } else if(msg.sender==owners[1]){
        require(approvalsLog[transferID].approved1==false, "Approval denied, you already approved this transfer!!!");
        approvalsLog[transferID].approved1=true;
        emit ApprovalReceived(transferID, msg.sender);
    } else {
        require(approvalsLog[transferID].approved2==false, "Approval denied, you already approved this transfer!!!");
        approvalsLog[transferID].approved2=true;
        emit ApprovalReceived(transferID, msg.sender);
    }
    
    if(requiredApprovers==2){
    
    if (
       ((approvalsLog[transferID].approved0==true && approvalsLog[transferID].approved1==true)
       ||
       (approvalsLog[transferID].approved0==true && approvalsLog[transferID].approved2==true)
       ||
       (approvalsLog[transferID].approved1==true && approvalsLog[transferID].approved2==true))
       && transferRequests[transferID].executed==false){
        transferRequests[transferID].recipient.transfer(transferRequests[transferID].amount);
        transferRequests[transferID].executed=true;
        emit TransferApproved(transferID);
    }} else{
    
    if (
       ((approvalsLog[transferID].approved0==true && approvalsLog[transferID].approved1==true && approvalsLog[transferID].approved2==true))
       && transferRequests[transferID].executed==false){
        transferRequests[transferID].recipient.transfer(transferRequests[transferID].amount);
        transferRequests[transferID].executed=true;
        emit TransferApproved(transferID);
    }}
}

function checkApproval(uint transferID) public view returns(bool, bool, bool){
    return(approvalsLog[transferID].approved0, approvalsLog[transferID].approved1, approvalsLog[transferID].approved2);
}

function checkTransfer(uint transferID) public view returns(address, uint, bool){
    return(transferRequests[transferID].recipient, transferRequests[transferID].amount, transferRequests[transferID].executed);
}    

}

I made the functions payble and now I can actually move ETHs from the Wallet to the addresses!!! :slight_smile:

2 Likes

So this is the multi-sig wallet contract base.

This is a simplification of my actual project which is seperates the transaction and ownership elements into two contracts.

I am hoping to use some functionality from Gnosis, add owner, remover owner etc, I have achieved this, but currently only in a way that has a glaring security flaw, whereby a user can add and remove an owner without the need for a vote, so hypothetically, they could remove the original owners and add multiple of their own wallets to gain majority.

As it stands this contract example is transaction only, and owners must be set upon deployment with no possibility to modify ownership. Present state;

  • Constructor sets owners in an array, and confirmation requirements.
  • Addresses can be checked to see if they are owners.
  • Any wallet or contract can deposit and send to the multi-sig.
  • Mulit-sig can call other contracts
  • Transaction struct can be queried through its array index to check confirmation, execution, to, value and data
  • Confirmation can be revoked by any owner at any point before execution.

Can post without pseudo-code if it makes it less verbose.

/*
How to initialize owners and required confirmations, here 3 owners, 2/3 confirmations needed
["0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2", 
"0x5B38Da6a701c568545dCfcB03FcB875f56beddC4", 
"0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c"], 
2
*/
//How to deposit (data must be even), 0xe4aE4dde9aA65e1C4C2A4BfbC040bB3675Abb6F6, 2000000000000000000, 0x00 
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.7.6; 
contract MultiSig {
    
    //EVENTS
    //Emit when deposit is made directly or when funds are sent to wallet. 
    event Deposit(address indexed sender, uint amount, uint balance);
    //Emit when a tx proposal is registered
    event Submit(
        address indexed owner,
        uint indexed txIndex,
        address indexed to, 
        uint value,
        bytes data
    );
    //Emit upon each confirmation
    event Confirm(address indexed owner, uint indexed txIndex);
    //Update total confirmations upon revokation 
    event Revoke(address indexed owner, uint indexed txIndex);
    //Emit when threshold is met and tx is sent
    event Execute(address indexed owner, uint indexed txIndex);
    
    //CONSTANTS, STATE VARIABLES
    uint constant public OWNER_LIMIT = 5;
    //Number of confirmations to execute tx
    uint public numConfirmationsRequired;
     //Owners stored in array of addresses
    address[] public owners;
    
    //MAPPINGS  
    //To make sure there are no duplicate owners use a mapping to tell if an address is owner or not, 
    //Used in constructor late during owner initialization to avoid duplicating owners. 
    mapping(address => bool) public isOwner;
    //Double mapping, uses txIndex and owner as inputs. 
    //Maps txIndex to Owner address, if they confirmed = true, if they did not = false
    mapping(uint => mapping(address  => bool)) public isConfirmed;
    
    //STRUCTS
    //Created by calling the submitTransaction function, stored in Transaction array below.
    struct Transaction {
        address to;
        uint value;
        //If calling another contract, tx data sent to that contract will be stored 
        bytes data;
        //If tx is executed, stored as boolean
        bool executed;
        uint numConfirmations;        
    }
    Transaction[] public transactions;
    
    //MODIFIERS
    modifier onlyOwner() {
       //Uses prior mapping to check if function caller is one of the contract owner addresses. 
       require (isOwner[msg.sender], "You are not an owner.");
       _;
    }
    //Takes in the txIndex as an input
    modifier txExists(uint _txIndex) {
       //If the input index is less than the array length, it exists in the array
       require(_txIndex < transactions.length, "TX does not exist");
       _;
    }
    modifier notExecuted(uint _txIndex) {
       //Use bracket and dot notation to check value of executed in struct of given txIndex is false (!)
       require(!transactions[_txIndex].executed, "Transaction already executed.");
       _;
    }
    modifier notConfirmed(uint _txIndex) {
        //Access double mapping, check if function caller has confirmed given Tx
        require(!isConfirmed[_txIndex][msg.sender], "TX already confirmed");
        _;
    }
   
    //CONSTRUCTOR to initialize array of owners and confimration requirement upon deployment of contract
    constructor(address[] memory _owners, uint _numConfirmationsRequired) {
        //Ensure that the array of owners is not empty 
        require(_owners.length > 0, "Owners required");
        require(_numConfirmationsRequired > 0 && _numConfirmationsRequired <= _owners.length,
            "Not enough confirmations"
        );
        //owners add as inputs are now added to the state variables. 
        for (uint i = 0; i < _owners.length; i++) {
            //owner at index i stored in variable
            address owner = _owners[i];
            require(owner != address(0), "Not an owner");
            //checking if address is already an owner using mapping
            require(!isOwner[owner], "Owner already registered");
            //If requirements are met, validate owner by setting it to true with mapping
            isOwner[owner] = true;
            //Add new owner to the state variable array of Owners
            owners.push(owner);
        }
        //set the number of required confirmations with function input
        numConfirmationsRequired = _numConfirmationsRequired;
    }
    //FUNCTIONS
    //FALLBACK 
    receive() payable external {
        emit Deposit(msg.sender, msg.value, address(this).balance);
    }
    
    function deposit() payable external {
        emit Deposit(msg.sender, msg.value, address(this).balance);
    }
        
    //One of the owners will propose transaction
    function submitTransaction (address _to, uint _value, bytes memory _data) public onlyOwner{
        //the tx id will be it's index in the transactions array, 1=0, 2=1...
        uint txIndex = transactions.length;
        //Initialize Transaction struct and add to array of transactions
        transactions.push(Transaction({
            //to value and data set as inputs for the function call
            to: _to,
            value: _value,
            data: _data,
            //executed set to false as it is a new submission
            executed: false,
            //confirmations set to 0 as is cannot be confirmed before submission
            numConfirmations: 0
        }));
        //Event emitted with function caller, current txIndex and inputs of function
        emit Submit(msg.sender, txIndex, _to, _value, _data);
    }
    //Other owners can confirm transaction
    function confirmTransaction(uint _txIndex)public onlyOwner  txExists(_txIndex) notExecuted(_txIndex) notConfirmed(_txIndex) {
        //To update the Transaction struct, get tx by its txIndex, store as 'transaction'
        Transaction storage transaction = transactions[_txIndex];
        //set isConfirmed for the function caller on this particular tx to true.
        //msg.sender has now approved this tx 
        isConfirmed[_txIndex][msg.sender] = true;
        //Incredment numConfirmations
        transaction.numConfirmations += 1;
        emit Confirm(msg.sender, _txIndex);
    }
    
    //Allow owner to revoke permission for tx before it is notExecuted
    //Functions as reverse of confirmation
    function revokeConfirmation(uint _txIndex)public onlyOwner txExists(_txIndex) notExecuted(_txIndex) {
         Transaction storage transaction = transactions[_txIndex];
         require(isConfirmed[_txIndex][msg.sender], "tx not confirmed");
         isConfirmed[_txIndex][msg.sender] = false;
         transaction.numConfirmations -= 1;
         emit Revoke(msg.sender, _txIndex);
         
    }
    
    function executeTransaction(uint _txIndex) public onlyOwner txExists(_txIndex) notExecuted(_txIndex){
        Transaction storage transaction = transactions[_txIndex];
        require(transaction.numConfirmations >= numConfirmationsRequired, "Not enough Confirmations, cannot execute");
        //Access executed setting of Tx ttruct for this tx and set to true
        transaction.executed =true;
        //Execute tx by using the call method
        (bool success, ) = transaction.to.call{value: transaction.value}(transaction.data);
        require(success, "tx failed");
        emit Execute(msg.sender, _txIndex);
    }
}
1 Like

hey @Jackthelad. Really coool solution man. In terms of your attempt too dynamically add users. I can propose a really easy fix to your security issue. Now you could of course make another struct for adding a user and create another approval function like we did with the transfer and this would actually be very cool to see. But there is an easier way. So basically the “main” or “true” owner of the wallet is the address of the person who deployes the actual wallet contract. All other owners are like signatories but are not main owners. Using this fact you could just require in your add and remover owner functions that the call of the function needs to be the main owner of the account who deployed the contract.

Since you are keen to get the dynamic adding and removing of users solved yourself i will not give a code solution snippet but i can give you a hint of starting to implement the approach i described above. We need some way to isolate the contract deployers address. However using msg.sender will not work as msg.sender only returns the address of the current account who is calling the contract functions. So we need another way to isolate the address of the contract deployer. To do this we can set a global var called contractOwner and make it private (which will be safer). Then we can simpily set this var equal to msg.sender in the constructor which will now get the deployer address. Then in your add and remover user functions we can require that msg.sender is equal to the contractOwner. Something akin to this

address private contractOwner;

    constructor() public {
        contractOwner = msg.sender;
    }

    //use this modifier on your add and remove user functions
    modifier requireContractOwner() {
        require(msg.sender == contractOwner, "Caller is not contract owner");
        _;
    }
2 Likes

Cheers for the feedback @mcgrane5.
I have used your suggestion to sort it out to a point where it is working and no longer has that flaw when any of the signatories can add and remove at will. In the current iteration, if the contract is deployed by 0x3d… with [0xAb… 0x5B… 0xCA], 2 (as owners and confirmation requirements)

  • Deployer will be ‘Control’ but not owner, and cannot become and owner unless included in owners array upon deployment.

    • I have tried for a long time to find a way to prevent the contract being deployed if the contract deployer includes their own address in the list of owners (for, if, require) but couldn’t get it to work.
  • Only ‘Control’ can add, remove or change requirements (modifier onlyControl)

I would prefer the control elements (add, remove, and changerequiremnts) to require majority confirmation from owners, but after hours at the laptop I’m doing more harm than good to my contract.

I have split the ownership control functionality and transaction functionality into two contracts for easier navigation.

pragma solidity ^0.7.6;
contract Controller {
    
    //Control EVENTS
    event OwnerAdded(address indexed owner);
    event OwnerRemoved(address indexed owner);
    event RequirementChanged(uint required);
    
     //MAPPINGS  
    mapping(address => bool) public isOwner;  //Used in constructor late during owner initialization to avoid duplicating owners. 
   
    mapping(uint => mapping(address  => bool)) public isConfirmed; //Maps txIndex to Owner address, if they confirmed = true, if they did not = false
    
    //CONSTANTS, STATE VARIABLES
    uint constant public OWNER_LIMIT = 5;
    uint public numConfirmationsRequired; //Number of confirmations to execute tx
    address[] public owners; //Owners stored in array of addresses
    address private Control; //Variable for controller
    
   //INITIALIZE CONTROLLER
    constructor() public {
            Control = msg.sender;
    }
    
    //MODIFIERS for Controller
    modifier onlyControl() {
        require(msg.sender == Control, "Caller is not in Control of contract Parameters");
        _;
    }
    modifier notControl() { //To be used for the submit transaction function. A redundancy due to the requirement in the add owner fucntion preventing the control from becoming an owner.
        require(msg.sender != Control,"Controller cannot transact");
        _;
    }
    modifier onlyOwner() {
       require (isOwner[msg.sender], "You are not an owner."); //Uses prior mapping to check if function caller is one of the contract owner addresses.
       _;
    }
    modifier ownerDoesNotExist(address owner) {
        require(!isOwner[owner]);
        _;
    }
    modifier ownerExists(address owner) {
        require(isOwner[owner]);
        _;
    }
    modifier notNull(address _address) {
        require(_address != address(0));
        _;
    }
    modifier validRequirement(uint ownerCount, uint _required) {
        require(ownerCount <= OWNER_LIMIT
            && _required <= ownerCount
            && _required != 0
            && ownerCount != 0);
        _;
    }
    //
    function addOwner(address _owner) public onlyControl ownerDoesNotExist(_owner) notNull(_owner) validRequirement(owners.length + 1, numConfirmationsRequired){
        require(_owner != Control, "Controller cannot be an owner");
        isOwner[_owner] = true;
        owners.push(_owner);
        emit OwnerAdded(_owner);
    }
    function removeOwner(address _owner) public onlyControl ownerExists(_owner) {
        isOwner[_owner] = false;
        for (uint i=0; i < owners.length; i++) 
            if (owners[i] == _owner) {
                    delete owners[i];
                }
            if (numConfirmationsRequired > owners.length)
            changeRequirement(owners.length);
            OwnerRemoved(_owner);
    }
    function changeRequirement(uint _required) public onlyControl validRequirement(owners.length, _required) {
        numConfirmationsRequired = _required;
        RequirementChanged(_required);
    }
}
pragma solidity ^0.7.6;
import "./MultiSig-Control.sol";
contract MultiSigOwner is Controller{
    
    //Transaction EVENTS
    event Deposit(address indexed sender, uint amount, uint balance); //Emit when deposit is made directly or when funds are sent to wallet. 
    event Submit( //Emit when a tx proposal is registered
        address indexed owner,
        uint indexed txIndex,
        address indexed to, 
        uint value,
        bytes data
    );
    event Confirm(address indexed owner, uint indexed txIndex); //Emit upon each confirmation
    event Revoke(address indexed owner, uint indexed txIndex); //Update total confirmations upon revokation 
    event Execute(address indexed owner, uint indexed txIndex); //Emit when threshold is met and tx is sent
    
    //STRUCTS
    struct Transaction { //Created by calling the submitTransaction function, stored in Transaction array below.
        address to;
        uint value;
        bytes data; //If calling another contract, tx data sent to that contract will be stored 
        bool executed; //If tx is executed, stored as boolean
        uint numConfirmations;        
    }
    Transaction[] public transactions;
    
    //MODIFIERS for TRANSACTIONS
    modifier txExists(uint _txIndex) { //Takes in the txIndex as an input
       require(_txIndex < transactions.length, "TX does not exist"); //If the input index is less than the array length, it exists in the array
       _;
    }
    modifier notExecuted(uint _txIndex) {
       require(!transactions[_txIndex].executed, "Transaction already executed."); //Use bracket and dot notation to check value of executed in struct of given txIndex is false (!)
       _;
    }
    modifier notConfirmed(uint _txIndex) {
        require(!isConfirmed[_txIndex][msg.sender], "TX already confirmed"); //Access double mapping, check if function caller has confirmed given Tx
        _;
    }

    //CONSTRUCTOR to initialize array of owners and confimration requirement upon deployment of contract
    constructor(address[] memory _owners, uint _numConfirmationsRequired) {
        require(_owners.length > 0, "Owners required"); //Ensure that the array of owners is not empty 
        require(_numConfirmationsRequired > 0 && _numConfirmationsRequired <= _owners.length,
            "Not enough confirmations"
        );
        for (uint i = 0; i < _owners.length; i++) { //Owners added as inputs are now added to the state variables. 
            address owner = _owners[i]; //owner at index i stored in variable
            require(owner != address(0), "Not an owner");
            require(!isOwner[owner], "Owner already registered");//checking if address is already an owner using mapping
            isOwner[owner] = true; //If requirements are met, validate owner by setting it to true with mapping
            owners.push(owner); //Add new owner to the state variable array of Owners
        }
        numConfirmationsRequired = _numConfirmationsRequired; //set the number of required confirmations with function input
    }
    
    //FUNCTIONS
    //FALLBACK FUNCTION
    receive() payable external { 
        emit Deposit(msg.sender, msg.value, address(this).balance);
    }
    //TRANSACTING + INTERACTING
    function deposit() payable external {
        emit Deposit(msg.sender, msg.value, address(this).balance);
    }
    function submitTransaction (address _to, uint _value, bytes memory _data) public onlyOwner { //One of the owners will propose transaction
        uint txIndex = transactions.length; //the tx id will be it's index in the transactions array, 1=0, 2=1...
        transactions.push(Transaction({ //Initialize Transaction struct and add to array of transactions
            to: _to, //to value and data set as inputs for the function call
            value: _value,
            data: _data,
            executed: false, //executed set to false as it is a new submission
            numConfirmations: 0 //confirmations set to 0 as is cannot be confirmed before submission
        }));
        emit Submit(msg.sender, txIndex, _to, _value, _data); //Event emitted with function caller, current txIndex and inputs of function
    }
    function confirmTransaction(uint _txIndex)public onlyOwner txExists(_txIndex) notExecuted(_txIndex) notConfirmed(_txIndex) { //Other owners can confirm transaction
        Transaction storage transaction = transactions[_txIndex]; //To update the Transaction struct, get tx by its txIndex, store as 'transaction'
        isConfirmed[_txIndex][msg.sender] = true; //set isConfirmed for the function caller on this particular tx to true.
        transaction.numConfirmations += 1; //Incredment numConfirmations
        emit Confirm(msg.sender, _txIndex);
    }
    function revokeConfirmation(uint _txIndex)public onlyOwner txExists(_txIndex) notExecuted(_txIndex) { //Allow owner to revoke permission for tx before it is notExecuted
         Transaction storage transaction = transactions[_txIndex];
         require(isConfirmed[_txIndex][msg.sender], "tx not confirmed");
         isConfirmed[_txIndex][msg.sender] = false;
         transaction.numConfirmations -= 1;
         emit Revoke(msg.sender, _txIndex);
    }
    function executeTransaction(uint _txIndex) public onlyOwner txExists(_txIndex) notExecuted(_txIndex){
        Transaction storage transaction = transactions[_txIndex];
        require(transaction.numConfirmations >= numConfirmationsRequired, "Not enough Confirmations, cannot execute");
        transaction.executed =true; //Access executed setting of Tx ttruct for this tx and set to true
        (bool success, ) = transaction.to.call{value: transaction.value}(transaction.data); //Execute tx by using the call method
        require(success, "tx failed");
        emit Execute(msg.sender, _txIndex);
    }
}

I made your

more confusing than it needed to be. Cost me a morning and afternoon, but I learnt a good bit amidst the frustration.

Thanks again.

1 Like

Before looking at any hints or other videos on this project, here is my multisig wallet:

pragma solidity ^0.8.7;

contract Wallet {
    address contractCreator;
    uint requiredApprovals;
    uint txCount = 0;
    
    struct Transfer {
        uint trnsfrAmt;
        address payable recipient;
    }
    
    mapping(address => bool) contractOwners;
    mapping(uint => uint) obtainedApprovals;
    mapping(uint => Transfer) transferInfo;
    
    event actionTaken(address actor, string action);
    
    constructor(uint _approvals) {
        emit actionTaken(msg.sender, "Deployed Contract");
        
        requiredApprovals = _approvals;
        
        contractCreator = msg.sender;
    }
    
    modifier onlyCreator {
        require(msg.sender == contractCreator, "Only available to contract creator.");
        _;
    }
    
    modifier onlyOwners {
        require(contractOwners[msg.sender] == true, "Only available to contract owners.");
        _;
    }
    
    function makeDeposit() public payable returns(uint depositAmount) {
        emit actionTaken(msg.sender, "Deposit made!");
        return msg.value;
    }
    
    function setOwners(address _whichAddress) public onlyCreator returns(address addressAdded) {
        require(contractOwners[_whichAddress] != true, "Already an owner.");
        contractOwners[_whichAddress] = true;
        return _whichAddress;
    }
    
    function transferRequest(uint transferValue, address payable _recipient) public onlyOwners returns(uint transferNumber) {
        emit actionTaken(msg.sender, "Transfer requested!");
        
        Transfer memory currentTransfer = Transfer(transferValue,_recipient);
        addTransaction(currentTransfer.trnsfrAmt, currentTransfer.recipient);
        
        return txCount;
    }
    
    function completeTransfer(uint trnsferAmount, address payable recipient, uint transactionNum) internal returns(uint status){
        if(obtainedApprovals[transactionNum] < requiredApprovals) {
            return transactionNum;
        }
        else {
            uint priorBalance = address(this).balance;
            recipient.transfer(trnsferAmount);
            
            assert(trnsferAmount == priorBalance - address(this).balance);
            return address(this).balance;
        }
    }
    
    function addTransaction(uint transferAmount, address payable forWhom) private {
        ++txCount;
        obtainedApprovals[txCount] = 0;
        transferInfo[txCount] = Transfer(transferAmount, forWhom);
        completeTransfer(transferAmount, forWhom, txCount);
    }
    
    function Approve(uint _txID) public onlyOwners returns(string memory) {
        ++obtainedApprovals[_txID];
        if (obtainedApprovals[_txID] < requiredApprovals) {
            return "Approved! Need more approvals.";
        } 
        else {
        Transfer memory currentTransfer = Transfer(transferInfo[_txID].trnsfrAmt,transferInfo[_txID].recipient);
        completeTransfer(currentTransfer.trnsfrAmt,currentTransfer.recipient,_txID);
        return "Approved! Authorized!";
        }
    }
    
    function printTxNumber(uint printNum) internal onlyOwners view returns(uint id) {
        return printNum;
    }
//Testing functions (i.e., not necessary for wallet functionality).
    
    function myAddress() public view returns(address currentAddress) {
        return msg.sender;
    }
       
    function areYouCreator() public view returns(string memory) {
        if (msg.sender == contractCreator) {
            return "Yes.";
        }
        else {
            return "No.";
        }
    }

    function contractBalance() public view returns(uint currentBalance) {
        return address(this).balance;
    }
    
    function howManyMoreApprovals(uint id) public view returns(uint necessaryApprovals) {
        return requiredApprovals - obtainedApprovals[id];
    }
    
    function txCountVal() public view returns(uint) {
        return txCount;
    }
}

I’ve been testing it. I’ve also left some of the testing functions, to make it easier for users to see the status of transactions. Everything seems to work as described. Please let me know what you think!

1 Like
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.0;
contract MultiSign{
   
    address[3] public owners;
    uint public vote;
    uint public balance = 0 ;
    constructor(address[3] memory _owners, uint _enoughVote){
        owners = _owners;
        vote = _enoughVote;
    } 
    
    struct Proposal{
        uint amount;
        address payable to;
        uint vote;
        address[3] Voters;
        uint agree;
    }
    
    Proposal public currentProposal;
    Proposal public emptyProposal = Proposal(0, payable(address(0)), 0, owners, 0);
    Proposal[] public listProposal;
    modifier ownersOnly(){
        require(Check(owners, msg.sender), "You are not owners");
        _;
    }
    event proposalCreated(uint indexed _id, address _creator, address indexed _to, uint indexed _amount);
    
    event proposalEnacted(uint indexed _id, address _enactor, address payable indexed _to, uint indexed _amount);
    
    function Check(address[3] memory list, address it) public pure returns (bool){
        if(list.length == 0){return false;}
        for (uint i = 0; i<3; i++){
            if(list[i] == it){
                return true;
            }
        }
        return false;
    }
    
    function createProposal(address _to, uint _amount) public ownersOnly{
        require(_to!=address(0), "address cannot be 0");
        require(currentProposal.to == address(0), "still voting another proposal");
        require(_amount <= balance, "insufficient balance");
        currentProposal = Proposal(_amount, payable(_to), 1, [msg.sender, address(0), address(0)], 1);
        listProposal.push(currentProposal);
        emit proposalCreated(listProposal.length, address(msg.sender), _to, _amount);
    }
    function getProposal() public view returns (uint, address, uint){
         require(currentProposal.to != address(0), "Not available proposal now!");
        return (currentProposal.amount, currentProposal.to, currentProposal.vote);
    }
    function approveCurrentProposal() public ownersOnly {
        require(currentProposal.to != address(0), "Not available proposal now!");
        require(Check(currentProposal.Voters, msg.sender), "You have voted");
        currentProposal.Voters[currentProposal.vote] = msg.sender;
        currentProposal.vote+=1;
        currentProposal.agree+=1;
        listProposal[listProposal.length-1] = currentProposal;
    }
    function devoteCurrentProposal() public ownersOnly{
        require(currentProposal.to != address(0), "Not available proposal now!");
        require(Check(currentProposal.Voters, msg.sender), "You have voted");
        currentProposal.Voters[currentProposal.vote] = msg.sender;
        currentProposal.vote+=1;
        listProposal[listProposal.length] = currentProposal;
        if(currentProposal.vote - currentProposal.agree >=2){
            cancelProposal();
        }
    }
    function enactProposal() public ownersOnly{
        require(currentProposal.to != address(0), "Not available proposal to enact now!");
        require(currentProposal.vote >=vote, "Not enough Vote");
        currentProposal.to.transfer(currentProposal.amount);
        emit proposalEnacted(listProposal.length, msg.sender, currentProposal.to, currentProposal.amount);
        currentProposal = emptyProposal;
    }
    function cancelProposal() private {
        delete listProposal[(listProposal.length)-1];
        currentProposal = emptyProposal;
    }
    
    function Deposit() payable public ownersOnly{
        balance+=msg.value;
    }

}
1 Like

Hello!
I decided to make a universal wallet solution. The contract owner can create an unlimited number of wallets and specify the owners and the sign limit for each one. The user can see a list of his wallets, a list of incomes, a list of transactions. And he can view and sign each transaction by an ID.

// SPDX-License-Identifier: Unlicense
pragma solidity 0.7.5;
pragma abicoder v2;
import "./Ownable.sol";
import "./Destroyable.sol";

contract MultisigWallet is Ownable, Destroyable {
    
    struct Wallet {
        address[] owners;
        uint8 sigLimit;
        uint256 balance;
        uint[] incomesIdx;
        uint[] transactionsIdx;
    }
    
    struct Transaction {
        uint amount;
        uint walletIndex;
        address payable withdrawTo;
        address[] signatures;
        bool isConfirmed;
        bool isPaid;
    }
    
    struct Income {
        uint amount;
        uint walletIndex;
        address incomeFrom;
    }
    
    struct Owner {
        uint[] myWallets;
    }


    Wallet[] wallets;
    Transaction[] transactions;
    Income[] incomes;

    mapping (address => uint[]) owners;
 
    // check is user a owner of specified wallet
    function isWalletOwner (uint _walletIndex) private view returns(bool) {
        address[] memory walletOwners = wallets[_walletIndex].owners;
        bool isOwner = false;
        for (uint i=0; i<walletOwners.length; i++) {
            if (walletOwners[i]==msg.sender) {
                isOwner = true;
                break;
            }
        }
        return (isOwner);
    }
    
    modifier onlyWalletOwner (uint _walletIndex) {
        require(isWalletOwner(_walletIndex) == true, "Only wallet owners able to see he balance.");
        _;
    }
    
    // modifier check dublicates in array
    modifier checkUniqueArray (address[] memory _addresses) {
        bool hasDublicates = false;
        for (uint8 i=0; i<_addresses.length-1; i++) {
            for (uint8 j=i+1; j<_addresses.length; j++) {
                if (_addresses[i] == _addresses[j]) {
                    hasDublicates = true;
                    break;
                }
            }
        }
        require(hasDublicates == false, "Array of arguments has dublicates.");
        _;
    }
    
    // create new wallet. pass the array of owners and signs limit. 
    function createWallet (address[] memory _addresses, uint8 _sigLimit) public checkUniqueArray(_addresses) {
        require( _addresses.length >= _sigLimit, "Limit of signatures should be less or equil then owners.");

        Wallet memory newWallet = Wallet(_addresses, _sigLimit, 0, new uint[](0), new uint[](0));
        wallets.push (newWallet);
        uint newWalletIndex = wallets.length - 1;
        for (uint8 i=0; i<_addresses.length; i++) {
            owners[_addresses[i]].push(newWalletIndex);
        }
    }
    
    // returns the list of wallet IDs owned by user
    function viewMyWallets () public view returns(uint[] memory) {
        return(owners[msg.sender]);
    }
    
    // get balance by wallet ID. Only for wallet owner
    function getBalance (uint _walletIndex) public view onlyWalletOwner(_walletIndex) returns(uint) {
        return (wallets[_walletIndex].balance);
        
    }
    
    // add money to wallets.
    function addBalance (uint _walletIndex) public payable returns(uint) {
        wallets[_walletIndex].balance += msg.value;
        Income memory newIncome = Income({amount:msg.value, walletIndex:_walletIndex, incomeFrom:msg.sender});
        incomes.push(newIncome);
        wallets[_walletIndex].incomesIdx.push(incomes.length-1);
        return (wallets[_walletIndex].balance);
    }
    
    // view incomes IDs to specified wallet
    function viewWalletIncomes (uint _walletIndex) public view onlyWalletOwner(_walletIndex) returns(uint[] memory) {
        return(wallets[_walletIndex].incomesIdx);
    }
    
    // view income information by income ID
    function viewIncome (uint _incomeIndex) public view returns(Income memory) {
        require(_incomeIndex < incomes.length, "Index is outside of incomes array.");
        return(incomes[_incomeIndex]);
    }

    // we are able to add transaction with any amount, but check later then will be signed    
    function addTransaction (uint _walletIndex, uint _amount, address payable _addressTo) public onlyWalletOwner(_walletIndex) {
        Transaction memory t = Transaction(_amount, _walletIndex, _addressTo, new address[](0), false, false);
        transactions.push(t);
        wallets[_walletIndex].transactionsIdx.push(transactions.length-1);
    }
    
    // view the list of transaction IDs for specified wallet
    function viewWalletTransactions (uint _walletIndex) public view onlyWalletOwner(_walletIndex) returns(uint[] memory) {
        return(wallets[_walletIndex].transactionsIdx);
    }

    // view transaction information by income ID
    function viewTransaction (uint _transactionIndex) public view returns(Transaction memory) {
        require(_transactionIndex<transactions.length, "Index is outside of transaction array.");
        return(transactions[_transactionIndex]);
    }
    
    // sign transaction and transafer money if have enought signs
    function signTransaction (uint _transactionIndex) public returns(bool) {
        Transaction storage t;
        require(_transactionIndex<transactions.length, "Index is outside of transaction array.");
        t = transactions[_transactionIndex];
        require(isWalletOwner(t.walletIndex) == true, "Only wallet owners able to sign transaction.");
        require(t.isConfirmed == false, "This transaction already has been confirmed.");
        bool alreadySign = false;
        for (uint i=0; i<t.signatures.length; i++) {
            if (t.signatures[i] == msg.sender) {
                alreadySign = true;
                break;
            }
        }
        require(alreadySign == false, "You already sign this transaction.");

        // Not accept to sign transaction more then balance amount
        require(t.amount<=wallets[transactions[_transactionIndex].walletIndex].balance, 
                "You don't have enought funds on wallet. Please, check the balance.");

        t.signatures.push(msg.sender);

        //address withdrawTo = transactions[_transactionIndex].withdrawTo;
        if (t.signatures.length >= wallets[t.walletIndex].sigLimit) {
            t.withdrawTo.transfer(t.amount);
            t.isConfirmed = true;
            t.isPaid = true;
        }
        
        return(true);
    }
    
}
2 Likes

Hi!
Here is my solution for the homework, which consists from 2 files.
/*********************************************************************************/
pragma solidity 0.7.5;
pragma abicoder v2;

import “./0_Owners.sol”;
import “./4_Destroyable.sol”;

contract Bank is Owners{
address[] private tempownersvoted ;

mapping(address => uint)  balance;

event depositDone(uint amount, address indexed depositedTo);

struct TrequestTransfer{
    uint id_trx;
    uint amount; 
    address recepient;
    address sender;
    address[] ownersvoted;
}

TrequestTransfer[] public requestTransfers;

constructor( address[] memory _BnewOwners, uint256 _BiNApprovals  ) Owners(_BnewOwners,_BiNApprovals) {
    int i;
}

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

function withdraw(uint amount) public isOwner returns (uint){
    require(balance[msg.sender] >= amount);
    
    balance[msg.sender] -= amount;
    msg.sender.transfer(amount);
    return balance[msg.sender];
} 


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

function initTransferRequest(address recipient, uint amount) public {
    require(balance[msg.sender] >= amount, "Balance not sufficinet"); 
    require( msg.sender != recipient, "Don't transfer money to yourself");
    uint previousSenderBalance = balance[msg.sender];
    
    // if(1==1){
    //     _transfer(msg.sender, recipient, amount);
    // }
    
    // address[] memory tempownersvoted = new address[](1);  
    // tempownersvoted[1] = msg.sender;
    
    delete tempownersvoted;
    tempownersvoted.push(msg.sender);
    
    // create request- for transfer 
    requestTransfers.push(
        TrequestTransfer(requestTransfers.length+1,amount,recipient,msg.sender,tempownersvoted)
    );
    
    //assert(balance[msg.sender] == previousSenderBalance - amount);
}

function doVote(uint _id_trx) public {

    // check if there is at least one request  
    require( requestTransfers.length>0  , "There is no any requests;");
    
    // find request 
    bool isFind=false;
    //address[] memory temp_ownersvoted;
    uint i;
    uint id_request;
    for (i = 0; i < requestTransfers.length && !isFind ; i++) {
        if( requestTransfers[i].id_trx==_id_trx  ){
            isFind=true;
            id_request=i;
        }
    } 
    
    // check if request is found 
    require( isFind  , "Request is not found;");

    // check if you could make vote 
    require( mIsOwnerAddress[msg.sender]==true  , "Current address doesn't have previligious to vote");
     
    
    // make vote - add vote to the list 
    requestTransfers[id_request].ownersvoted.push(msg.sender);
    
    //check if we all votes are done.Operator "<=" for testing
    if(requestTransfers[id_request].ownersvoted.length >= iNApprovals){
        dotransfer( id_request ) ;
        //delete requestTransfers[id_request];
        
        // Move the last element into the place to delete
        requestTransfers[id_request] = requestTransfers[requestTransfers.length - 1];
        // Remove the last element
        requestTransfers.pop();
    }
}


function dotransfer( uint _id_request) internal {
    require(balance[requestTransfers[_id_request].sender] >= requestTransfers[_id_request].amount, "Balance not sufficinet"); 
    require( requestTransfers[_id_request].sender != requestTransfers[_id_request].recepient, "Don't transfer money to yourself");
    uint previousSenderBalance = balance[requestTransfers[_id_request].sender]; 
    

    payable(requestTransfers[_id_request].sender).transfer(requestTransfers[_id_request].amount);
    balance[requestTransfers[_id_request].sender] -= requestTransfers[_id_request].amount;
    balance[requestTransfers[_id_request].recepient] += requestTransfers[_id_request].amount;
    
    assert(balance[requestTransfers[_id_request].sender] == previousSenderBalance - requestTransfers[_id_request].amount);
}


function getRequestedTransfers() public view returns (TrequestTransfer[] memory){
    return requestTransfers;
}

}
/*********************************************************************************/
// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.7.0 <0.9.0;

/**
 * @title Owner
 * @dev Set & change owner
 */
contract Owner {

    address private owner;
    
    // event for EVM logging
    event OwnerSet(address indexed oldOwner, address indexed newOwner);
    
    // modifier to check if caller is owner
    modifier isOwner() {
        // If the first argument of 'require' evaluates to 'false', execution terminates and all
        // changes to the state and to Ether balances are reverted.
        // This used to consume all gas in old EVM versions, but not anymore.
        // It is often a good idea to use 'require' to check if functions are called correctly.
        // As a second argument, you can also provide an explanation about what went wrong.
        require(msg.sender == owner, "Caller is not owner");
        _;
    }
    
    /**
     * @dev Set contract deployer as owner
     */
    constructor() {
        owner = msg.sender; // 'msg.sender' is sender of current call, contract deployer for a constructor
        emit OwnerSet(address(0), owner);
    }

    /**
     * @dev Change owner
     * @param newOwner address of new owner
     */
    function changeOwner(address newOwner) public isOwner {
        emit OwnerSet(owner, newOwner);
        owner = newOwner;
    }

    /**
     * @dev Return owner address 
     * @return address of owner
     */
    function getOwner() external view returns (address) {
        return owner;
    }
}

/************************************************************************************************/

1 Like

hey @faraday very cool solution mang. I like the wallet struct which allows a user to create multiple wallets. However if you were to make a DApp of this you actually would not need the wallet struct. The way you would make multiple instances could be to for example, use web3.js. There is a function in the web3.js library called contract.deploy() which when called deploys your smart contract. You can then use this function to deply multiple instances of your contract. This method would be better because each new contract instance would have its own unique address. Doing it this way using the wallet struct, all instances of the wallet struct all jave the same contract addres.

However you will learn more about web3.js in other courses. But wow man very good solution for this wallet very creative and good thinking on the front of wanting to create multiple wallets. Keep it up man excellent work

1 Like

Hi all,
I have a question about the project.
Actually I do not understand one topic and do not really know how to ask about it, so do not please be understanding.

Every wallet has an address. For example I have a MetaMask wallet and I have an address to my account in the MetaMask wallet.
In our project we have three owner and their wallet account .
So, do we have 4 addresses for one account, one for the account and 3 for owners in that case?
Or we have 3 addresses for the account, only for owners and this 3 addresses?
Or we have just one address for the account and all owners share it?
Or this is up to our implementation?

For my the most logical solution is one address for the account for all users.
How would we store the information about about the owner? as IDs in uint?

Thank you for in advance for help.

Kacper Pajak