Project - Multisig Wallet

pragma solidity 0.7.5;
pragma abicoder v2;

contract Wallet {
    address[] public owners;
    uint limit; 
    
    struct Transfer{
        uint amount;
        address payable receiver;
        uint approvals;
        bool hasBeenSent;
        uint id;
    }
    
    event TransferRequestCreated (uint _id, uint _amount, address _initiator, address _receiver);
    event ApprovalReceived (uint _id, uint _approvals, address _approver);
    event TransferApproved (uint _id);
    
    Transfer[] transferRequests;
    
    mapping(address => mapping(uint => bool)) approvals; 
    
    modifier onlyOwners(){
        require (inOwnersArray() == true);
    _;
    }
    
    function inOwnersArray () private view returns (bool){ //additional function that checks if msg.sender is in the owners array.
        bool inArray;
        for (uint i = 0; i < owners.length; i++){
            if (owners[i] == msg.sender){
                inArray = true;
                break;
            }
            else {inArray = false;}
        }
        return inArray;
    }
   
    constructor(address[] memory _owners, uint _limit) {
        owners = _owners;
        limit = _limit;
    }
    
    function deposit() public payable { 
        mappingBalances[msg.sender] += msg.value;
    }
    
    //We keep track of balances that have been changed after transfer requests go through.
    mapping (address => uint) mappingBalances; 
    
    function getAllBalances () public view returns (uint){ 
        return mappingBalances[msg.sender];
        
    }
    
        
    function createTransfer(uint _amount, address payable _receiver) public onlyOwners {
        require(_amount <= mappingBalances[msg.sender]); // Require for the sender's balance to be able to cover the transfer.
        
        emit TransferRequestCreated(transferRequests.length, _amount, msg.sender, _receiver);
        transferRequests.push(Transfer(_amount, _receiver, 0, false, transferRequests.length)); // 0 approvals at the start.
    }
    
    //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 onlyOwners {
        require(approvals[msg.sender][_id] == false);
        if (transferRequests[_id].approvals < limit){
            transferRequests[_id].approvals++;
            emit ApprovalReceived (_id, transferRequests[_id].approvals, msg.sender);
    }
            
            approvals[msg.sender][_id] = true;
            if (transferRequests[_id].approvals >= limit){
                transferRequests[_id].receiver.transfer(transferRequests[_id].amount); 
                transferRequests[_id].hasBeenSent = true;
                
                mappingBalances[transferRequests[_id].receiver] += transferRequests[_id].amount;
                
                emit TransferApproved (_id);
            }
        }
        
    
        //Should return all transfer requests
    function getTransferRequests() public view returns (Transfer[] memory){
        return transferRequests;
    }
    
    
}

This is my solution. I’d like some feedback on whether this could actually be used in practice and what are the things that have to be fixed.

I used the template that Filip provided and added a few more features.

There’s a separate function that checks whether msg.sender is included in the owners array.

I added a mapping called mappingBalances that’s supposed to keep track of all the balances after different transfers are confirmed and go through.
It’s also required for the msg.sender balance to have enough funds to be able to cover a requested transfer.
However, I wasn’t able to figure out how to reduce the msg.sender balance for the amount of money sent in a transfer that went through. Any tips?

Thanks :cowboy_hat_face:

1 Like

Hi @evasilev, hope you are well.

I have run your code, it compiles and deploy without any error, your initiateTransfer functions works as expected, also the viewMempool, approveTransaction and contractBalance functions.

The only issues that I see so far is your deposit function, which does not have any logic on its body. You might wanna check it, because your functions does not manage any funds for now.

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

Carlos Z.

1 Like

Hi @Andro, hope you are well.

I’m having issues after deploying your contract, i need to input at least 2 accounts to initialize your contract, but apparently there is an error on the argument type, also are you sure in your constructor, is that the best way to bind data into an array?

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

Carlos Z.

Hi Carlos, thanks for answering.

I wasn’t sure about the constructor so I watched Filip’s Full project code video and that’s the way he did it. It worked when I tested the program so I didn’t change it. What’s the correct way to input the owners addresses?

I’m not sure why it doesn’t work for you. I just copy/pasted the code into remix and I deployed the contract with inputting 1, 2, or 3 addresses in the array.
I tested it with 3 addresses and 2 as the number of users who need to approve every transfer for it to go through (uint limit).
The only thing that didn’t work for me is keeping track of the balances after transfers are confirmed.

Thanks :crossed_fingers:

1 Like

Hi,
I get an error when I deploy this constructor in Remix. Please advise. From what I have been able to find, I should be able to pass parameters in a constructor.

ERROR (when deploying):
creation of multiSigWallet errored: Error encoding arguments: Error: invalid address (argument=“address”, value="", code=INVALID_ARGUMENT, version=address/5.0.5) (argument=null, value="", code=INVALID_ARGUMENT, version=abi/5.0.7)

CODE:
contract multiSigWallet {
// setup the owners of the wallet.
address owner1; address owner2; address owner3; uint approvalLimit;
constructor(address _owner1, address _owner2, address _owner3, uint _limit){
owner1 = _owner1;
owner2 = _owner2;
owner3 = _owner3;
approvalLimit = _limit;
}

1 Like

Multisig owners confusion.
Please explain how a user should be used in Remix. I assume I have to select an owner address when I approve a withdrawal. So I created a three separate contracts and used those addresses in my wallet’s Constructor(). Approvals worked fine. However when I closed and reopened Remix IDE I could no longer find these user addresses in the ACCOUNT drop-down in the DEPLOY view. I ended up using three existing addresses in the ACCOUNT drop-down as these addresses seem to always be in the list.

1 Like

Multi Sig Wallet project:

WORKFLOW

  1. Set up a pending withdrawal
  2. Approved users can log in and approve whenever they want.
  3. Withdraw - works once there are 2 or more approvals (and enough wei exists)
  4. Withdrawal is flagged as done (recorded in an array)

LIMITATIONS:
Only one pending withdrawal allowed at a time.
Constructor is hard-coded with owners and limit - I could not get parameters working with a constructor (see constructor that is commented out).

CONTRACT SUMMARY
constructs
Withdrawal struct - to create a withdrawal object
utxo dictionary - to keep track of deposts
withdrawals array - to hold all withdrawal objects created.
constructor - sets owners and approval limit
functions
deposit - allows wei deposits from anyone
getWalletBalance - display balance
queueWithdrawal - creates a pending withdraw
viewWithdrawal - displays pending withdrawal (object)
isApprover - private - returns approver number or 0 if user is not an approver
approve - Approves withdrawal (if user is approver 1, 2 or 3)
walletAddr - displays wallet address (for testing)
senderAddr - displays user address (for testing)

CODE
//“SPDX-License-Identifier: UNLICENSED”
pragma solidity 0.7.5;
pragma abicoder v2;
import “./Ownable.sol”;

contract multiSigWallet is Ownable {

// setup the owners of the wallet. 
address owner1; address owner2; address owner3; uint approvalLimit;

/* constructor(address _owner1, address _owner2, address _owner3, uint _limit){
owner1 = _owner1;
owner2 = _owner2;
owner3 = _owner3;
approvalLimit = _limit;
}
*/
constructor(){
owner1 = 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2;
owner2 = 0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db;
owner3 = 0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB;
approvalLimit = 2;
}

// setup a dictionary to keep track of unspent ETH
mapping(address => uint) utxos;

// establish a pending withdrawal class
struct Withdrawal{
    address to;
    uint amount;
    string note;
    bool owner1Approved;
    bool owner2Approved;
    bool owner3Approved;
    bool done;
}


// create an array of withdrawals. Only one (the last) withdrawal can be pending. All otheres are done == true.
Withdrawal[] withdrawals;  // an array of withdrawals

// allow any address to deposit money into the wallet. keep track of the utxo addresses
function deposit() public payable returns (uint){
    utxos[msg.sender] += msg.value;
    return utxos[msg.sender];
}

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

// Create a pending withdrawal for approval
function queueWithdrawal(address _to, uint _amount, string memory _note) public returns (string memory) {
    if (withdrawals.length == 0 || withdrawals[withdrawals.length-1].done == false){
       Withdrawal memory withdrawal = Withdrawal(_to, _amount, _note, false, false, false, false);
       withdrawals.push(withdrawal);
    }
    return "Withdrawal Queued";
}

// Show pending withdrawal contents
function viewWithdrawal() view public returns (Withdrawal memory){
    if(withdrawals.length >0){
        return withdrawals[withdrawals.length-1];
    }
    else{
        Withdrawal memory withdrawal = Withdrawal(address(this),0, "no pending withdrawals", false, false, false, false);
        return withdrawal;
    }
}


// Approve withdrawal
function approve() public returns(string memory){
    uint approver = isApprover();
    require(approver > 0, "Sorry, you are not authorized");
    require(withdrawals.length > 0, "Nothing to approve");
    require(withdrawals[withdrawals.length-1].done = true, "Nothing to approve");
    string memory _reply;
    if (approver == 1 && withdrawals[withdrawals.length-1].owner1Approved == false){
       withdrawals[withdrawals.length-1].owner1Approved = true;
       _reply = "Approved";
    }else if (approver == 2 && withdrawals[withdrawals.length-1].owner2Approved == false){
       withdrawals[withdrawals.length-1].owner2Approved = true;
       _reply = "Approved";
    }else if (approver ==3 && withdrawals[withdrawals.length-1].owner3Approved == false){
       withdrawals[withdrawals.length-1].owner3Approved = true;
       _reply = "Approved";
    }else { _reply = "You already approved";
    }
    return _reply;
}

// validate a user is an approver
function isApprover() private view  returns (uint){
    if (msg.sender==owner1){
        return 1;
    }
    else if (msg.sender==owner2){
        return 2;
    }
    if (msg.sender==owner3){
        return 3;
    }
    else {return 0;}
}


// Withdraw approved funds
function withdraw() public onlyOwner returns (uint) {
    // Requirements
    require(withdrawals.length>0, "No withdrawal is pending");
    require(withdrawals[withdrawals.length-1].done == false, "No withdrawal is pending");
    require(address(this).balance >= withdrawals[withdrawals.length-1].amount, "Insufficient funds");
    // sum approvals
    uint approvals;
    if (withdrawals[withdrawals.length-1].owner1Approved){
        approvals++;
    }
    if (withdrawals[withdrawals.length-1].owner2Approved){
        approvals++;
    }
    if (withdrawals[withdrawals.length-1].owner2Approved){
        approvals++;
    }
    require(approvals > 1,"Cannot withdraw. Need 2 signatures.");

    // do the transfer
    msg.sender.transfer(withdrawals[withdrawals.length-1].amount);
    
    // close approved withdrawal
    withdrawals[withdrawals.length-1].done = true;
    
    return address(this).balance;
}


function walletAddr() public view returns (address){
    return address(this);
}
function senderAddr() public view returns (address){
    return msg.sender;
}

}

1 Like

Hi @thecil,

Thanks for going through my code.

The Deposit function is there simply to add value/ether into contract’s balance. I’ve tested it with my contractBalance function and it works as expected, but maybe this method is not correct?

1 Like

I watched only the introduction video. Some of the wording in the requirements threw me off, so I may have done some unnecessary things here, but it all works as expected.

First, my Ownable.sol file:

pragma solidity 0.8.0;

contract Ownable {
    mapping(address => bool) owners;
    
    uint approvalsNeeded;
    
    event ownerAdded(address creator, address added);
    
    modifier onlyOwner {
        require(owners[msg.sender], "This function is restricted to wallet owners");
        _; //run the function
    }

    function addOwner(address _newOwner) public onlyOwner {
        owners[_newOwner] = true;
        emit ownerAdded(msg.sender, _newOwner);
    }
    
    constructor(){
        owners[msg.sender] = true;
        approvalsNeeded = 2;
    }
}

I first created a mapping to hold the owner’s addresses. Any address that has not been added to the mapping will return false, so adding an owner is as simple as setting the Boolean to true, which I do in the addOwner function as well as the constructor. I changed the require statement in onlyOwners to include any owner who has been added to the mapping.

Now on to my wallet:

pragma solidity 0.8.0;
pragma abicoder v2;
import "./Ownable.sol";


contract MyWallet is Ownable{
    event depositDone(uint amount, address indexed depositor);
    event Received(address indexed sender, uint amount);
    event Requested(address request, address recipient, uint amount, string memo);
    event Approved(address approver,address request, address recipient, uint amount, string memo);
    event Transferred(address recipient, uint amount, string memo);
    
    struct Request {
        address sender;
        address payable recipient;
        uint amount;
        string memo;
        uint approvals;
    }
    
    Request[] requests;
    
    receive() external payable {
        emit Received(msg.sender, msg.value);
    }
    
    function deposit() public payable returns (uint){
        emit depositDone(msg.value, msg.sender);
        return address(this).balance;
    }
    
    function requestTransfer(address payable _to, uint _amount, string memory _memo) public onlyOwner{
        Request memory newRequest = Request(msg.sender, _to, _amount, _memo, 1);
        requests.push(newRequest);
        emit Requested(msg.sender, _to, _amount, _memo);
    }
    
    function approveTransfer(uint _index) public onlyOwner{
        require(msg.sender != requests[_index].sender, "You Can't Approve Your Own Request!");
        requests[_index].approvals++;
        if(requests[_index].approvals >= approvalsNeeded){
            _transfer(_index);
        }
        emit Approved(msg.sender, requests[_index].sender, requests[_index].recipient, requests[_index].amount, requests[_index].memo);
    }
    
    function _transfer(uint _index) internal onlyOwner{
        require(address(this).balance >= requests[_index].amount, "You don't have enough ether for this transfer");
        address payable receiver = requests[_index].recipient;
        receiver.transfer(requests[_index].amount);
        emit Transferred(receiver,requests[_index].amount, requests[_index].memo);
        delete requests[_index];
        
    }
    
    function getBalance() public view onlyOwner returns (uint){
        return address(this).balance;
    }
    
    function getNumRequests() public onlyOwner view returns (uint){
        return requests.length;
    }
    
    function requestLookup(uint _index) public view onlyOwner returns (address, address, uint, string memory, uint){
        return (requests[_index].sender, requests[_index].recipient, requests[_index].amount,requests[_index].memo,requests[_index].approvals);
    }
}

I created a struct for transfer requests that included all the data that I thought should be included, including a memo string to communicate what the transfer is for. I also created events for each step along the chain: request, approval, transfer.

Since multiple requests may be pending at a given time, I created a dynamic array of the pending requests. I am not very happy with how I handled the request lookup here, but I would handle it much differently with a graphical interface, which I imagine something like this would use. Essentially you query how many requests are in the array and then you can query each request by it’s index.

Approval of a request requires that the user is an owner and is not the owner who made the initial request. After a request is approved and transferred, the request is deleted from the array.

2 Likes

Hey. This was harder than I thought it will be. I first tried it on my own, but found myself peeking in the forum, so I decided to see some hints in the videos. I had to copy most of the structure, but I filled it mostly myself. I have a question about the last function I did - getOpenRequests(). It throws me an error “Member push is not available in struct MultiSigWallet.TxRequest memory[] memory outside of storage.”

pragma solidity 0.7.5;
pragma abicoder v2;

import "./Ownable.sol";

contract MultiSigWallet is Ownable {
    
    address[] public owners;
    uint n; 
    
    struct TxRequest {
        uint txId;
        address payable receiver;
        uint amount;
        uint confirmations;
        bool ifSent;
    }

    TxRequest[] requests;

    mapping(address => mapping(uint => bool)) approvals;
    
    modifier ownersOnly() {
        bool answer = false;
        for (uint i=0; i < owners.length; i++){
            if(owners[i] == msg.sender){
                answer = true;
                break;
            }
            else {
                answer = false;
            }
        }
        require(answer == true);
        _;
    }

    constructor(address[] memory _owners, uint _n){
        owners = _owners;
        n = _n;
    }
    
    mapping(address => uint) balances;
    
    function addOwner(address _newAddress) private onlyOwner {
        owners.push(_newAddress);
    }
    
    function updateLimit(uint _n) private onlyOwner {
        require(_n <= owners.length);
        n = _n;
    }
    
    function deposit(uint _amount) public payable {
        balances[msg.sender] += _amount;
    }
    
    function createTransfer(uint _amount, address payable _receiver) public ownersOnly{
        requests.push(
            TxRequest(requests.length, _receiver, _amount, 0, false));
    }
    
    function approve(uint _idApproval) public ownersOnly{
        require(approvals[msg.sender][_idApproval] == false);
        require(requests[_idApproval].ifSent = false);
        
        approvals[msg.sender][_idApproval] = true;
        requests[_idApproval].confirmations += 1;
        if(requests[_idApproval].confirmations >= n){
            requests[_idApproval].ifSent = true;
            requests[_idApproval].receiver.transfer(requests[_idApproval].amount);
        }
    }
    
    function getAllRequests() public view returns(TxRequest[] memory){
        return requests;
    }
    /*
    function getOpenRequests() public view returns(TxRequest[] memory){
        TxRequest[] memory reqs;
        for(uint i = 0; i <= requests.length; i++){
            if(requests[i].ifSent == false){
                reqs.push(requests[i]);
            }
        }
        return reqs;
    }
    */
}

The method that you are using is valid, deposit is just to receive msg.value to the contract, then you can use initiate a transfer and wait for approvals, it works for funds managing on the contract only. :nerd_face:

Carlos Z.

1 Like

Would you please share an screenshot that show how are you inputing the values to the array?
I tried different ways and is not working for me :confused:

Carlos Z

1 Like

Hi @Mirran, hope you are well.

Would be great if you can share properly your code so i can copy/paste to test it, i encounter many errors copying the way you share it.

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


function formatText(){
X	
let words = “I’m a preformatted Text box, Please use me wisely!”

}

prefromatted_text-animated

Carlos Z.

Amazing Job @Conor_Neary! Congratulations :partying_face:

I have tested:

  • Deploy and addOwner.
  • Deposit funds, then ask for a transfer.
  • Approve the transfer from owner to receiver.

All functions are working great, can receive deposits, requrest a transfer and approve it, then funds got sent to the receiver.

Carlos Z

//"SPDX-License-Identifier: UNLICENSED"
/*
Multi Sig Wallet project:

WORKFLOW
1) Set up a pending withdrawal 
2) Approved users can log in and approve whenever they want. 
3) Withdraw - works once there are 2 or more approvals (and enough wei exists)
4) Withdrawal is flagged as done (recorded in an array)

LIMITATIONS:
Only one pending withdrawal allowed at a time. 
Constructor is hard-coded with owners and limit - I could not get parameters working with a constructor (see constructor that is commented out). 

CONTRACT SUMMARY
constructs
	Withdrawal struct - to create a withdrawal object
	utxo dictionary     - to keep track of deposts
	withdrawals array - to hold all withdrawal objects created.
	constructor          - sets owners and approval limit
functions
	deposit                - allows wei deposits from anyone
	getWalletBalance - display balance
	queueWithdrawal  - creates a pending withdraw
	viewWithdrawal     - displays pending withdrawal (object)
	isApprover            - private - returns approver number or 0  if user is not an approver 
	approve                - Approves withdrawal (if user is approver 1, 2 or 3)
	walletAddr            - displays wallet address (for testing)
	senderAddr          - displays user address (for testing)

*/

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

contract multiSigWallet is Ownable {    

    // setup the owners of the wallet. 
    address owner1; address owner2; address owner3; uint approvalLimit;
/*    constructor(address memory _owner1, address memory _owner2, address memory _owner3, uint _limit){
         owner1 = _owner1;
         owner2 = _owner2;
         owner3 = _owner3;
         approvalLimit = _limit;
    }
*/
    constructor(){
         owner1 = 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2;
         owner2 = 0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db;
         owner3 = 0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB;
         approvalLimit = 2;
    }

    // setup a dictionary to keep track of unspent ETH
    mapping(address => uint) utxos;

    // establish a pending withdrawal class
    struct Withdrawal{
        address to;
        uint amount;
        string note;
        bool owner1Approved;
        bool owner2Approved;
        bool owner3Approved;
        bool done;
    }

    
    // create an array of withdrawals. Only one (the last) withdrawal can be pending. All otheres are done == true.
    Withdrawal[] withdrawals;  // an array of withdrawals

    // allow any address to deposit money into the wallet. keep track of the utxo addresses
    function deposit() public payable {
        utxos[msg.sender] += msg.value;
    }
    
        //should only allow people int he owners list to continue this execution
    modifier onlyOwners() {
        bool owner = false;
        if (msg.sender==owner1){
            owner = true;
        }
        else if (msg.sender==owner2){
            owner = true;
        }
        else if (msg.sender==owner3){
            owner = true;
        }
        require(owner == true);
        _;
    }
    
    // Show wallet balance
    function getWalletBalance() public onlyOwners view returns (uint){
        return address(this).balance;
    }

    // Create a pending withdrawal for approval
    function queueWithdrawal(address _to, uint _amount, string memory _note) public onlyOwners returns (string memory) {
        if (withdrawals.length == 0 || withdrawals[withdrawals.length-1].done == false){
           Withdrawal memory withdrawal = Withdrawal(_to, _amount, _note, false, false, false, false);
           withdrawals.push(withdrawal);
        }
        return "Withdrawal Queued";
    }

    // Show pending withdrawal contents
    function viewWithdrawal() view public onlyOwners returns (Withdrawal memory){
        if(withdrawals.length >0){
            return withdrawals[withdrawals.length-1];
        }
        else{
            Withdrawal memory withdrawal = Withdrawal(address(this),0, "no pending withdrawals", false, false, false, false);
            return withdrawal;
        }
    }

    
    // Approve withdrawal
    function approve() public onlyOwners returns(string memory){
        uint approver = isApprover();
        require(approver > 0, "Sorry, you are not authorized");
        require(withdrawals.length > 0, "Nothing to approve");
        require(withdrawals[withdrawals.length-1].done = true, "Nothing to approve");
        string memory _reply;
        if (approver == 1 && withdrawals[withdrawals.length-1].owner1Approved == false){
           withdrawals[withdrawals.length-1].owner1Approved = true;
           _reply = "Approved";
        }else if (approver == 2 && withdrawals[withdrawals.length-1].owner2Approved == false){
           withdrawals[withdrawals.length-1].owner2Approved = true;
           _reply = "Approved";
        }else if (approver ==3 && withdrawals[withdrawals.length-1].owner3Approved == false){
           withdrawals[withdrawals.length-1].owner3Approved = true;
           _reply = "Approved";
        }else { _reply = "You already approved";
        }
        return _reply;
    }

    // validate a user is an approver
    function isApprover() private view  returns (uint){
        if (msg.sender==owner1){
            return 1;
        }
        else if (msg.sender==owner2){
            return 2;
        }
        if (msg.sender==owner3){
            return 3;
        }
        else {return 0;}
    }

    
    // Withdraw approved funds
    function withdraw() public onlyOwner returns (uint) {
        // Requirements
        require(withdrawals.length>0, "No withdrawal is pending");
        require(withdrawals[withdrawals.length-1].done == false, "No withdrawal is pending");
        require(address(this).balance >= withdrawals[withdrawals.length-1].amount, "Insufficient funds");
        // sum approvals
        uint approvals;
        if (withdrawals[withdrawals.length-1].owner1Approved){
            approvals++;
        }
        if (withdrawals[withdrawals.length-1].owner2Approved){
            approvals++;
        }
        if (withdrawals[withdrawals.length-1].owner2Approved){
            approvals++;
        }
        require(approvals > 1,"Cannot withdraw. Need 2 signatures.");

        // do the transfer
        msg.sender.transfer(withdrawals[withdrawals.length-1].amount);
        
        // close approved withdrawal
        withdrawals[withdrawals.length-1].done = true;
        
        return address(this).balance;
    }
    

    function walletAddr() public view returns (address){
        return address(this);
    }
    function senderAddr() public view returns (address){
        return msg.sender;
    }

}
1 Like

You contract looks very good, you have made a very good job. :nerd_face:

Some suggestions that can improve your contract a little further:

your function queueWithdrawal returns an string value, string variables or values are not that friendly with solidity, which could end in a huge gas cost at the execution of the function, maybe you want to emit an event instead of that return string.

In the onlyOwners modifier, your require could return a message to know why the transaction get reverted. require(owner == true, "only Owners allow");

I tried to withdraw some funds but i was not able to, the signatures approval is working but i was not able to finish the withdraw function, tried different ways and different contract owners. Maybe you can give me another point of view to try it :nerd_face:

Overall you have made a very amazing job! :partying_face:

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

Carlos Z.

Hi,

I’m wondering how I can apply the modifier check for onlyOwner where the owners applied to the constructor as an array:

    address[] public owners;     

    constructor(address[] memory _owners, uint _signlimit) {
        owners = _owners;
        signlimit = _signlimit;

    }

    modifier onlyOwners {
        for(uint i = 0; i < owners.length; i++) {
            if(owners[i] == msg.sender) {
                _;
            }
            else {
                revert("You don't have permission to check owners.");
            }
            
        }
    }

However, when I call a function that has onlyOwner modifier, it always reverts, no matter from which address I call the function. How can I fix that?

1 Like

Hello everyone, here is my contract.

pragma solidity 0.7.5;
contract ContractCreator{
    uint numberOfAddresses;
    uint numberOfApprovals;
    uint walletBalance;
    uint signatureCount;
    struct User{
        address _address;
    }
    User[] users;
    string message_1 ="User has been added already!!!";
    string message_2 ="No inside transfers, only outside!!!";
    string message_3 ="You can not vote!!! Who are you?";
    mapping(address => bool) signed;
    mapping(address => uint) balance;
    modifier onlyNewUsers{
        if(users.length != 0){
            _forLoop(msg.sender, message_1);} _;
    }
    modifier onlyWalletOwners{
        bool owner;
        for(uint i=0; i < users.length; i++){
            if(msg.sender != users[i]._address){continue;}
            else if(msg.sender == users[i]._address){owner = true; break;}
        } 
        require(owner, message_3);
         _;
    }
    modifier OutsideOnly(uint amount, address recipient){
         _forLoop(recipient,message_2);_;
    }
    modifier SignatureCount {
        for(uint i=0; i < users.length; i++){
             if(signed[users[i]._address] == true){signatureCount +=1;}
             else{continue;}
         }
         require(signatureCount == numberOfApprovals, "Not enough Signatures");_;
    }
    function _transfer(uint amount) internal {
        walletBalance -= amount;
    }
    function _voteReset()internal{
        for(uint i=0; i < users.length; i++){
             signed[users[i]._address] = false;
        }
        signatureCount = 0;
    }
    // The _forLoop checks for new or not wallet owners addresses
    function _forLoop(address sender, string memory message)internal view {
        for(uint i=0; i < users.length; i++){
            if(sender != users[i]._address){continue;}
            else{_revert(message);}}
    }
    function _revert(string memory _message)pure internal{
        revert(_message);
    }
}
pragma solidity 0.7.5;
import "./ContractCreator.sol";

contract Wallet is ContractCreator{
    constructor(uint _number, uint _signature){
        numberOfAddresses=_number;
        numberOfApprovals= _signature;
        assert(_number >= _signature);
    }
    event UserCreation(address _address, uint _howManyUsers, uint _howmanyAddr, bool signed);
    event DepositDone(address _address, uint _amount);

    function createUser() public onlyNewUsers{
        require(users.length < numberOfAddresses, "Too many users!!!");
        users.push(User(msg.sender));
        signed[msg.sender]=false;
        emit UserCreation(msg.sender, users.length, numberOfAddresses,signed[msg.sender]);
    }
    function deposit() public payable returns (uint)  {
        balance[msg.sender] += msg.value;
        walletBalance +=msg.value;
        emit DepositDone(msg.sender, msg.value);
        return balance[msg.sender];
    }
    function totalWalletBalance() public view returns(uint){
        return walletBalance;
    }
    function voteToggle()public onlyWalletOwners{
        for(uint i=0; i < users.length; i++ ){
            if(msg.sender == users[i]._address){
                signed[users[i]._address] = !signed[users[i]._address];
                break;}
            else{continue;}
        }
    }
    function checkTheVote() public view returns(bool){
        if(signed[msg.sender]){return true;}
        else{return false;}
    }
    function transfer(uint amount, address recipient) public OutsideOnly(amount, recipient) SignatureCount{
        require(walletBalance >= amount, "Not sufficient balance!!!");
        uint previousSenderBalance = walletBalance;
        _transfer(amount);
        assert(walletBalance == previousSenderBalance - amount);
        _voteReset();
    }
}
1 Like

This project opened my eyes. i did not believe that i can do this

pragma solidity 0.7.5;

contract Multiwallet{
address private owner1;
address private owner2;
address private owner3;
uint private ContrctBalance;

event depositDone(uint amount, address indexed DepositedTo);
event SubmitWit(uint amount, address indexed Adr,uint txid,address reqstr);
event SignedTran(uint amount, address indexed Adr,uint txid,address reqstr);

//Transaction pools struct
struct TransactionPool{
    address payable To;
    uint Amount;
    bool Sing1;
    address SingAdr1;
    bool Sing2;
    address SingAdr2;
    uint txid;
    bool status;
}

TransactionPool[] transactionlog;

constructor(address _owner2,address _owner3){
    owner1 = msg.sender;
    owner2 = _owner2;
    owner3 = _owner3;
}

function Deposit() public payable{
    require(msg.value > 0);
    
    _transfer(msg.value);
    
    emit depositDone(msg.value,msg.sender);
}

function _transfer( uint _amt) private{
    ContrctBalance  += _amt;
}

function SubmitTrn(uint _SAmt,address payable _adrs)public returns(uint){
    require(ContrctBalance >= _SAmt ,"Insufficient Balance");
    TransactionPool memory newTransaction;
    newTransaction.To = _adrs;
    newTransaction.Amount = _SAmt;
    newTransaction.Sing1 = false;
    newTransaction.SingAdr1 = address(0);
    newTransaction.Sing2 = false;
    newTransaction.SingAdr2 = address(0);
    newTransaction.status = false;
    newTransaction.txid = transactionlog.length;
    
    transactionlog.push(newTransaction);
    
    emit SubmitWit(_SAmt,_adrs,transactionlog.length,msg.sender);
    
    return(transactionlog.length);
}

function signMsg(uint idex) public Owers{
    require(transactionlog[idex].status != true ,"Transaction already signed");
    if(transactionlog[idex].Sing1 == false){
        transactionlog[idex].Sing1 = true;
        transactionlog[idex].SingAdr1 = msg.sender;
    }else{
        transactionlog[idex].Sing2 = true;
        transactionlog[idex].SingAdr2 = msg.sender;
        SendTrn(idex);
        transactionlog[idex].status = true;
    }
    
}

function SendTrn(uint _idex)private{
    
    transactionlog[_idex].To.transfer(transactionlog[_idex].Amount);
    ContrctBalance -= transactionlog[_idex].Amount

    ;
    
}

function GetContractBal() public view returns(uint){
    return(ContrctBalance);
}

modifier onlyOwner{
    require(owner1 == msg.sender,"Your not the owner");
    _;
}

modifier Owers{
    require(owner1 == msg.sender || owner2 == msg.sender || owner3 == msg.sender,"You can't sign this transaction ");
    _;
}

}

2 Likes

Hi @Elekko, hope you are great.

Remember that modifiers rely on a require condition to then approve the execution of the function.
You can have the loop to iterate over the owners array but you cant use the if condition to validate the execution of the function.

Here is an example so you can have a better understanding:

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

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

Carlos Z.

2 Likes