// SPDX-License-Identifier: MIT pragma solidity 0.8.10; import "@openzeppelin/contracts/access/AccessControlEnumerable.sol"; /** * MultiSigWallet with timelocker */ contract BEMultiSigWallet is AccessControlEnumerable { bytes32 public constant PROPOSER_ROLE = keccak256("PROPOSER_ROLE"); bytes32 public constant EXECUTOR_ROLE = keccak256("EXECUTOR_ROLE"); bytes32 public constant CONFIRM_ROLE = keccak256("CONFIRM_ROLE"); uint256 internal constant _DONE_TIMESTAMP = uint256(1); mapping(bytes32 => uint256) private _timestamps; mapping(bytes32 => mapping(address => bool)) public confirmations; uint256 public minDelay; uint256 public required; /** * @dev Emitted when a call is scheduled as part of operation `id`. */ event ScheduleAdded(bytes32 indexed id); /** * @dev Emitted when a call is performed as part of operation `id`. */ event ScheduleExecuted(bytes32 indexed id); /** * @dev Emitted when operation `id` is cancelled. */ event Cancelled(bytes32 indexed id); event Confirmation(address indexed sender, bytes32[] ids); event Revocation(address indexed sender, bytes32[] ids); /** * @dev Emitted when the minimum delay for future operations is modified. */ event MinDelayChange(uint256 oldDuration, uint256 newDuration); /** * @dev Emitted when the number of required confimations is modified. */ event RequirementChange(uint256 oldDuration, uint256 newDuration); modifier onlyWallet() { require( msg.sender == address(this), "BEMultiSigWallet: caller must be wallet" ); _; } /** * @dev Initializes the contract with a given `minDelay`. */ constructor( uint256 _minDelay, uint256 _required, address[] memory proposers, address[] memory confirmers, address[] memory executors ) { _setRoleAdmin(PROPOSER_ROLE, DEFAULT_ADMIN_ROLE); _setRoleAdmin(EXECUTOR_ROLE, DEFAULT_ADMIN_ROLE); _setRoleAdmin(CONFIRM_ROLE, DEFAULT_ADMIN_ROLE); // deployer + self administration _setupRole(DEFAULT_ADMIN_ROLE, _msgSender()); _setupRole(DEFAULT_ADMIN_ROLE, address(this)); // register proposers for (uint256 i = 0; i < proposers.length; ++i) { _setupRole(PROPOSER_ROLE, proposers[i]); } // register confirmers for (uint256 i = 0; i < confirmers.length; ++i) { _setupRole(CONFIRM_ROLE, confirmers[i]); } // register executors for (uint256 i = 0; i < executors.length; ++i) { _setupRole(EXECUTOR_ROLE, executors[i]); } minDelay = _minDelay; emit MinDelayChange(0, minDelay); required = _required; emit RequirementChange(0, required); } /** * @dev Modifier to make a function callable only by a certain role. In * addition to checking the sender's role, `address(0)` 's role is also * considered. Granting a role to `address(0)` is equivalent to enabling * this role for everyone. */ modifier onlyRoleOrOpenRole(bytes32 role) { if (!hasRole(role, address(0))) { _checkRole(role, _msgSender()); } _; } /** * @dev Contract might receive/hold ETH as part of the maintenance process. */ receive() external payable {} /** * @dev Returns whether an id correspond to a registered operation. This * includes both Pending, Ready and Done operations. */ function isOperation(bytes32 id) public view virtual returns (bool pending) { return getTimestamp(id) > 0; } /** * @dev Returns whether an operation is pending or not. */ function isOperationPending( bytes32 id ) public view virtual returns (bool pending) { return getTimestamp(id) > _DONE_TIMESTAMP; } /** * @dev Returns whether an operation is confirmed or not. */ function isConfirmed(bytes32 id) public view returns (bool ready) { uint256 count = 0; uint256 confirmCount = getRoleMemberCount(CONFIRM_ROLE); for (uint256 i = 0; i < confirmCount; i++) { if (confirmations[id][getRoleMember(CONFIRM_ROLE, i)]) count += 1; if (count == required) return true; } } /** * @dev Returns whether an operation is ready or not. */ function isOperationReady( bytes32 id ) public view virtual returns (bool ready) { uint256 timestamp = getTimestamp(id); return timestamp > _DONE_TIMESTAMP && timestamp <= block.timestamp; } /** * @dev Returns whether an operation is done or not. */ function isOperationDone(bytes32 id) public view virtual returns (bool done) { return getTimestamp(id) == _DONE_TIMESTAMP; } /** * @dev Returns the timestamp at with an operation becomes ready (0 for * unset operations, 1 for done operations). */ function getTimestamp( bytes32 id ) public view virtual returns (uint256 timestamp) { return _timestamps[id]; } /** * @dev Returns the identifier of an operation containing a batch of * transactions. */ function hashOperation( address[] calldata targets, uint256[] calldata values, bytes[] calldata datas, bytes32 predecessor, bytes32 salt ) public pure virtual returns (bytes32 hash) { return keccak256(abi.encode(targets, values, datas, predecessor, salt)); } /** * @dev Schedule an operation containing a batch of transactions. * * Emits one {ScheduleAdded} event per transaction in the batch. * * Requirements: * * - the caller must have the 'proposer' role. */ function schedule( address[] calldata targets, uint256[] calldata values, bytes[] calldata datas, bytes32 predecessor, bytes32 salt, uint256 delay ) external virtual onlyRoleOrOpenRole(PROPOSER_ROLE) { require( targets.length > 0, "BEMultiSigWallet: no transactions to schedule" ); require( targets.length == values.length, "BEMultiSigWallet: length mismatch" ); require( targets.length == datas.length, "BEMultiSigWallet: length mismatch" ); bytes32 id = hashOperation(targets, values, datas, predecessor, salt); _schedule(id, delay); emit ScheduleAdded(id); } /** * @dev Schedule an operation that is to becomes valid after a given delay. */ function _schedule(bytes32 id, uint256 delay) private { require(!isOperation(id), "BEMultiSigWallet: operation already scheduled"); require(delay >= minDelay, "BEMultiSigWallet: insufficient delay"); _timestamps[id] = block.timestamp + delay; } /** * @dev Cancel an operation. * * Requirements: * * - the caller must have the 'proposer' role. */ function cancel( bytes32 id ) external virtual onlyRoleOrOpenRole(PROPOSER_ROLE) { require( isOperationPending(id), "BEMultiSigWallet: operation cannot be cancelled" ); delete _timestamps[id]; emit Cancelled(id); } /** * @dev Allows an executor to confirm multiple transactions. */ function confirmTransaction( bytes32[] calldata ids ) external onlyRoleOrOpenRole(CONFIRM_ROLE) { require(ids.length > 0, "BEMultiSigWallet: empty ids"); for (uint256 i = 0; i < ids.length; ++i) { require( isOperationPending(ids[i]), "BEMultiSigWallet: operation not exist or finished" ); confirmations[ids[i]][msg.sender] = true; } emit Confirmation(msg.sender, ids); } /** * @dev Allows an executor to revoke multiple confirmations for a transaction. */ function revokeConfirmation( bytes32[] calldata ids ) external onlyRoleOrOpenRole(CONFIRM_ROLE) { require(ids.length > 0, "BEMultiSigWallet: empty ids"); for (uint256 i = 0; i < ids.length; ++i) { require( isOperationPending(ids[i]), "BEMultiSigWallet: operation not exist or finished" ); confirmations[ids[i]][msg.sender] = false; } emit Revocation(msg.sender, ids); } /** * @dev Execute an (ready) operation containing a batch of transactions. * * Emits one {CallExecuted} event per transaction in the batch. * * Requirements: * * - the caller must have the 'executor' role. */ function execute( address[] calldata targets, uint256[] calldata values, bytes[] calldata datas, bytes32 predecessor, bytes32 salt ) external payable virtual onlyRoleOrOpenRole(EXECUTOR_ROLE) { require(targets.length > 0, "BEMultiSigWallet: no calls to make"); require( targets.length == values.length, "BEMultiSigWallet: length mismatch" ); require( targets.length == datas.length, "BEMultiSigWallet: length mismatch" ); bytes32 id = hashOperation(targets, values, datas, predecessor, salt); _beforeCall(id, predecessor); for (uint256 i = 0; i < targets.length; ++i) { _call(targets[i], values[i], datas[i]); } _afterCall(id); emit ScheduleExecuted(id); } /** * @dev Checks before execution of an operation's calls. */ function _beforeCall(bytes32 id, bytes32 predecessor) private view { require(isOperationReady(id), "BEMultiSigWallet: operation is not ready"); require( isConfirmed(id), "BEMultiSigWallet: operation not reach required confirmations" ); require( predecessor == bytes32(0) || isOperationDone(predecessor), "BEMultiSigWallet: missing dependency" ); } /** * @dev Checks after execution of an operation's calls. */ function _afterCall(bytes32 id) private { require(isOperationReady(id), "BEMultiSigWallet: operation is not ready"); _timestamps[id] = _DONE_TIMESTAMP; } /** * @dev Execute an operation's call. * * Emits a {CallExecuted} event. */ function _call(address target, uint256 value, bytes calldata data) private { (bool success, ) = target.call{value: value}(data); require(success, "BEMultiSigWallet: underlying transaction reverted"); } /** * @dev Changes the minimum wallet duration for future operations. * * Emits a {MinDelayChange} event. * * Requirements: * * - the caller must be the wallet itself. This can only be achieved by scheduling and later executing * an operation where the wallet is the target and the data is the ABI-encoded call to this function. */ function changeDelay(uint256 _minDelay) external virtual onlyWallet { emit MinDelayChange(minDelay, _minDelay); minDelay = _minDelay; } /** * @dev Allows to change the number of required confirmations. Transaction has to be sent by wallet. * @param _required Number of required confirmations. */ function changeRequirement(uint256 _required) external virtual onlyWallet { emit RequirementChange(required, _required); required = _required; } }