Created
December 26, 2021 03:28
-
-
Save z0r0z/52bb8902c482c2cbe7f088eb8156391c to your computer and use it in GitHub Desktop.
EIP-712-signed multi-signature contract
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
// SPDX-License-Identifier: GPL-3.0-or-later | |
error NoExecParity(); | |
error NoSigParity(); | |
error NotSigner(); | |
error SigOutOfOrder(); | |
error ExecuteFailed(); | |
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)'); | |
bytes32 private constant GOV_HASH = | |
keccak256('Gov(address[] signers,uint256 requiredSignatures_,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 domainSeparator) { | |
domainSeparator = block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : _computeDomainSeparator(); | |
} | |
function _computeDomainSeparator() private view returns (bytes32 domainSeparator) { | |
domainSeparator = keccak256( | |
abi.encode( | |
keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'), | |
keccak256(bytes('Multisig')), | |
keccak256(bytes('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_, | |
uint8[] calldata v, | |
bytes32[] calldata r, | |
bytes32[] calldata s | |
) external { | |
if (v.length != r.length || r.length != s.length) revert NoSigParity(); | |
bytes32 digest = | |
keccak256( | |
abi.encodePacked( | |
'\x19\x01', | |
DOMAIN_SEPARATOR(), | |
keccak256( | |
abi.encode( | |
GOV_HASH, | |
signers, | |
requiredSignatures_, | |
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 < 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