Hey there fellas, so I decided to give it a try without help, and soon after got stuck so had to watch a bit more than half of the assistance video. Iām sure that this is definitely not the most efficient way to do it, but was the best I could come up with. Anyhow, Iāll leave you guys my code with its corresponding explanation down below. I really look forward to your feedback.
pragma solidity 0.7.5;
contract Ownable{
address [] owners;
constructor(){
owners.push(msg.sender);
owners.push(0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2);
owners.push(0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db);
}
modifier onlyOwners{
require(msg.sender == owners[0] || msg.sender == owners[1] || msg.sender == owners[2], "You're not an owner");
_;
}
}
I made a few changes to the ownable contract to ensure that one wallet doesnāt have control over the other owner wallets. The owner addresses are pushed onto the owners
array as soon as the contract is created and created a modifier that specifies that only the owners can perform certain functions.
pragma solidity 0.7.5;
pragma abicoder v2;
import "./ownable.sol";
contract MultiSigWallet is Ownable {
constructor(){
approvalsRequired = 2;
}
//DECLARATIONS
uint approvalsRequired;
mapping (address => mapping(uint => bool)) approves;
struct Transaction{
address sender;
address payable receiver;
uint amount;
uint txID;
uint approvals;
string state;
}
Transaction[] transactions;
//EVENTS DECLARATIONS
event depositReceived(uint amount, address sender);
event transactionApproved(address approver, Transaction transaction);
event transactionCompleted(uint amount, address sender, address receiver);
//FUNCTIONS
function deposit() public payable{
emit depositReceived(msg.value, msg.sender);
}
function createTransaction(address payable receiver, uint amount) public onlyOwners{
transactions.push(Transaction(msg.sender, receiver, amount, transactions.length, 0, "Pending"));
}
function approveDisapproveTransaction(uint txID) public onlyOwners{
approves[msg.sender][txID] = !approves[msg.sender][txID];
if(approves[msg.sender][txID] == true) transactions[txID].approvals ++;
else transactions[txID].approvals --;
emit transactionApproved(msg.sender, transactions[txID]);
}
function transfer(uint txID) public onlyOwners{
require(transactions[txID].approvals >= approvalsRequired,"Not enough approvals");
require(address(this).balance >= transactions[txID].amount, "Not enough funds");
uint walletOriginalBalance = address(this).balance;
transactions[txID].state = "Confirmed";
transactions[txID].receiver.transfer(transactions[txID].amount);
assert(address(this).balance == walletOriginalBalance - transactions[txID].amount);
emit transactionCompleted(transactions[txID].amount, transactions[txID].sender, transactions[txID].receiver);
}
function getTransaction(uint txID) public view returns(Transaction memory){
return transactions[txID];
}
function getWalletBalance() public view returns(uint){
return address(this).balance;
}
function getNumberOfApprovals(uint txID) public view returns(uint){
return transactions[txID].approvals;
}
}
And here is the main multisig wallet contract. First, the minimum amount of approvals required is saved when the contract is created, then the needed data types and events are declared. The function deposit()
allows anyone to deposit, the function createTransaction()
takes various parameters that will be saved in the array of structs transactions[]
, the function approveDisapproveTransaction checks whether a transaction has been approved or not by toggling between true or false and adding or substracting approval votes to the approvals property of the Transaction
struc, this allows each owner to be capable to vote only once and also provides with an opportunity to remove your approval if necessary. The transfer function checks that amount of approvals matches the minimum amount required and if there is enough funds in the wallet it will perform the transfer and update the state of the transaction to confirmed. And to finalize there are a few other functions that helps us to keep tract of the amount of approvals per transaction, the wallet balance, and the transaction that have been created.
Iāve also implemented event logs, to better keep track of every action and error handling with require() and asser() functions.
Iāve tested this with a different inputs and situations and it all seems to work alright. Iām really looking forward to read your feedback, now Iāll start reading some of the community memberās solutions and see how Filip does it.
CHEERS!!!
