Project - Multisig Wallet

Hello everyone! I just finished my multisig wallet, I tried everything that i could imagin to test the requirements given. So far i just have a problem when an address that’s not an owner tries to submit a new owner or tx or aprove a tx, the log says it worked and gas was payed but when i chek the owners or transactions its not there, so it means that the onlyOwner part is working, but not entirely? i really dont know whats going on there.

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

contract Multisigwallet {

struct PendTx{
    uint amount;
    address cont;
    uint votes;
    address payable recipient;
}

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

PendTx[] pending;

uint walletBalance=address(this).balance;

struct Owner{
    address payable owner;
} 

Owner[] ownersGroup;

modifier onlyOwners{
    for (uint i=0;i<ownersGroup.length;i++){
        if (msg.sender == ownersGroup[i].owner){
            _;
        }
        
    }
}
constructor(){
    ownersGroup.push(Owner(msg.sender));
}

function notAnOwner (address payable newAdd) internal view returns (bool){
    for (uint i=0;i<ownersGroup.length;i++){
        if (newAdd == ownersGroup[i].owner){
           return false;
        }
    }
    return true;
}

function addNewOwner (address payable _newOwner) public payable onlyOwners{
    require( msg.sender != _newOwner);
    require( notAnOwner(_newOwner)==true);
    ownersGroup.push(Owner(_newOwner));
}
function showOwners(uint pos) public view returns (Owner memory){
    return ownersGroup[pos];
}

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

function deposit() public payable returns (uint) {
    walletBalance+=(msg.value);
    return address(this).balance;
}

function submitTx (address payable _recipient,uint _mount) public payable onlyOwners{
    require(address(this).balance >= _mount);
    PendTx memory newTx = PendTx(_mount,address(this),1,_recipient);
    pending.push(newTx);
    votedIdApr[msg.sender][pending.length-1]=true;
    
}

function aprTx (uint _a) public payable onlyOwners returns (uint) {
    require(votedIdApr[msg.sender][_a]==false);
    votedIdApr[msg.sender][_a]=true;
    pending[_a].votes++;
    if(pending[_a].votes>ownersGroup.length/2){
        _trans(pending[_a].recipient,pending[_a].amount);
       delete pending[_a];
    }
    return walletBalance-=pending[_a].amount;
    
}

function _trans(address payable res,uint amount) private {
   res.transfer(amount);
}

function showTx(uint _tx) public view returns (PendTx memory){
    return pending[_tx];
}

}

1 Like
pragma solidity 0.7.5;
pragma abicoder v2;

contract MultiSigWallet {
    Transfer[] transferRequests;
    mapping(address => mapping(uint => bool)) approvals;
    uint limit;
    address[] internal owners;
    uint balance;
    
    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);
    event deposited(uint valueDeposited, address _sender);

    modifier onlyOwners(){
        
       bool _amOwner;
        for (uint i=0; i <= owners.length - 1 ; i++)
       {
            if (owners[i] == msg.sender) {
                _amOwner = true;
                break;
            }
        }
        require(_amOwner,"Unauthorized");
        _;
   }
   
   constructor(address[] memory _owners, uint _limit){
       require(_owners.length > 1, "More than one address required");
       require(_limit > 1, "More than one signature required");
       require(_limit <= _owners.length, "Total number of signatures required can't exceed the total number provided.");
       
       //Check for duplicate addresses
       bool _duplicateFound;
       for (uint i = 0; i <= _owners.length - 1; i++){
           for (uint j = 1; j < _owners.length; j++) {
               if(_owners[i] == _owners[j]){
                   _duplicateFound = true;
                   break;
               }
           }
           if(_duplicateFound == false){
                 owners.push(_owners[i]);
               }
       }
       require(_duplicateFound == false, "Duplicate address found.");
       limit = _limit;
       
   }
   
   function deposit() public payable{
       require(msg.value > 0);
       uint oldBalance = balance;
       balance += msg.value;
       assert(balance == oldBalance + msg.value);
       
       emit deposited(msg.value, msg.sender);
   }
   
   function createTransfer(uint _amount, address payable _receiver) public onlyOwners {
       require(_amount > 0, "Amount must be greater than zero.");
       require(address(this).balance >= _amount, "Insufficient Funds");
    
       emit TransferRequestCreated(transferRequests.length, _amount, msg.sender, _receiver);

       transferRequests.push(Transfer(_amount, _receiver, 0, false, transferRequests.length));
   }
   
   function approve(uint _id) public onlyOwners{
       require(approvals[msg.sender][_id] == false);
       require(transferRequests[_id].hasBeenSent == false);
       
       approvals[msg.sender][_id] = true;
       transferRequests[_id].approvals++;
       
       emit ApprovalReceived(_id, transferRequests[_id].approvals, msg.sender);
   
       if(transferRequests[_id].approvals >= limit){
           transferRequests[_id].hasBeenSent = true;
           transferRequests[_id].receiver.transfer(transferRequests[_id].amount);
           emit TransferApproved(_id);
           
       }
   }
   
   function getTrasnferRequests() public view returns(Transfer[] memory) {
       return transferRequests;
   }
   
   function getTransferRequestApprovals(uint _id) public view returns(uint){
       return transferRequests[_id].approvals;
   }
}
1 Like

Here I created a Multisig Wallet, as per spec. I added events for loggings, asserts to validate that the code is working as expected, and requires to validate some edge cases I found.
In this case, the requester should send the request first, then sign it. I did that to avoid the case when the request is sent, but the contract has not enough funds to continue.

pragma solidity 0.7.5;
pragma abicoder v2;
contract MultisigWallet 
{
    //I will copy the CrowdFunding approach in the Structs section in https://docs.soliditylang.org/en/v0.7.5/types.html
    
    //This RequestStatus ENUM will track TransactionRequest Status
    enum RequestStatus { Pending, Sent }
    
    //Here we define the TransactionRequest struct, that will have a signatures mapping to avoid users to sign more than once
    struct TransactionRequest
    {
        address requester;
        address payable receiver;
        uint amount;
        uint approvalSignatures;
        RequestStatus status;
        mapping (address => bool) signatures;
    }
    //Here we create a mapping that will simulate the behavior of an array
    uint public numTransactionRequests;
    mapping (uint => TransactionRequest) transactionRequestLog;

    //Here we define owners mapping (to check if the sender is owner in a O(1) answer lookup, and the required amount of signatures to execute the transfer
    mapping(address => bool) private owners;
    uint private requiredSignatures;
    
    //This variable will store the balance, to reduce gas usage (asking for address balance)
    uint private contractBalance;
    
    //Here we define events as contract logs
    event depositAdded(uint amount, address indexed sender);
    event transactionRequestAdded(uint index, uint amount, address indexed receiver, address indexed sender);
    event transactionRequestSignature(uint index, address indexed signedBy);
    event transactionRequestExecuted(uint index, uint amount, address indexed receiver);
    
    //Here we define a modifier to check ownership, in order to restrict access to certain methods
    modifier onlyOwner
    {
        require(owners[msg.sender], "You are not allowed to perform this operation");
        _; // run the function
    }
    
    /*
    The contract creator should be able to input 
    (1): the addresses of the owners and 
    (2):  the numbers of approvals required for a transfer, in the constructor. 
    For example, input 3 addresses and set the approval limit to 2.
    In my remix account, I deployed using the following parameters:
    _owners: ["0x5B38Da6a701c568545dCfcB03FcB875f56beddC4","0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2","0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db"]
    _requiredSignatures: 2
    */
    constructor(address[] memory _owners, uint _requiredSignatures)
    {
        require(_owners.length > 0, "The wallet needs at least one owner's address");
        require(_requiredSignatures > 0, "The amount of required signatures cannot be smaller than 1");
        require(_requiredSignatures < _owners.length + 1, "The amount of required signatures cannot be bigger than the amount of owners");
        requiredSignatures = _requiredSignatures;
        //We should verify that all addresses are different.
        bool areAllUniqueAddresses = true;
        for(uint index = 0; index < _owners.length; index++)
        {
            if(owners[_owners[index]])
            {
                areAllUniqueAddresses = false;
                break;
            }
            owners[_owners[index]] = true;
        }
        require(areAllUniqueAddresses, "Owners' addresses should be unique");
    }

    //Anyone should be able to deposit ether into the smart contract
    function deposit() public payable returns (uint)
    {
        uint previousContractBalance = contractBalance;
        contractBalance += msg.value;
        emit depositAdded(msg.value, msg.sender);
        assert(contractBalance == previousContractBalance + msg.value);
        return getBalance();
    }
    
    //We will use a state variable for the contractBalance to reduce gas expenses
    function getBalance() public view returns (uint)
    {
        //return address(this).balance;
        return contractBalance;
    }
    
    /*
    Anyone of the owners should be able to create a transfer request. (It will be done using the onlyOwner modifier)
    The creator of the transfer request will specify what amount and to what address the transfer will be made
    It will return the request index, to avoid using other methods such as getTransferRequest or getTransferRequestList
    */
    function transferRequest(address payable _receiver, uint _amount) public onlyOwner returns (uint)
    {
        require(getBalance() >= _amount, "Error: Not enough funds");
        uint transactionRequestId = numTransactionRequests;
        //In the current approach we can't initialize the struct with a constructor, since it has a mapping
        TransactionRequest storage request = transactionRequestLog[transactionRequestId];
        request.requester = msg.sender;
        request.receiver = _receiver;
        request.amount = _amount;
        request.approvalSignatures = 0;
        request.status = RequestStatus.Pending;
        numTransactionRequests++;
        assert(numTransactionRequests == transactionRequestId + 1);
        assert(transactionRequestLog[transactionRequestId].requester != address(0x0));
        emit transactionRequestAdded(transactionRequestId, _amount, _receiver, msg.sender);
        return transactionRequestId;
    }
    
    /*
    - Owners should be able to approve transfer requests.
    - When a transfer request has the required approvals, the transfer should be sent. 
    We will return the request status, to give a feedback of the request progression
    */
    function approveTransferRequest(uint _index) public onlyOwner returns (RequestStatus)
    {
        require(_index < numTransactionRequests, "Error: The transaction request does not exist");
        require(transactionRequestLog[_index].status == RequestStatus.Pending, "Error: The transaction request have been already approved and funds were delivered");
        require(transactionRequestLog[_index].signatures[msg.sender] == false, "Error: You have already signed this request");
        require(getBalance() >= transactionRequestLog[_index].amount, "Error: The account has not enough funds to approve this transaction");
        uint previousSignatures = transactionRequestLog[_index].approvalSignatures;
        transactionRequestLog[_index].approvalSignatures++;
        transactionRequestLog[_index].signatures[msg.sender] = true;
        assert(transactionRequestLog[_index].approvalSignatures == previousSignatures + 1);
        assert(transactionRequestLog[_index].signatures[msg.sender]);
        emit transactionRequestSignature(_index, msg.sender);
        //If we have the required signatures, we will generate the transfer
        if(transactionRequestLog[_index].approvalSignatures == requiredSignatures)
        {
            executeTransferRequest(_index);
        }
        return transactionRequestLog[_index].status;
        
    }
    
    // Validations were performed in previous methods to avoid transfer failures due to insufficient funds
    function executeTransferRequest(uint _index) private
    {
        uint previousContractBalance = contractBalance;
        contractBalance -= transactionRequestLog[_index].amount;
        transactionRequestLog[_index].status = RequestStatus.Sent;
        //I reordered the code, putting the transfer function after the balance and status modifications, for security reasons (applying the fixes from the previous assignment)
        (transactionRequestLog[_index].receiver).transfer(transactionRequestLog[_index].amount);
        assert(contractBalance == previousContractBalance - transactionRequestLog[_index].amount);
        assert(transactionRequestLog[_index].status == RequestStatus.Sent);
        emit transactionRequestExecuted(_index, transactionRequestLog[_index].amount, transactionRequestLog[_index].receiver);
    }
    
    //this method allow owners to check what is the content of the transaction
    function getTransferRequest(uint _index) public view onlyOwner returns (address, address, uint, uint)
    {
        require(_index < numTransactionRequests, "Error: The transaction request does not exist");
        return (transactionRequestLog[_index].requester, transactionRequestLog[_index].receiver, transactionRequestLog[_index].amount, transactionRequestLog[_index].approvalSignatures);
    }
    
}
1 Like

Hello everyone.
Does anybody know if we can use require on more than one condition?
I mean if we have condition1, condition2 … and so on and if at least one of them is met then we can execute a function.

1 Like

Here is my final project. quite similar to the one in the video, however it keeps track of the balance which you can check and you can also can not approve a transfer request unless there is enough balance to do so but you can add a new request even though there is not enough balance if you ever wanted to preload payments into the system. Perhaps an expiry date on a transfer request would be good. Maybe i’ll add that next.

pragma solidity 0.7.5;
pragma abicoder v2;

//Added functionality
//Keeps track of Balance
//Cannot approve a transfer request unless there is enough balance to transfer out


contract multisigwallet {
  
    address[] public owners;
    uint limit;
    uint public Balance = 0; //balance available to view & check
    
    struct Transfer{
        uint id;
        address payable receiver;
        uint amount;
        uint approvals;
        bool sentStatus;
    }
    
    event transferRequestsCreated(uint _id, uint _amount, address _initiator, address _receiver );
    event approvalRecieved(uint _id, uint _approvals, address _approver);
    event transferApproved(uint _id);
    
       
    Transfer[] transferRequests;
    
    //mapping[address][transferId]
    
    mapping(address => mapping(uint => bool)) approvals;

    //only allows people on the owners list to perfome certain functions
    modifier onlyOwners(){
        bool owner = false;
        for(uint i=0; i<owners.length; i++) {
            if (owners[i] == msg.sender) {
                owner = true;
            }
            
        }
        require(owner == true);
        _;
    }
    
    
    //initialies owners sets limit of owners
    constructor(address[] memory _owners, uint _limit) {
        owners = _owners;
        limit = _limit; 
        //check all the addresses are unique
    }
    
    
    //deposite to multisigwallet
    function deposit() public payable {
        Balance = Balance + msg.value; //adds the deposited amount to balace
        
    }
    
    
    //creates a new Transfer and appends transferRequests
    function createTransfer(uint _amount, address payable _receiver) public onlyOwners {
        emit transferRequestsCreated(transferRequests.length, _amount, msg.sender, _receiver);
        
        transferRequests.push(
            Transfer(transferRequests.length, _receiver, _amount, 0, false)
            );
    }
    
    
    
    function approve(uint _id) public onlyOwners {
        require(approvals[msg.sender][_id] == false); //owner should not be able to vote twice
        require(transferRequests[_id].sentStatus == false); //can't vote on transfers that have already been sent
        require(Balance >= transferRequests[_id].amount); // checks if there is enough balance to transfer
        approvals[msg.sender][_id] = true;
        transferRequests[_id].approvals++;//gives a single approval
        emit approvalRecieved(_id, transferRequests[_id].approvals, msg.sender);
         
        if(transferRequests[_id].approvals >= limit){
            transferRequests[_id].sentStatus = true;
            transferRequests[_id].receiver.transfer(transferRequests[_id].amount); //when enough approvals have been made send amount to receiver
            Balance = Balance - transferRequests[_id].amount;
            emit transferApproved(_id);
        }
       
    }
    
    

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

    
    
}
1 Like
pragma solidity ^0.8.4;

contract MultiSignatureWallet {
    
    struct Transfer {
        address payable to;
        uint amount;
        uint id;
        bool isExecuted;
    }
    
    address[] public owners;
    uint public requiredApprovals;

    uint private balance;
    uint private transactionCounter;
    mapping(address => mapping(uint => bool)) private approvals;
    mapping(uint => Transfer) private transfers;

    event deposited(address sender, uint amount);
    event transfered(address receiver, uint amount);



    modifier onlyOwner{
        bool senderIsOwner = false;
        for(uint i = 0; i < owners.length; i++){
            if(owners[i] == msg.sender){
                senderIsOwner = true;
            }
        }
        require(senderIsOwner, "Error: not an owner of this wallet.");
        _;
    }
    
    modifier checkExecuted(uint transferId) {
        require(transfers[transferId].isExecuted == false, "Transfer is already executed.");
        _;
    }
    
    function getApprovals(uint transferId) public view onlyOwner returns(uint){
        uint foundApprovals = 0;
        for(uint i = 0; i < owners.length; i++){
            if(approvals[owners[i]][transferId]){
                foundApprovals++;
            }
        }
        return foundApprovals;
    }
    
    function getBalance() public view onlyOwner returns(uint){
        return balance;
    }
    
    modifier checkFunds(uint _amount){
        require(balance >= _amount, "Insufficient funds!");
        _;
    }
    
    constructor(address[] memory _owners, uint _requiredApprovals) {
        require(_requiredApprovals <= _owners.length, "Amount of approvals can't be greater than amount of owners.");
        owners = _owners;
        owners.push(msg.sender);
        requiredApprovals = _requiredApprovals;
        transactionCounter = 0;
    }
    
    function deposit() public payable returns(uint){
        require(msg.value > 0, "Deposited amount cannot be 0.");
        uint balanceBefore = balance;
        balance += msg.value;
        assert(balanceBefore + msg.value == balance);
        emit deposited(msg.sender, msg.value);
        return balance;
    }
    
    function requestTransfer(address payable _to, uint _amount) public onlyOwner checkFunds(_amount) returns(uint){
        uint id = transactionCounter++;
        transfers[id] = Transfer(_to, _amount, id, false);
        approvals[msg.sender][id] = true; //sender implicitly approves
        return id;
    }
    
    function executeTransfer(uint transferId) private checkFunds(transfers[transferId].amount) onlyOwner checkExecuted(transferId) {
        Transfer memory t = transfers[transferId];
        t.to.transfer(t.amount);
        balance -= t.amount;
        emit transfered(t.to, t.amount);
        transfers[t.id].isExecuted = true;
    }
    
    function approve(uint transferId) public onlyOwner checkExecuted(transferId) {
        require(approvals[msg.sender][transferId] == false, "Already approved.");
        approvals[msg.sender][transferId] = true;
        if(getApprovals(transferId) >= requiredApprovals)
            executeTransfer(transferId);
    }
    
    
    
    
}
1 Like

Hello thecil
This is what I came up with.
I wanted to completely DIY the assignment without peeking a other members solutions
The code might seem primitive but after battle testing it for 2 days It works fine.

pragma solidity 0.7.5;

contract MultiSigWallet{
    
    struct owner{
        address signer; //Owner address
        uint balance; //balance
        uint signed; //1 if owner signed
        uint signature; //1 for approval 0 for rejection
    }
    
    mapping (address => owner) ownerMapping;
    
    owner owner1;
    owner owner2;
    owner owner3;
    
    constructor(){
        owner1.signer = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;
        ownerMapping[owner1.signer].signer = owner1.signer;
        owner2.signer = 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2;
        ownerMapping[owner2.signer].signer = owner2.signer;
        owner3.signer = 0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db;
        ownerMapping[owner3.signer].signer = owner3.signer;
    }
    
    uint _signed; //number of signatures
    uint signatures; //number of approvals
    uint request; //requested amount
    bool requestSubmited = false; //pending request
    address requester;
    
    modifier onlyOwners{
        require(msg.sender == ownerMapping[owner1.signer].signer || msg.sender == ownerMapping[owner2.signer].signer || msg.sender == ownerMapping[owner3.signer].signer);
        _;
    }
    
    
    //Update the number of signatures
    function updateSigs() private{
        signatures = ownerMapping[owner1.signer].signature + ownerMapping[owner2.signer].signature + ownerMapping[owner3.signer].signature;
        _signed = ownerMapping[owner1.signer].signed + ownerMapping[owner2.signer].signed + ownerMapping[owner3.signer].signed;
    }
    
    //Reset paramaeters after a transfer to allow for the next one
    function paramReset() private{
        requestSubmited = false;
        ownerMapping[owner1.signer].signature = 0;
        ownerMapping[owner2.signer].signature = 0;
        ownerMapping[owner3.signer].signature = 0;
        signatures = 0;
        ownerMapping[owner1.signer].signed = 0;
        ownerMapping[owner2.signer].signed = 0;
        ownerMapping[owner3.signer].signed = 0;
        _signed =0;
    }

    function deposit() public payable{
        ownerMapping[msg.sender].balance += msg.value;
    }
    
    //Wallet (contarct) balance
    function getBalance() public view returns(uint){
        return address(this).balance;
    }
    
    //Transfer request
    function requestTransfer(uint _request) public onlyOwners returns(uint){
        require(address(this).balance >= _request);
        require(requestSubmited == false, "One request at a time!");
        request = _request;
        requestSubmited = true;
        requester = msg.sender;
        return request;
    }
    
    //Request approval
    function approve() public onlyOwners{
        require(requestSubmited == true, "No pending Tx!");
        require(ownerMapping[msg.sender].signed != 1, "Don't signe twice for the same transaction!");
        ownerMapping[msg.sender].signature = 1;
        ownerMapping[msg.sender].signed = 1;
        updateSigs();
    }
    
    //Request rejection
    function reject() public onlyOwners{
        require(requestSubmited == true, "No pending Tx!");
        require(ownerMapping[msg.sender].signed != 1, "Don't signe twice for the same transaction!");
        ownerMapping[msg.sender].signature = 0;
        ownerMapping[msg.sender].signed = 1;
        updateSigs();
    }
    
    function getSignatures() public view returns(string memory, uint){
        return ("Number of signatures", signatures);
    }
    
    //Submitting transfer
    function withdraw() public returns(string memory){
        require(requestSubmited == true); //Make sure there a request pending
        require(msg.sender == requester); //Make sure it is the requester himself
        require(_signed >= 2, "Approval pending!"); //At least 2 owners signed
        
        if(signatures >= 2){ // At least 2 approvals
            msg.sender.transfer(request);
            paramReset();
            return "Transfer done!";
        }else if(signatures < 2 && _signed == 3){ //3 signatures 2 rejections
            paramReset();
            return "Transfer rejected!";
        }else if(signatures == 0 && _signed >= 2){ //2 signatures 2 rejections
            paramReset();
            return "Transfer rejected!";
        }
    }
    
}
1 Like
pragma solidity 0.7.5;
pragma abicoder v2;

contract MultiSig
    {
    
    address[] public ownersList;
    uint sigReq;
    uint transId;
    uint sigCount;
    
    struct transfer 
    {
    uint transId;
    address payable receiver;
    uint amount;
    uint approvalCount;
    bool trasactStatus;
    }
    
    //["0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2","0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db","0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB"],2

    transfer[] public transferReq;
    uint[] pendingOwner;
    
   
    mapping (address => mapping (uint=>bool)) approvalStatus;
    
    event TransferReqCreated(uint _transId,address _creator,address _receiver,uint _amount);
    event ApprovalReceived(uint _transId,uint _approvalCount,address _approver);
    event Approved(uint _transId);
    
    constructor (address[] memory _ownersList, uint _sigReq)
    {
    
    //Set Owners List and Set signature limit
    
    ownersList = _ownersList;
    sigReq = _sigReq;
    
    }
    
    //Checking for owners Authenticity
    
     modifier onlyOwners
    {

      
      bool chkOwner= false;
       
      for(uint i=0; i < ownersList.length; i++)
      {
         
          
         if(ownersList[i] == msg.sender)
         {
             chkOwner=true;
            
         }
         
         
      }
      
       require(chkOwner==true,"You are not authorised to make transaction");
         _;
        
    }
    

    // Deposit funds in smart contract wallet
    
    function depositeFunds() public payable{}
    
    
    //Initiate transfer
    
    function createTransfer(uint _amount, address payable _receiver) public  onlyOwners
    {
        emit TransferReqCreated(transferReq.length,msg.sender,_receiver, _amount);
        transferReq.push(transfer(transferReq.length,_receiver,_amount,0,false));
        
    }
    
  
    
    //Approve transfer and execute transfer
    
    function approveTransfer(uint _transId) public onlyOwners payable
    {
    require(approvalStatus[msg.sender][_transId] == false,"Already approved by you!");
    require(transferReq[_transId].trasactStatus == false,"Already approved!");
    
    approvalStatus[msg.sender][_transId]=true;
    transferReq[_transId].approvalCount++;
    
    
    emit ApprovalReceived(_transId,transferReq[_transId].approvalCount,msg.sender);
    
    if(transferReq[_transId].approvalCount >= sigReq)
    {
        
        transferReq[_transId].trasactStatus= true;
        transferReq[_transId].receiver.transfer(transferReq[_transId].amount);
        emit Approved(_transId);
    }
    
    
    
    }
    
    // View transfer information
    function getTransferRequests() public view returns (transfer[] memory)
    {
        return transferReq;
        
    }
    
    
    
 

    
    
    
    

    
}
1 Like

My code

pragma solidity 0.8.3;
pragma experimental ABIEncoderV2;

contract wallet{
address[] public owners;

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

constructor() {
    owners.push(0x5B38Da6a701c568545dCfcB03FcB875f56beddC4);
    owners.push(0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2);
    owners.push(0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db);
}

modifier onlyOwner{
    require(msg.sender == owners[0] || msg.sender == owners[1] || msg.sender == owners[2]);
    _;
}

struct request{
    uint id;
    uint amount;
    address from;
    address to;
    uint approvals;
}

request[] transaction;

function createTransaction(address recipient, uint amount)public {
    require(msg.sender != recipient);
    require(balance[msg.sender] >= amount);
    transaction.push( request(transaction.length,amount,msg.sender,recipient,0) );
}

function approval(uint index)public onlyOwner{
    require(approvals[msg.sender][index] != true);
    approvals[msg.sender][index] = true;
    transaction[index].approvals += 1;
    
    if(transaction[index].approvals == 2)
    Transfer(index);
}

function Transfer(uint index)private {
    require(transaction[index].approvals == 2);
    require(balance[transaction[index].from] >= transaction[index].amount);
    
    uint prevamount = balance[transaction[index].from];
    _Transfer( transaction[index].from, transaction[index].to, transaction[index].amount);
    
    assert(prevamount - transaction[index].amount == balance[transaction[index].from]);
}

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

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

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

function getTransaction(uint index)public view returns(request memory){
    return transaction[index];
}

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

Here is the code.

pragma solidity  0.7.5;
pragma abicoder v2;

//["0x5B38Da6a701c568545dCfcB03FcB875f56beddC4", "0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2", "0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db"]
contract MultiWallet {
    
    address[] public owners;
    uint limit;
    uint currentId;
    uint balance; 
     
    struct Transfer{
        address payable recievingAddress;
        uint amount;
        uint approvals;
        uint id;
        bool transferExecuted;
    }
    
    constructor(address[] memory _owners, uint _limit){
        owners =_owners;
        limit = _limit;
    }
     
    Transfer[] transferRequests;
     
    mapping(address => mapping(uint => bool)) approvalsMap;
    
    modifier onlyOwner {
        for(uint i = 0; i < owners.length;i++){
            if(msg.sender == owners[i]){
                _;
            }
        }
    }
     
    event transferRequested(Transfer);
    
    function deposit() public payable{
        
    }
    
    
    function createTransfer(address payable requestingAddress, uint amount) public onlyOwner{
        
        transferRequests.push(Transfer(requestingAddress, amount,0 ,transferRequests.length, false));
        
    }
    
   
    
    function approve(uint _id) public onlyOwner{
        require(approvalsMap[msg.sender][_id] == false);
        require(transferRequests[_id].transferExecuted == false);
        
        approvalsMap[msg.sender][_id] = true;
        transferRequests[_id].approvals++;
        
        if(transferRequests[_id].approvals >= limit){
            transferRequests[_id].transferExecuted = true;
            transferRequests[_id].recievingAddress.transfer(transferRequests[_id].amount);
        }
    }
    
    function getTransferRequests() public view returns(Transfer[] memory){
        return transferRequests;
    }
    
    
}

My code:

pragma solidity 0.7.5;
pragma abicoder v2;

contract multiSigWallet {

    uint private totalBalance;
    address[3] owners;
    uint public numOfApprovalsNeeded;

    constructor(address _owner1, address _owner2, address _owner3, uint _numOfApprovalsNeeded) {
        require(_owner1 != _owner2 && _owner1 != _owner3 && _owner2 != _owner3, "Three different owners needed");
        owners = [_owner1, _owner2, _owner3];
        numOfApprovalsNeeded = _numOfApprovalsNeeded;
    }

    struct Transaction {
        address payable to;
        uint amount;
        uint approvals;
        bool executed;
    }

    Transaction[] transactions;

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

    modifier onlyOwners {
        require(msg.sender == owners[0] || msg.sender == owners[1] || msg.sender == owners[2], "Only owners");
        _;
    }

    modifier notApproved (uint _txID) {
        require(!approved[msg.sender][_txID], "You have already approved this transaction");
        _;
    }

    modifier notExecuted (uint _txID) {
        require(!transactions[_txID].executed, "Transaction already executed");
        _;
    }

    function deposit() public payable {
        totalBalance += msg.value;
    }

    function requestTransaction(address payable _to, uint _amount) public onlyOwners {
        require(_to != msg.sender, "Don't send money to yourself");

        createTransaction(_to, _amount);
    }

    function requestWithdrawal(uint _amount) public onlyOwners {
        createTransaction(msg.sender, _amount);
    }

    function createTransaction (address payable _to, uint _amount) private onlyOwners {
        require(_amount <= totalBalance, "Not enough balance");

        transactions.push(Transaction({
            to: _to,
            amount: _amount,
            approvals: 0,
            executed: false
        }));
    }

    function approveTransaction(uint _txID) public onlyOwners notApproved(_txID) notExecuted(_txID) {
        Transaction storage transaction = transactions[_txID];

        transaction.approvals += 1;
        approved[msg.sender][_txID] = true;

        if(transaction.approvals >= numOfApprovalsNeeded) {
            executeTransaction(_txID);
        }
    }

    function executeTransaction(uint _txID) private onlyOwners notExecuted(_txID) {
        require(transactions[_txID].approvals >= numOfApprovalsNeeded, "Not enough approvals");

        Transaction storage transaction = transactions[_txID];
        totalBalance -= transaction.amount;
        transaction.executed = true;
        transaction.to.transfer(transaction.amount);
    }

    function getBalance() public view returns(uint) {
        return totalBalance;
    }

    function getOwners() public view returns(address[3] memory) {
        return owners;
    }

    function getTransaction(uint _txID) public view returns(Transaction memory) {
        return(transactions[_txID]);
    }

    function getNumberOfTransactions() public view returns(uint) {
        return transactions.length;
    }
}

Hey @Fuera, hope you are ok.

Sorry for the delay, the problem with your modifier onlyOwners is that modifiers needs a require statement to continue its execution, so the way yo are doing it is not exactly the proper one.


modifier onlyOwners{
    for (uint i=0;i<ownersGroup.length;i++){
        if (msg.sender == ownersGroup[i].owner){
            _;
        }
        
    }
}

Instead, you should add a require to make it work properly.

modifier onlyOwners{
    // will save the owner state, start default at false
    bool _isOwner;
    for (uint i=0;i<ownersGroup.length;i++){
        if (msg.sender == ownersGroup[i].owner){
            // if msg.sender exist in the array, we set to true
         _isOwner = true;   
        }
    }
    // if owner exist, continue execution
    require(_isOwner == true, "Not an owner");
    _;
}

Carlos Z

1 Like

Yes, you could have something like:

require(isOwner == true && balance[msg.sender] >= amount, "is not owner or does not have enough funds");

Carlos Z

TransferRequest.sol
pragma solidity ^0.8.0;

struct TransferRequest{
    
    mapping(address => bool) approvedSignatures;

    address reciever;
    
    uint transferAmount;
    
    uint approvedSignaturesCount;
    
    uint requiredAmountOfApprovals;
}
Wallet.sol
pragma solidity ^0.8.0;

import "./TransferRequest.sol";

struct Wallet{
    
    address owner;
    
    uint balance;
    
    address[] signatures;

    mapping(address => TransferRequest) transferRequests;

    uint requiredAmountOfApprovals;
    
  
}
MultisigWallet.sol
pragma solidity ^0.8.0;

import "./Wallet.sol";
import "./TransferRequest.sol";

contract MultisigWallet {
    

    mapping(address => uint) walletIndexes;
    
    mapping(uint => Wallet) wallets;
    
    uint existingWallets = 1;
    
    constructor(uint requiredAmountOfApprovals, address[] memory signatures){
        
        createWallet();
        
        for (uint i=0; i<signatures.length; i++){
            addSignature(signatures[i]);
        }
        
        wallets[walletIndexes[msg.sender]].signatures = signatures;
        wallets[walletIndexes[msg.sender]].requiredAmountOfApprovals = requiredAmountOfApprovals;
    }
    
    modifier requiresWalletOwner{
        
        require(msg.sender == wallets[walletIndexes[msg.sender]].owner, "Only the owner of the wallet can do this transaction");
        _;
    }

    function createWallet() public{
        
        require(walletIndexes[msg.sender] == 0, "You already have a wallet!");
        
        existingWallets++;
        
        walletIndexes[msg.sender] = existingWallets; 
        wallets[walletIndexes[msg.sender]].owner = msg.sender;
        addSignature(msg.sender);
        
    }
    
    function deposit() public payable{
            
        require(msg.value > 0, "You cannot deposit zero, get a job!");
        
        wallets[walletIndexes[msg.sender]].balance += msg.value;
    }
    
    function transfer(address reciever, uint amount) public{
        
        createTransferRequest(reciever, amount);
        
    }
    
    function createTransferRequest(address reciever, uint amount) private{
        
        wallets[walletIndexes[msg.sender]].transferRequests[msg.sender].approvedSignaturesCount = 0;

        wallets[walletIndexes[msg.sender]].transferRequests[msg.sender].requiredAmountOfApprovals = wallets[walletIndexes[msg.sender]].requiredAmountOfApprovals;
            
        wallets[walletIndexes[msg.sender]].transferRequests[msg.sender].reciever = reciever;
    
        wallets[walletIndexes[msg.sender]].transferRequests[msg.sender].transferAmount = amount;
        
    }
   
    
    function addSignature(address signature) public requiresWalletOwner{
        
        walletIndexes[signature] = walletIndexes[msg.sender];
   
        wallets[walletIndexes[msg.sender]].signatures.push(signature);    
        wallets[walletIndexes[msg.sender]].requiredAmountOfApprovals++;
    
    }
    
    
    function approve(address transferRequest) public {
    
        require(wallets[walletIndexes[msg.sender]].transferRequests[transferRequest].approvedSignatures[msg.sender] == false, "You cannot approve twice");
    
        wallets[walletIndexes[msg.sender]].transferRequests[transferRequest].approvedSignatures[msg.sender] = true;
        wallets[walletIndexes[msg.sender]].transferRequests[transferRequest].approvedSignaturesCount++;
        
        if (wallets[walletIndexes[msg.sender]].transferRequests[transferRequest].approvedSignaturesCount >= wallets[walletIndexes[msg.sender]].requiredAmountOfApprovals){
            
            executeTransferRequest(transferRequest);
            
        }
    }
    
    function executeTransferRequest(address transferRequest) private{
        
        address reciever = wallets[walletIndexes[msg.sender]].transferRequests[transferRequest].reciever;
        uint transferAmount = wallets[walletIndexes[msg.sender]].transferRequests[transferRequest].transferAmount;
        
        wallets[walletIndexes[msg.sender]].balance -= transferAmount;
        wallets[walletIndexes[reciever]].balance += transferAmount;
        
        removeTransferRequest(msg.sender, transferRequest);
    

    }
    
    function removeTransferRequest(address walletAddress, address transferRequest) private{
        
        for (uint i=0; i<wallets[walletIndexes[walletAddress]].signatures.length; i++){
            wallets[walletIndexes[walletAddress]].transferRequests[transferRequest].approvedSignatures[
                wallets[walletIndexes[walletAddress]].signatures[i] ] = false;
        }
        wallets[walletIndexes[walletAddress]].transferRequests[transferRequest].approvedSignaturesCount = 0;
    }
    
    function setRequiredApprovalCount(uint requiredAmountOfApprovals) public requiresWalletOwner{
        
        wallets[walletIndexes[msg.sender]].requiredAmountOfApprovals = requiredAmountOfApprovals;
    }
    
    
    function getBalance() public view returns(uint){
        return wallets[walletIndexes[msg.sender]].balance;    
    }
    
    function getSignatures() public view returns(address[] memory){
        
        return wallets[walletIndexes[msg.sender]].signatures;
    }
    
    function getApprovalCount(address transferRequest) public view returns(uint){
        return wallets[walletIndexes[msg.sender]].transferRequests[transferRequest].approvedSignaturesCount;
    }
    
    function getRequiredApprovals() public view returns(uint){
        return wallets[walletIndexes[msg.sender]].requiredAmountOfApprovals;
    }
}
1 Like

Hey @alve_lagander, hope you are ok.

Nice solution, each wallet can have a multisig and their working pretty well.

The only thing that i think could be awesome to improve, is to be able to withdraw the funds from the contract. Because even by approving correctly and execute the transfer, it just move the funds from one balance mapping address to another.

Carlos Z

Multisig.sol (contract owners >> approvers per transaction >> execute transaction)

pragma solidity 0.8.1;

contract Multisig {
  
  uint TransactionCounter;
  uint TotalApprovers;
  Transaction[] public Transactions;
  address contractOwner;
  uint walletBalance;
  
  mapping(address =>bool)Owners;
  
  struct Transaction {
    address[] approvers;
    uint amount;
    address payable receiver;
    uint ID;
  }  
  
  constructor(){
      contractOwner=msg.sender;
      TotalApprovers=3;
  }
  
  function deposit() payable public returns(uint) {
      return walletBalance += msg.value;
  }
  
  function createTransaction(uint _amount, address payable _receiver) public returns(uint,uint) {
      require(walletBalance >= _amount);
      Transaction memory newTransaction = Transaction(new address[](0), _amount, _receiver, TransactionCounter);
      Transactions.push(newTransaction);
      walletBalance -= _amount;
      assert(TransactionCounter == Transactions.length-1);
      TransactionCounter += 1;
      return(TransactionCounter-1, _amount);
  }
  function setOwners(address _Owner) public {
      require (msg.sender == contractOwner);
      Owners[_Owner]=true;
  }
  function setTotalApprovals(uint _total) public {
      require (msg.sender == contractOwner);
      TotalApprovers = _total;
  }
  function removeOwners(address _Owner) public {
      require (msg.sender == contractOwner);
      Owners[_Owner]=false;
  }
  function getTransactions() public view returns (Transaction[] memory){
      return Transactions;
  }
  function getTransApprovers(uint _TransactionID) public view returns (address[] memory){
     return Transactions[_TransactionID].approvers;
  }
  function addTransApprovers(uint _TransactionID) public returns (address[] memory){
      require(Owners[msg.sender]==true);
      Transactions[_TransactionID].approvers.push(msg.sender);
      return Transactions[_TransactionID].approvers;
  }
  function withdraw(uint _TransactionID) payable public {
     require(Transactions[_TransactionID].approvers.length >=TotalApprovers);
     Transactions[_TransactionID].receiver.transfer(Transactions[_TransactionID].amount);
  }
}
1 Like

I’ve seen the vids after trying my own versions of a multi-sig.

Why could I not make a global flag attribute per account and require() it was greater than 2 with valid addresses in the transfer function parameters? Again, I thought about using objects(struct instantiations) and loops, but how choppy would I be if I started:

contract Wallet {
    
    mapping(address => uint) balance;
    address ID;
    uint currentReqSigs;
    
    constructor(){  //instantiates address that called SC to be used as admin
    ID = msg.sender; 
    }
    
    modifier approvable(uint numberSigs){
        require(numberSigs >= 2);
        _;
    }
..............



Yes, I’ve written my pseudocode and coded some spaghetti. However, I think I’m attempting to add every single language feature we’ve gone over so far, and want to keep it simple with the ability to make it extensible for outside components later. Any suggestions will be appreciated! Thank you!

1 Like

pragma solidity 0.7.5;
pragma abicoder v2;

//owners addresses
//[“0x5B38Da6a701c568545dCfcB03FcB875f56beddC4”, “0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2”, “0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db”]

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

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

//create events to keep track of transactions
event transferRquestCreated(uint _id, uint _amount, address _initiator, address _receiver);
event approvalReceived(uint _id, uint _approval, address _approver);
event transferApproved(uint _id);


Transfer[ ] transferRequests;

mapping(address => mapping(uint =>bool)) approvals;
//mapping[msg.sender][5] = true;

// only the owners can make and approve tranfers 
modifier onlyOwners(){
    bool owner = false;
    for(uint i=0; i<owners.length; i++){
        if(owners[i] ==msg.sender){
            owner = true;
        }
    }
    require(owner == true);
    _;
}

//list of owners addresses and tranfer limits
constructor(address[] memory _owners, uint _limit){
    owners = _owners;
    limit = _limit;
}

//anyone can make a deposit
function deposit () public payable {} 

// only the owners can make and approve tranfers and add to the array
function createTransfer(uint _amount, address payable _receiver) public onlyOwners{
    emit transferRquestCreated(transferRequests.length, _amount, msg.sender, _receiver);
    transferRequests.push(Transfer(_amount, _receiver, 0, false, transferRequests.length));
 }

// update transfer struct  
// record the approval 
function approve(uint _id) public onlyOwners {
    
    // an owner cant approve twice
    require(approvals[msg.sender][_id] == false);
    
    // an owner cant approve once tranfer is sent
    require(transferRequests[_id].hasBeenSent == false);
    
    approvals[msg.sender][_id] = true;
    transferRequests[_id].approvals++;
    
    emit approvalReceived(_id, transferRequests[_id].approvals, msg.sender);
    
    //when bool 2 of 3 met. send Transfer
    if(transferRequests[_id].approvals >= limit){
        transferRequests[_id].hasBeenSent = true;
        transferRequests[_id].receiver.transfer(transferRequests[_id].amount);
        emit transferApproved(_id);
    }
}


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

}

1 Like
pragma solidity 0.8.4;
pragma abicoder v2;

contract Multisig_Wallet {
    
    struct Transaction {
        uint amount;
        address[] approvedBy;
        mapping (address => bool) isIn;
        bool sent;
    }
    
    mapping(address => mapping(uint => Transaction)) approvals;
    mapping(address => uint) lastIds;
    address[] owners;
    uint signsRequired;
    
    
    modifier onlyOwner {
        bool isOwner = false;
        for (uint i = 0; i < owners.length; i++) {
            if (msg.sender == owners[i]) {
                isOwner = true;
            }
        }
        require(isOwner == true, "Not the owner of a contract");
        _; // run the function
    }
    
    constructor(address[] memory _owners, uint _signsRequired) {
        require(_owners.length >= _signsRequired, "The signs required number should be less or equal to owners number");
        require(_signsRequired > 1, "The signs required number should be more than 0");
        
        owners = _owners;
        signsRequired = _signsRequired;
    }
    
    
    function deposit() public payable {
        // deposit money to the contract
    }
        
    function transfer(address payable _recipient, uint _amount) public onlyOwner {
        require(_amount <= address(this).balance);

        lastIds[_recipient] += 1;
        uint currentId = lastIds[_recipient];
        
        approvals[_recipient][currentId].approvedBy.push(msg.sender);
        approvals[_recipient][currentId].isIn[msg.sender] = true;
        approvals[_recipient][currentId].amount = _amount;
    }
    
    function approve(address payable _recipient, uint _id) public onlyOwner {
        require(approvals[_recipient][_id].isIn[msg.sender] == false);
        require(approvals[_recipient][_id].sent == false);
        require(approvals[_recipient][_id].approvedBy.length >= 1);
        
        approvals[_recipient][_id].approvedBy.push(msg.sender);
        approvals[_recipient][_id].isIn[msg.sender] = true;
        
        if (approvals[_recipient][_id].approvedBy.length >= signsRequired) {
            approvals[_recipient][_id].sent = true;
            _recipient.transfer(approvals[_recipient][_id].amount);
        }
    }
}
1 Like

Dear all,

this is my first smart contract. Would anyone be so kind and please check it? I’m especially interested in how safe this contract is. Maybe @jon_m, @filip,…?

Is the maximum size of an array 2^256? How important it is to take this into consideration in the real world smart contracts? Does mappings have maximum size as well?

Thank you in advance!

pragma solidity 0.7.5;
pragma abicoder v2;



contract multiSigWallet{
   
   address deployer;
   address[] owners;
   uint nOfMinApprovals;
   uint txIdCnt;
   
   struct transfer {
       address payable to;
       uint amount;
       address requested;
       bool sent;
       uint nOfApprovals;
       uint txId;
       
   }
   
   transfer[] transferRequests;
      
   event transferExecuted(address to, uint amount);
   
   //mapping address->txid->true/false
   mapping(address => mapping(uint => bool)) public approvals;
   
   constructor(address[] memory _owners, uint _nOfMinApprovals) {
       
       owners = _owners;
       deployer = msg.sender;
       owners.push() = deployer; //last address in owners array will be the deployer address
       nOfMinApprovals = _nOfMinApprovals;
       
       //when deploying array use ["address1", "address2", ...] 
       //for testing ["0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2","0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db"],2
   }
       
   modifier onlyOwners{
       bool isOwner = false;
       for (uint i; i<owners.length; i++){
           if(msg.sender == owners[i]){
               isOwner = true;
               break;
           }
       }
       require(isOwner);
       _; //run the function
   }
   

   function depositToSc() external payable {
       //deposit to smart contract
   }
   
   function balanceOfSc() external view onlyOwners returns(uint256){
       return address(this).balance;
   }
   
   function requestTransfer(address payable _to,uint _amount) public onlyOwners {
       transferRequests.push(transfer(_to,_amount,msg.sender,false,0,txIdCnt));
       txIdCnt++;
       
       //for testing 0xdD870fA1b7C4700F2BD7f44238821C26f7392148,2000000000000000000
   }
   
   function getAllTransferRequests() public view onlyOwners returns(transfer[] memory){
       return transferRequests;
   }

   function approveAndExecuteTransfers(uint _txId) public onlyOwners{

       require(transferRequests[_txId].requested != msg.sender); //creator of the request isn't allowed to approve the transfer
       require(approvals[msg.sender][_txId] == false); //check that msg.sender hasn't approved the transfer yet
       //do we need to add require that checks if amount to be transfered is smaller than balance of smart contract?
       
       
       approvals[msg.sender][_txId] = true;
       transferRequests[_txId].nOfApprovals++;
       
       if (transferRequests[_txId].nOfApprovals>=nOfMinApprovals){
           transferRequests[_txId].to.transfer(transferRequests[_txId].amount);
           transferRequests[_txId].sent = true;
           emit transferExecuted(transferRequests[_txId].to, transferRequests[_txId].amount);
       }
       
   }
   
       
}