Created
January 7, 2022 02:03
-
-
Save z0r0z/2f8b7772a3fa0c444d6f17326d2b851e to your computer and use it in GitHub Desktop.
EIP-712-signed multi-signature contract.
This file contains hidden or 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
| // SPDX-License-Identifier: GPL-3.0-or-later | |
| error NoExecParity(); | |
| error NoSigParity(); | |
| error NotSigner(); | |
| error SigOutOfOrder(); | |
| error ExecuteFailed(); | |
| error Forbidden(); | |
| pragma solidity >=0.8.4; | |
| /// @notice EIP-712-signed multi-signature contract. | |
| /// @author Modified from MultiSignatureWallet (https://github.com/SilentCicero/MultiSignatureWallet) | |
| contract MultiSig { | |
| event Execute(address[] targets, uint256[] values, bytes[] payloads); | |
| event Govern(address[] signers, uint256 requiredSignatures); | |
| uint256 public nonce; | |
| uint256 public requiredSignatures; | |
| uint256 private INITIAL_CHAIN_ID; | |
| bytes32 private INITIAL_DOMAIN_SEPARATOR; | |
| bytes32 private constant EXEC_HASH = | |
| keccak256('Exec(address[] targets,uint256[] values,bytes[] payloads,uint256 nonce)'); | |
| mapping(address => bool) public isSigner; | |
| constructor(address[] memory signers, uint256 requiredSignatures_) { | |
| // cannot realistically overflow on human timescales | |
| unchecked { | |
| for (uint256 i = 0; i < signers.length; i++) | |
| isSigner[signers[i]] = true; | |
| } | |
| requiredSignatures = requiredSignatures_; | |
| INITIAL_CHAIN_ID = block.chainid; | |
| INITIAL_DOMAIN_SEPARATOR = _computeDomainSeparator(); | |
| } | |
| function DOMAIN_SEPARATOR() private view returns (bytes32) { | |
| return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : _computeDomainSeparator(); | |
| } | |
| function _computeDomainSeparator() private view returns (bytes32 domainSeparator) { | |
| return | |
| keccak256( | |
| abi.encode( | |
| keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'), | |
| keccak256(bytes('Multisig')), | |
| keccak256('1'), | |
| block.chainid, | |
| address(this) | |
| ) | |
| ); | |
| } | |
| function execute( | |
| address[] calldata targets, | |
| uint256[] calldata values, | |
| bytes[] calldata payloads, | |
| uint8[] calldata v, | |
| bytes32[] calldata r, | |
| bytes32[] calldata s | |
| ) external { | |
| if (targets.length != values.length || values.length != payloads.length) revert NoExecParity(); | |
| if (v.length != r.length || r.length != s.length) revert NoSigParity(); | |
| bytes32 digest = | |
| keccak256( | |
| abi.encodePacked( | |
| '\x19\x01', | |
| DOMAIN_SEPARATOR(), | |
| keccak256( | |
| abi.encode( | |
| EXEC_HASH, | |
| targets, | |
| values, | |
| payloads, | |
| nonce++ | |
| ) | |
| ) | |
| ) | |
| ); | |
| address previous; | |
| // cannot realistically overflow on human timescales | |
| unchecked { | |
| for (uint256 i = 0; i < requiredSignatures; i++) { | |
| address recoveredAddress = ecrecover(digest, v[i], r[i], s[i]); | |
| if (!isSigner[recoveredAddress]) revert NotSigner(); | |
| // check for duplicates or zero value | |
| if (recoveredAddress < previous) revert SigOutOfOrder(); | |
| previous = recoveredAddress; | |
| } | |
| } | |
| // cannot realistically overflow on human timescales | |
| unchecked { | |
| for (uint256 i = 0; i < targets.length; i++) { | |
| (bool success, ) = targets[i].call{value: values[i]}(payloads[i]); | |
| if (!success) revert ExecuteFailed(); | |
| } | |
| } | |
| emit Execute(targets, values, payloads); | |
| } | |
| function govern(address[] calldata signers, uint256 requiredSignatures_) external { | |
| if (msg.sender != address(this)) revert Forbidden(); | |
| // cannot realistically overflow on human timescales | |
| unchecked { | |
| for (uint256 i = 0; i < signers.length; i++) { | |
| isSigner[signers[i]] = !isSigner[signers[i]]; | |
| } | |
| } | |
| requiredSignatures = requiredSignatures_; | |
| emit Govern(signers, requiredSignatures_); | |
| } | |
| receive() external payable {} | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment