becrypto/contracts/core/BEMultiSigWallet.sol

371 lines
10 KiB
Solidity

// 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;
}
}