// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract AvoMultisigAdminStructs {
/// @notice Transaction represents a executeable transaction and it's multisig status
struct Transaction {
/// @notice The hash of the transaction, keccak(encodePacked(target, data)))
bytes30 hash;
/// @notice The number of confirmations for the transaction
uint8 confirmation;
/// @notice The number of revokes for the transaction
uint8 revoke;
}
}
contract AvoMultisigAdminErrors {
/// @notice Error if input params are invalid
error AvoMultisigAdmin__InvalidParams();
/// @notice Error if msg.sender is not authorized
error AvoMultisigAdmin__Unauthorized();
/// @notice Error if signer is not a signer
error AvoMultisigAdmin__InvalidSigner();
/// @notice Error if transaction is already created
error AvoMultisigAdmin__TransactionAlreadyCreated();
/// @notice Error if transaction is already confirmed by the signer
error AvoMultisigAdmin__TransactionAlreadyConfirmed();
/// @notice Error if transaction is already revoked by the signer
error AvoMultisigAdmin__TransactionAlreadyRevoked();
/// @notice Error if transaction is already confirmed or revoked
error AvoMultisigAdmin__TransactionError();
/// @notice Error if transaction is not found
error AvoMultisigAdmin__TransactionNotFoundError();
}
abstract contract AvoMultisigAdminConstants is AvoMultisigAdminErrors {
/// @notice The number of signers required to confirm a transaction
uint256 public immutable REQUIRED_CONFIRMATIONS;
/// @notice Addresses for the signers
address public immutable SIGNER_1;
address public immutable SIGNER_2;
address public immutable SIGNER_3;
address public immutable SIGNER_4;
address public immutable SIGNER_5;
address public immutable SIGNER_6;
/// @notice sets up the immutable vars: signers and required confirmations.
constructor(
address signer1_,
address signer2_,
address signer3_,
address signer4_,
address signer5_,
address signer6_,
uint256 requiredConfirmations_
) {
if (
signer1_ == address(0) ||
signer2_ == address(0) ||
signer3_ == address(0) ||
signer4_ == address(0) ||
signer5_ == address(0) ||
signer6_ == address(0)
) {
revert AvoMultisigAdmin__InvalidParams();
}
if (requiredConfirmations_ == 0 || requiredConfirmations_ > 6) {
revert AvoMultisigAdmin__InvalidParams();
}
SIGNER_1 = signer1_;
SIGNER_2 = signer2_;
SIGNER_3 = signer3_;
SIGNER_4 = signer4_;
SIGNER_5 = signer5_;
SIGNER_6 = signer6_;
REQUIRED_CONFIRMATIONS = requiredConfirmations_;
}
}
contract AvoMultisigAdminVariables {
/// @notice The mapping of the hash of (targetAddress, data, salt) to the transaction
mapping(bytes30 => AvoMultisigAdminStructs.Transaction) public hashToTransaction;
/// @notice The mapping of signer to transaction hash to whether or not they've confirmed
mapping(address => mapping(bytes30 => bool)) public signedTx;
/// @notice The mapping of signer to transaction hash to whether or not they've revoked
mapping(address => mapping(bytes30 => bool)) public revokeTx;
}
contract AvoMultisigAdminEvents {
/// @notice Emitted when a transaction is created
event TransactionCreated(bytes30 indexed hash, address indexed creator, address target, bytes data, bytes32 salt);
/// @notice Emitted when a transaction is confirmed
event TransactionConfirmed(bytes30 indexed hash, address indexed confirmator, uint8 newConfirmationCount);
/// @notice Emitted when a transaction is revoked
event TransactionRevoked(bytes30 indexed hash, address indexed revoker, uint8 newConfirmationCount);
/// @notice Emitted when a transaction is executed
event TransactionExecuted(bytes30 indexed hash, address indexed executor);
/// @notice Emitted when a transaction fails
event TransactionFailed(bytes30 indexed hash, address indexed executor, bytes returnData);
}
/// @title Static Multisignature Wallet Contract
/// @notice This contract represents a multisignature wallet with a static configuration of hardcoded addresses for confirmation.
/// Each address can confirm or revoke a transaction on-chain.
contract AvoMultisigAdmin is
AvoMultisigAdminStructs,
AvoMultisigAdminErrors,
AvoMultisigAdminConstants,
AvoMultisigAdminVariables,
AvoMultisigAdminEvents
{
modifier onlySigner() {
if (
msg.sender != SIGNER_1 &&
msg.sender != SIGNER_2 &&
msg.sender != SIGNER_3 &&
msg.sender != SIGNER_4 &&
msg.sender != SIGNER_5 &&
msg.sender != SIGNER_6
) revert AvoMultisigAdmin__InvalidSigner();
_;
}
/// @notice constructor sets up the immutable vars: signers and required confirmations.
constructor(
address signer1_,
address signer2_,
address signer3_,
address signer4_,
address signer5_,
address signer6_,
uint256 requiredConfirmations_
) AvoMultisigAdminConstants(signer1_, signer2_, signer3_, signer4_, signer5_, signer6_, requiredConfirmations_) {}
/// @notice Create a new transaction
/// @param target_ The address of the target contract or account for the transaction
/// @param data_ The data payload of the transaction
/// @param salt_ The salt used to make the transaction hash unique
function create(address target_, bytes memory data_, bytes32 salt_) external onlySigner {
bytes30 txHash_ = hashTransactionData(target_, data_, salt_);
if (hashToTransaction[txHash_].hash != bytes30(0)) revert AvoMultisigAdmin__TransactionAlreadyCreated();
hashToTransaction[txHash_] = Transaction(txHash_, 1, 0);
signedTx[msg.sender][txHash_] = true;
emit TransactionCreated(txHash_, msg.sender, target_, data_, salt_);
}
/// @notice Confirm a transaction
/// @dev Confirming is not allowed if `confirmation` or `revoke` is already 3 (transaction executed or cancelled).
/// @param target_ The address of the target contract or account for the transaction
/// @param data_ The data payload of the transaction
/// @param salt_ The salt used to make the transaction hash unique
function confirm(address target_, bytes memory data_, bytes32 salt_) external onlySigner {
bytes30 txHash_ = hashTransactionData(target_, data_, salt_);
if (hashToTransaction[txHash_].hash == bytes30(0)) revert AvoMultisigAdmin__TransactionNotFoundError();
if (signedTx[msg.sender][txHash_]) revert AvoMultisigAdmin__TransactionAlreadyConfirmed();
Transaction memory txInfo_ = hashToTransaction[txHash_];
if (txInfo_.confirmation == REQUIRED_CONFIRMATIONS || txInfo_.revoke == REQUIRED_CONFIRMATIONS)
revert AvoMultisigAdmin__TransactionError();
signedTx[msg.sender][txHash_] = true;
hashToTransaction[txHash_].confirmation = ++txInfo_.confirmation;
if (txInfo_.confirmation == REQUIRED_CONFIRMATIONS) {
// Execute the transaction
bool success_;
bytes memory retData_;
(success_, retData_) = target_.call(data_);
if (success_) {
emit TransactionExecuted(txHash_, msg.sender);
} else {
emit TransactionFailed(txHash_, msg.sender, retData_);
}
}
emit TransactionConfirmed(txHash_, msg.sender, txInfo_.confirmation);
}
/// @notice Revoke a transaction confirmation.
/// @dev Revoking is not allowed if `confirmation` or `revoke` is already 3 (transaction executed or cancelled).
/// @param target_ The address of the target contract or account for the transaction
/// @param data_ The data payload of the transaction
/// @param salt_ The salt used to make the transaction hash unique
function revoke(address target_, bytes memory data_, bytes32 salt_) external onlySigner {
bytes30 txHash_ = hashTransactionData(target_, data_, salt_);
if (hashToTransaction[txHash_].hash == bytes30(0)) revert AvoMultisigAdmin__TransactionNotFoundError();
Transaction memory txInfo_ = hashToTransaction[txHash_];
if (txInfo_.confirmation == REQUIRED_CONFIRMATIONS || txInfo_.revoke == REQUIRED_CONFIRMATIONS)
revert AvoMultisigAdmin__TransactionError();
hashToTransaction[txHash_].revoke = ++txInfo_.revoke;
if (revokeTx[msg.sender][txInfo_.hash] == false) {
revokeTx[msg.sender][txInfo_.hash] = true;
} else {
revert AvoMultisigAdmin__TransactionAlreadyRevoked();
}
emit TransactionRevoked(txHash_, msg.sender, txInfo_.revoke);
}
/// @notice Hashes the transaction data
/// @param target_ The address of the target contract or account for the transaction
/// @param data_ The data payload of the transaction
/// @param salt_ The salt used to make the transaction hash unique
/// @return hash_ The unique hash representing this transaction
function hashTransactionData(
address target_,
bytes memory data_,
bytes32 salt_
) public pure returns (bytes30 hash_) {
hash_ = bytes30(keccak256(abi.encodePacked(target_, data_, salt_)));
}
// Code for Multicall below directly taken from https://github.com/mds1/multicall/blob/main/src/Multicall3.sol#L98
// Copyright (c) 2023 Matt Solomon
struct Call3 {
address target;
bool allowFailure;
bytes callData;
}
struct Result {
bool success;
bytes returnData;
}
/// @notice Aggregate calls, ensuring each returns success if required. Can only be called by self.
/// @param calls An array of Call3 structs
/// @return returnData An array of Result structs
function aggregate3(Call3[] calldata calls) public returns (Result[] memory returnData) {
// @dev check for only self is added to original code
if (msg.sender != address(this)) {
revert AvoMultisigAdmin__Unauthorized();
}
uint256 length = calls.length;
returnData = new Result[](length);
Call3 calldata calli;
for (uint256 i = 0; i < length; ) {
Result memory result = returnData[i];
calli = calls[i];
(result.success, result.returnData) = calli.target.call(calli.callData);
assembly {
// Revert if the call fails and failure is not allowed
// `allowFailure := calldataload(add(calli, 0x20))` and `success := mload(result)`
if iszero(or(calldataload(add(calli, 0x20)), mload(result))) {
// set "Error(string)" signature: bytes32(bytes4(keccak256("Error(string)")))
mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000)
// set data offset
mstore(0x04, 0x0000000000000000000000000000000000000000000000000000000000000020)
// set length of revert string
mstore(0x24, 0x0000000000000000000000000000000000000000000000000000000000000017)
// set revert string: bytes32(abi.encodePacked("Multicall3: call failed"))
mstore(0x44, 0x4d756c746963616c6c333a2063616c6c206661696c6564000000000000000000)
revert(0x00, 0x64)
}
}
unchecked {
++i;
}
}
}
}
{
"compilationTarget": {
"contracts/admin/AvoMultisigAdmin.sol": "AvoMultisigAdmin"
},
"evmVersion": "paris",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 10000000
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"signer1_","type":"address"},{"internalType":"address","name":"signer2_","type":"address"},{"internalType":"address","name":"signer3_","type":"address"},{"internalType":"address","name":"signer4_","type":"address"},{"internalType":"address","name":"signer5_","type":"address"},{"internalType":"address","name":"signer6_","type":"address"},{"internalType":"uint256","name":"requiredConfirmations_","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AvoMultisigAdmin__InvalidParams","type":"error"},{"inputs":[],"name":"AvoMultisigAdmin__InvalidSigner","type":"error"},{"inputs":[],"name":"AvoMultisigAdmin__TransactionAlreadyConfirmed","type":"error"},{"inputs":[],"name":"AvoMultisigAdmin__TransactionAlreadyCreated","type":"error"},{"inputs":[],"name":"AvoMultisigAdmin__TransactionAlreadyRevoked","type":"error"},{"inputs":[],"name":"AvoMultisigAdmin__TransactionError","type":"error"},{"inputs":[],"name":"AvoMultisigAdmin__TransactionNotFoundError","type":"error"},{"inputs":[],"name":"AvoMultisigAdmin__Unauthorized","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes30","name":"hash","type":"bytes30"},{"indexed":true,"internalType":"address","name":"confirmator","type":"address"},{"indexed":false,"internalType":"uint8","name":"newConfirmationCount","type":"uint8"}],"name":"TransactionConfirmed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes30","name":"hash","type":"bytes30"},{"indexed":true,"internalType":"address","name":"creator","type":"address"},{"indexed":false,"internalType":"address","name":"target","type":"address"},{"indexed":false,"internalType":"bytes","name":"data","type":"bytes"},{"indexed":false,"internalType":"bytes32","name":"salt","type":"bytes32"}],"name":"TransactionCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes30","name":"hash","type":"bytes30"},{"indexed":true,"internalType":"address","name":"executor","type":"address"}],"name":"TransactionExecuted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes30","name":"hash","type":"bytes30"},{"indexed":true,"internalType":"address","name":"executor","type":"address"},{"indexed":false,"internalType":"bytes","name":"returnData","type":"bytes"}],"name":"TransactionFailed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes30","name":"hash","type":"bytes30"},{"indexed":true,"internalType":"address","name":"revoker","type":"address"},{"indexed":false,"internalType":"uint8","name":"newConfirmationCount","type":"uint8"}],"name":"TransactionRevoked","type":"event"},{"inputs":[],"name":"REQUIRED_CONFIRMATIONS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SIGNER_1","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SIGNER_2","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SIGNER_3","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SIGNER_4","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SIGNER_5","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SIGNER_6","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"target","type":"address"},{"internalType":"bool","name":"allowFailure","type":"bool"},{"internalType":"bytes","name":"callData","type":"bytes"}],"internalType":"struct AvoMultisigAdmin.Call3[]","name":"calls","type":"tuple[]"}],"name":"aggregate3","outputs":[{"components":[{"internalType":"bool","name":"success","type":"bool"},{"internalType":"bytes","name":"returnData","type":"bytes"}],"internalType":"struct AvoMultisigAdmin.Result[]","name":"returnData","type":"tuple[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"target_","type":"address"},{"internalType":"bytes","name":"data_","type":"bytes"},{"internalType":"bytes32","name":"salt_","type":"bytes32"}],"name":"confirm","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"target_","type":"address"},{"internalType":"bytes","name":"data_","type":"bytes"},{"internalType":"bytes32","name":"salt_","type":"bytes32"}],"name":"create","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes30","name":"","type":"bytes30"}],"name":"hashToTransaction","outputs":[{"internalType":"bytes30","name":"hash","type":"bytes30"},{"internalType":"uint8","name":"confirmation","type":"uint8"},{"internalType":"uint8","name":"revoke","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"target_","type":"address"},{"internalType":"bytes","name":"data_","type":"bytes"},{"internalType":"bytes32","name":"salt_","type":"bytes32"}],"name":"hashTransactionData","outputs":[{"internalType":"bytes30","name":"hash_","type":"bytes30"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"target_","type":"address"},{"internalType":"bytes","name":"data_","type":"bytes"},{"internalType":"bytes32","name":"salt_","type":"bytes32"}],"name":"revoke","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bytes30","name":"","type":"bytes30"}],"name":"revokeTx","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bytes30","name":"","type":"bytes30"}],"name":"signedTx","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}]