Skip to content

Instantly share code, notes, and snippets.

@djokicx
Created May 27, 2022 13:30
Show Gist options
  • Save djokicx/b55e5e00ac37a273068d6a3f6a7ef3a7 to your computer and use it in GitHub Desktop.
Save djokicx/b55e5e00ac37a273068d6a3f6a7ef3a7 to your computer and use it in GitHub Desktop.
Created using remix-ide: Realtime Ethereum Contract Compiler and Runtime. Load this file by pasting this gists URL or ID at https://remix.ethereum.org/#version=soljson-v0.8.13+commit.abaa5c0e.js&optimize=false&runs=200&gist=
pragma solidity ^0.8.13;
contract MultiSigWallet {
event Deposit(address indexed sender, uint amount);
event Submit(uint indexed txId); // tx submitted waiting for other owners to approve
event Approve(address indexed owner, uint indexed txId); // can aprove
event Revoke(address indexed owner, uint indexed txId); // can revoke approval
event Execute(uint indexed txId);
modifier onlyOwner {
require(isOwner[msg.sender], "Not an owner of the multisig");
_;
}
modifier txExists(uint _txId) {
require(_txId < transactions.length, "Tx does not exist");
_;
}
modifier notApproved(uint _txId) {
require(!approved[_txId][msg.sender], "Owner already approved the tx");
_;
}
modifier notExecuted(uint _txId) {
require(!transactions[_txId].executed, "Tx already executed");
_;
}
struct Transaction {
address to;
uint value;
bytes data;
bool executed;
}
address[] public owners;
mapping(address => bool) public isOwner; // would it be cheaper to iterate thru owners array each time?
Transaction[] public transactions;
// key is the index of the tx in the transactions queue
mapping(uint => mapping(address => bool)) public approved;
uint public required;
constructor(address[] memory _owners, uint _required) {
require(_owners.length > 0, "Owners required");
require(_required <= _owners.length && _required > 0, "Invalid required num of owners");
for(uint i; i < _owners.length; i++) {
address owner = _owners[i];
require(owner != address(0), "Invalid owner at address 0");
require(!isOwner[owner], "Owner is not unique");
isOwner[owner] = true;
owners.push(owner);
required = _required;
}
}
receive() external payable {
emit Deposit(msg.sender, msg.value);
}
function submit(address _to, uint _value, bytes calldata _data) external onlyOwner {
transactions.push(Transaction(_to, _value, _data, false));
emit Submit(transactions.length - 1);
}
function approve(uint _txId) external onlyOwner txExists(_txId) notApproved(_txId) notExecuted(_txId) {
approved[_txId][msg.sender] = true;
emit Approve(msg.sender, _txId);
}
function _getApprovalCount(uint _txId) private view returns (uint count) {
for (uint i; i < owners.length; i++) {
if(approved[_txId][owners[i]]) {
count += 1;
}
}
}
function execute(uint _txId) external txExists(_txId) notExecuted(_txId) {
require(_getApprovalCount(_txId) > required, "Not enough approvals to execute the tx");
Transaction storage transaction = transactions[_txId];
// mark execution true - in case of failed transaction it will be an 'attempted' execution
// will not be possible to execute it again
transaction.executed = true;
// execution
(bool success,) = transaction.to.call{value: transaction.value}(transaction.data);
require(success, "tx failed");
emit Execute(_txId);
}
function revoke(uint _txId) external onlyOwner txExists(_txId) notExecuted(_txId) {
require(approved[_txId][msg.sender], "Not approved");
approved[_txId][msg.sender] = false;
emit Revoke(msg.sender, _txId);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment