Created
May 27, 2022 13:30
-
-
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=
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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