Skip to content

Instantly share code, notes, and snippets.

@z0r0z
Created December 26, 2021 03:28
Show Gist options
  • Save z0r0z/52bb8902c482c2cbe7f088eb8156391c to your computer and use it in GitHub Desktop.
Save z0r0z/52bb8902c482c2cbe7f088eb8156391c to your computer and use it in GitHub Desktop.
EIP-712-signed multi-signature contract
// 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