添加一个多签钱包的合约
This commit is contained in:
parent
b73b34c964
commit
7a6cbe0421
36103
build/contracts/BEMultiSigWallet.json
Normal file
36103
build/contracts/BEMultiSigWallet.json
Normal file
File diff suppressed because one or more lines are too long
450
contracts/core/BEMultiSigWallet.sol
Normal file
450
contracts/core/BEMultiSigWallet.sol
Normal file
@ -0,0 +1,450 @@
|
||||
// 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 TIMELOCK_ADMIN_ROLE =
|
||||
keccak256("TIMELOCK_ADMIN_ROLE");
|
||||
bytes32 public constant PROPOSER_ROLE = keccak256("PROPOSER_ROLE");
|
||||
bytes32 public constant EXECUTOR_ROLE = keccak256("EXECUTOR_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 CallScheduled(
|
||||
bytes32 indexed id,
|
||||
uint256 indexed index,
|
||||
address target,
|
||||
uint256 value,
|
||||
bytes data,
|
||||
bytes32 predecessor,
|
||||
uint256 delay
|
||||
);
|
||||
|
||||
/**
|
||||
* @dev Emitted when a call is performed as part of operation `id`.
|
||||
*/
|
||||
event CallExecuted(
|
||||
bytes32 indexed id,
|
||||
uint256 indexed index,
|
||||
address target,
|
||||
uint256 value,
|
||||
bytes data
|
||||
);
|
||||
|
||||
/**
|
||||
* @dev Emitted when operation `id` is cancelled.
|
||||
*/
|
||||
event Cancelled(bytes32 indexed id);
|
||||
|
||||
event Confirmation(address indexed sender, bytes32 id);
|
||||
|
||||
event Revocation(address indexed sender, bytes32 id);
|
||||
|
||||
/**
|
||||
* @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,
|
||||
address[] memory proposers,
|
||||
address[] memory executors
|
||||
) {
|
||||
_setRoleAdmin(TIMELOCK_ADMIN_ROLE, TIMELOCK_ADMIN_ROLE);
|
||||
_setRoleAdmin(PROPOSER_ROLE, TIMELOCK_ADMIN_ROLE);
|
||||
_setRoleAdmin(EXECUTOR_ROLE, TIMELOCK_ADMIN_ROLE);
|
||||
|
||||
// deployer + self administration
|
||||
_setupRole(TIMELOCK_ADMIN_ROLE, _msgSender());
|
||||
_setupRole(TIMELOCK_ADMIN_ROLE, address(this));
|
||||
|
||||
// register proposers
|
||||
for (uint256 i = 0; i < proposers.length; ++i) {
|
||||
_setupRole(PROPOSER_ROLE, proposers[i]);
|
||||
}
|
||||
|
||||
// register executors
|
||||
for (uint256 i = 0; i < executors.length; ++i) {
|
||||
_setupRole(EXECUTOR_ROLE, executors[i]);
|
||||
}
|
||||
|
||||
minDelay = _minDelay;
|
||||
emit MinDelayChange(0, minDelay);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
|
||||
function isConfirmed(bytes32 id) public view returns (bool ready) {
|
||||
uint256 count = 0;
|
||||
uint256 executorCount = getRoleMemberCount(EXECUTOR_ROLE);
|
||||
for (uint256 i = 0; i < executorCount; i++) {
|
||||
if (confirmations[id][getRoleMember(EXECUTOR_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 single
|
||||
* transaction.
|
||||
*/
|
||||
function hashOperation(
|
||||
address target,
|
||||
uint256 value,
|
||||
bytes calldata data,
|
||||
bytes32 predecessor,
|
||||
bytes32 salt
|
||||
) public pure virtual returns (bytes32 hash) {
|
||||
return keccak256(abi.encode(target, value, data, predecessor, salt));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Returns the identifier of an operation containing a batch of
|
||||
* transactions.
|
||||
*/
|
||||
function hashOperationBatch(
|
||||
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 single transaction.
|
||||
*
|
||||
* Emits a {CallScheduled} event.
|
||||
*
|
||||
* Requirements:
|
||||
*
|
||||
* - the caller must have the 'proposer' role.
|
||||
*/
|
||||
function schedule(
|
||||
address target,
|
||||
uint256 value,
|
||||
bytes calldata data,
|
||||
bytes32 predecessor,
|
||||
bytes32 salt,
|
||||
uint256 delay
|
||||
) public virtual onlyRole(PROPOSER_ROLE) {
|
||||
bytes32 id = hashOperation(target, value, data, predecessor, salt);
|
||||
_schedule(id, delay);
|
||||
emit CallScheduled(id, 0, target, value, data, predecessor, delay);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Schedule an operation containing a batch of transactions.
|
||||
*
|
||||
* Emits one {CallScheduled} event per transaction in the batch.
|
||||
*
|
||||
* Requirements:
|
||||
*
|
||||
* - the caller must have the 'proposer' role.
|
||||
*/
|
||||
function scheduleBatch(
|
||||
address[] calldata targets,
|
||||
uint256[] calldata values,
|
||||
bytes[] calldata datas,
|
||||
bytes32 predecessor,
|
||||
bytes32 salt,
|
||||
uint256 delay
|
||||
) public virtual onlyRole(PROPOSER_ROLE) {
|
||||
require(
|
||||
targets.length == values.length,
|
||||
"BEMultiSigWallet: length mismatch"
|
||||
);
|
||||
require(
|
||||
targets.length == datas.length,
|
||||
"BEMultiSigWallet: length mismatch"
|
||||
);
|
||||
|
||||
bytes32 id = hashOperationBatch(targets, values, datas, predecessor, salt);
|
||||
_schedule(id, delay);
|
||||
for (uint256 i = 0; i < targets.length; ++i) {
|
||||
emit CallScheduled(
|
||||
id,
|
||||
i,
|
||||
targets[i],
|
||||
values[i],
|
||||
datas[i],
|
||||
predecessor,
|
||||
delay
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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) public virtual onlyRole(PROPOSER_ROLE) {
|
||||
require(
|
||||
isOperationPending(id),
|
||||
"BEMultiSigWallet: operation cannot be cancelled"
|
||||
);
|
||||
delete _timestamps[id];
|
||||
|
||||
emit Cancelled(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Allows an executor to confirm a transaction.
|
||||
* Requirements
|
||||
* - the caller must have the 'executor' role.
|
||||
*/
|
||||
function confirmTransaction(bytes32 id)
|
||||
public
|
||||
onlyRoleOrOpenRole(EXECUTOR_ROLE)
|
||||
{
|
||||
require(
|
||||
isOperationPending(id),
|
||||
"BEMultiSigWallet: operation not exist or finished"
|
||||
);
|
||||
confirmations[id][msg.sender] = true;
|
||||
emit Confirmation(msg.sender, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Allows an executor to revoke a confirmation for a transaction.
|
||||
* Requirements
|
||||
* - the caller must have the 'executor' role.
|
||||
*/
|
||||
function revokeConfirmation(bytes32 id)
|
||||
public
|
||||
onlyRoleOrOpenRole(EXECUTOR_ROLE)
|
||||
{
|
||||
require(
|
||||
isOperationPending(id),
|
||||
"BEMultiSigWallet: operation not exist or finished"
|
||||
);
|
||||
confirmations[id][msg.sender] = false;
|
||||
emit Revocation(msg.sender, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Execute an (ready) operation containing a single transaction.
|
||||
*
|
||||
* Emits a {CallExecuted} event.
|
||||
*
|
||||
* Requirements:
|
||||
*
|
||||
* - the caller must have the 'executor' role.
|
||||
*/
|
||||
function execute(
|
||||
address target,
|
||||
uint256 value,
|
||||
bytes calldata data,
|
||||
bytes32 predecessor,
|
||||
bytes32 salt
|
||||
) public payable virtual onlyRoleOrOpenRole(EXECUTOR_ROLE) {
|
||||
bytes32 id = hashOperation(target, value, data, predecessor, salt);
|
||||
_beforeCall(id, predecessor);
|
||||
_call(id, 0, target, value, data);
|
||||
_afterCall(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 executeBatch(
|
||||
address[] calldata targets,
|
||||
uint256[] calldata values,
|
||||
bytes[] calldata datas,
|
||||
bytes32 predecessor,
|
||||
bytes32 salt
|
||||
) public payable virtual onlyRoleOrOpenRole(EXECUTOR_ROLE) {
|
||||
require(
|
||||
targets.length == values.length,
|
||||
"BEMultiSigWallet: length mismatch"
|
||||
);
|
||||
require(
|
||||
targets.length == datas.length,
|
||||
"BEMultiSigWallet: length mismatch"
|
||||
);
|
||||
|
||||
bytes32 id = hashOperationBatch(targets, values, datas, predecessor, salt);
|
||||
_beforeCall(id, predecessor);
|
||||
for (uint256 i = 0; i < targets.length; ++i) {
|
||||
_call(id, i, targets[i], values[i], datas[i]);
|
||||
}
|
||||
_afterCall(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(
|
||||
bytes32 id,
|
||||
uint256 index,
|
||||
address target,
|
||||
uint256 value,
|
||||
bytes calldata data
|
||||
) private {
|
||||
(bool success, ) = target.call{value: value}(data);
|
||||
require(success, "BEMultiSigWallet: underlying transaction reverted");
|
||||
|
||||
emit CallExecuted(id, index, target, value, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user