diff --git a/.gas-snapshot b/.gas-snapshot index 131b2f3a..3c5ce628 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -658,94 +658,94 @@ SignatureCheckerTest:testFuzzEOAWithInvalidSignature(bytes,string) (runs: 257, SignatureCheckerTest:testFuzzEOAWithInvalidSigner(string,string) (runs: 257, μ: 22170, ~: 22231) SignatureCheckerTest:testFuzzEOAWithValidSignature(string,string) (runs: 257, μ: 21289, ~: 21350) SignatureCheckerTest:testInitialSetup() (gas: 8315) -TimelockControllerInvariants:invariantExecutedLessThanOrEqualToScheduled() (runs: 256, calls: 3840, reverts: 1228) -TimelockControllerInvariants:invariantExecutedProposalCancellation() (runs: 256, calls: 3840, reverts: 1236) -TimelockControllerInvariants:invariantExecutingCancelledProposal() (runs: 256, calls: 3840, reverts: 1248) -TimelockControllerInvariants:invariantExecutingNotReadyProposal() (runs: 256, calls: 3840, reverts: 1194) -TimelockControllerInvariants:invariantOnceProposalExecution() (runs: 256, calls: 3840, reverts: 1211) -TimelockControllerInvariants:invariantProposalsExecutedMatchCount() (runs: 256, calls: 3840, reverts: 1228) -TimelockControllerInvariants:invariantSumOfProposals() (runs: 256, calls: 3840, reverts: 1228) -TimelockControllerTest:testAdminCannotBatchExecute() (gas: 750776) -TimelockControllerTest:testAdminCannotBatchSchedule() (gas: 748562) -TimelockControllerTest:testAdminCannotCancel() (gas: 13498) -TimelockControllerTest:testAdminCannotExecute() (gas: 18552) -TimelockControllerTest:testAdminCannotSchedule() (gas: 16224) -TimelockControllerTest:testBatchCancelFinished() (gas: 4641628) -TimelockControllerTest:testBatchEqualAndGreaterMinimumDelay() (gas: 6145274) -TimelockControllerTest:testBatchHasBeenExecuted() (gas: 4639618) -TimelockControllerTest:testBatchHasNotBeenExecuted() (gas: 3078886) -TimelockControllerTest:testBatchInsufficientDelay() (gas: 1532923) -TimelockControllerTest:testBatchMinimumDelayUpdate() (gas: 3086579) -TimelockControllerTest:testBatchOperationAlreadyScheduled() (gas: 4593561) -TimelockControllerTest:testBatchOperationIsNotReady() (gas: 4598878) -TimelockControllerTest:testBatchPendingIfExecuted() (gas: 4638232) -TimelockControllerTest:testBatchPendingIfNotYetExecuted() (gas: 3078894) -TimelockControllerTest:testBatchPredecessorInvalid() (gas: 4601231) -TimelockControllerTest:testBatchPredecessorMultipleNotExecuted() (gas: 6141917) -TimelockControllerTest:testBatchPredecessorNotExecuted() (gas: 7663188) -TimelockControllerTest:testBatchPredecessorNotScheduled() (gas: 6117091) -TimelockControllerTest:testBatchReadyAfterTheExecutionTime() (gas: 3079459) -TimelockControllerTest:testBatchReadyBeforeTheExecutionTime() (gas: 3079476) -TimelockControllerTest:testBatchReadyOnTheExecutionTime() (gas: 3079362) -TimelockControllerTest:testBatchScheduleAndExecuteWithEmptySalt() (gas: 4647416) -TimelockControllerTest:testBatchScheduleAndExecuteWithNonEmptySalt() (gas: 4650861) -TimelockControllerTest:testBatchTargetRevert() (gas: 9186542) -TimelockControllerTest:testBatchTimestampHasBeenExecuted() (gas: 4638070) -TimelockControllerTest:testBatchTimestampHasNotBeenExecuted() (gas: 3078714) +TimelockControllerInvariants:statefulFuzzExecutedLessThanOrEqualToScheduled() (runs: 256, calls: 3840, reverts: 1247) +TimelockControllerInvariants:statefulFuzzExecutedProposalCancellation() (runs: 256, calls: 3840, reverts: 1288) +TimelockControllerInvariants:statefulFuzzExecutingCancelledProposal() (runs: 256, calls: 3840, reverts: 1234) +TimelockControllerInvariants:statefulFuzzExecutingNotReadyProposal() (runs: 256, calls: 3840, reverts: 1227) +TimelockControllerInvariants:statefulFuzzOnceProposalExecution() (runs: 256, calls: 3840, reverts: 1224) +TimelockControllerInvariants:statefulFuzzProposalsExecutedMatchCount() (runs: 256, calls: 3840, reverts: 1247) +TimelockControllerInvariants:statefulFuzzSumOfProposals() (runs: 256, calls: 3840, reverts: 1247) +TimelockControllerTest:testAdminCannotBatchExecute() (gas: 750799) +TimelockControllerTest:testAdminCannotBatchSchedule() (gas: 748585) +TimelockControllerTest:testAdminCannotCancel() (gas: 13521) +TimelockControllerTest:testAdminCannotExecute() (gas: 18598) +TimelockControllerTest:testAdminCannotSchedule() (gas: 16247) +TimelockControllerTest:testBatchCancelFinished() (gas: 4641697) +TimelockControllerTest:testBatchEqualAndGreaterMinimumDelay() (gas: 6145366) +TimelockControllerTest:testBatchHasBeenExecuted() (gas: 4639664) +TimelockControllerTest:testBatchHasNotBeenExecuted() (gas: 3078909) +TimelockControllerTest:testBatchInsufficientDelay() (gas: 1532946) +TimelockControllerTest:testBatchMinimumDelayUpdate() (gas: 3086625) +TimelockControllerTest:testBatchOperationAlreadyScheduled() (gas: 4593607) +TimelockControllerTest:testBatchOperationIsNotReady() (gas: 4598924) +TimelockControllerTest:testBatchPendingIfExecuted() (gas: 4638278) +TimelockControllerTest:testBatchPendingIfNotYetExecuted() (gas: 3078917) +TimelockControllerTest:testBatchPredecessorInvalid() (gas: 4601277) +TimelockControllerTest:testBatchPredecessorMultipleNotExecuted() (gas: 6141986) +TimelockControllerTest:testBatchPredecessorNotExecuted() (gas: 7663257) +TimelockControllerTest:testBatchPredecessorNotScheduled() (gas: 6117137) +TimelockControllerTest:testBatchReadyAfterTheExecutionTime() (gas: 3079482) +TimelockControllerTest:testBatchReadyBeforeTheExecutionTime() (gas: 3079499) +TimelockControllerTest:testBatchReadyOnTheExecutionTime() (gas: 3079385) +TimelockControllerTest:testBatchScheduleAndExecuteWithEmptySalt() (gas: 4647531) +TimelockControllerTest:testBatchScheduleAndExecuteWithNonEmptySalt() (gas: 4650976) +TimelockControllerTest:testBatchTargetRevert() (gas: 9186634) +TimelockControllerTest:testBatchTimestampHasBeenExecuted() (gas: 4638116) +TimelockControllerTest:testBatchTimestampHasNotBeenExecuted() (gas: 3078737) TimelockControllerTest:testCanReceiveEther() (gas: 15016) -TimelockControllerTest:testCancellerCanCancelOperation() (gas: 3065193) -TimelockControllerTest:testCompleteOperationWithAssignExecutorRoleToZeroAddress() (gas: 125339) -TimelockControllerTest:testCompletePipelineOperationMinimumDelayUpdate() (gas: 73717) -TimelockControllerTest:testCompletePipelineOperationSetRoleAdmin() (gas: 101140) -TimelockControllerTest:testExecutorCanBatchExecute() (gas: 3049657) -TimelockControllerTest:testExecutorCanExecute() (gas: 30232) -TimelockControllerTest:testExecutorCannotBatchSchedule() (gas: 1485781) -TimelockControllerTest:testExecutorCannotCancel() (gas: 15717) -TimelockControllerTest:testExecutorCannotSchedule() (gas: 19383) -TimelockControllerTest:testFuzzBatchValue(uint256) (runs: 257, μ: 4653472, ~: 4653589) -TimelockControllerTest:testFuzzHashOperation(address,uint256,bytes,bytes32,bytes32) (runs: 257, μ: 11069, ~: 10967) +TimelockControllerTest:testCancellerCanCancelOperation() (gas: 3065262) +TimelockControllerTest:testCompleteOperationWithAssignExecutorRoleToZeroAddress() (gas: 125385) +TimelockControllerTest:testCompletePipelineOperationMinimumDelayUpdate() (gas: 73832) +TimelockControllerTest:testCompletePipelineOperationSetRoleAdmin() (gas: 101209) +TimelockControllerTest:testExecutorCanBatchExecute() (gas: 3049703) +TimelockControllerTest:testExecutorCanExecute() (gas: 30324) +TimelockControllerTest:testExecutorCannotBatchSchedule() (gas: 1485827) +TimelockControllerTest:testExecutorCannotCancel() (gas: 15763) +TimelockControllerTest:testExecutorCannotSchedule() (gas: 19429) +TimelockControllerTest:testFuzzBatchValue(uint256) (runs: 257, μ: 4653491, ~: 4653635) +TimelockControllerTest:testFuzzHashOperation(address,uint256,bytes,bytes32,bytes32) (runs: 257, μ: 11092, ~: 10990) TimelockControllerTest:testFuzzHashOperationBatch(address[],uint256[],bytes[],bytes32,bytes32) (runs: 257, μ: 1907784, ~: 1935381) -TimelockControllerTest:testFuzzOperationValue(uint256) (runs: 257, μ: 113831, ~: 113948) -TimelockControllerTest:testHandleERC1155() (gas: 41708457) -TimelockControllerTest:testHandleERC721() (gas: 7242387) -TimelockControllerTest:testHashOperation() (gas: 13089) +TimelockControllerTest:testFuzzOperationValue(uint256) (runs: 257, μ: 113896, ~: 114040) +TimelockControllerTest:testHandleERC1155() (gas: 41708480) +TimelockControllerTest:testHandleERC721() (gas: 7242410) +TimelockControllerTest:testHashOperation() (gas: 13112) TimelockControllerTest:testHashOperationBatch() (gas: 1526310) -TimelockControllerTest:testInitialSetup() (gas: 4396244) -TimelockControllerTest:testInvalidOperation() (gas: 10719) -TimelockControllerTest:testOperationAlreadyScheduled() (gas: 52466) -TimelockControllerTest:testOperationCancelFinished() (gas: 102072) -TimelockControllerTest:testOperationEqualAndGreaterMinimumDelay() (gas: 90672) -TimelockControllerTest:testOperationHasBeenExecuted() (gas: 100095) -TimelockControllerTest:testOperationHasNotBeenExecuted() (gas: 52687) -TimelockControllerTest:testOperationInsufficientDelay() (gas: 19637) -TimelockControllerTest:testOperationMinimumDelayUpdate() (gas: 61720) -TimelockControllerTest:testOperationOperationIsNotReady() (gas: 57821) -TimelockControllerTest:testOperationPendingIfExecuted() (gas: 98665) -TimelockControllerTest:testOperationPendingIfNotYetExecuted() (gas: 52751) -TimelockControllerTest:testOperationPredecessorInvalid() (gas: 63001) -TimelockControllerTest:testOperationPredecessorMultipleNotExecuted() (gas: 92638) -TimelockControllerTest:testOperationPredecessorNotExecuted() (gas: 99399) -TimelockControllerTest:testOperationPredecessorNotScheduled() (gas: 66880) -TimelockControllerTest:testOperationReadyAfterTheExecutionTime() (gas: 53306) -TimelockControllerTest:testOperationReadyBeforeTheExecutionTime() (gas: 53249) -TimelockControllerTest:testOperationReadyOnTheExecutionTime() (gas: 53143) -TimelockControllerTest:testOperationTargetRevert() (gas: 110257) -TimelockControllerTest:testOperationTimestampHasBeenExecuted() (gas: 98439) -TimelockControllerTest:testOperationTimestampHasNotBeenExecuted() (gas: 52538) -TimelockControllerTest:testProposerCanBatchSchedule() (gas: 3088649) -TimelockControllerTest:testProposerCanCancel() (gas: 20647) -TimelockControllerTest:testProposerCanSchedule() (gas: 75567) -TimelockControllerTest:testProposerCannotBatchExecute() (gas: 1490192) -TimelockControllerTest:testProposerCannotExecute() (gas: 23909) +TimelockControllerTest:testInitialSetup() (gas: 4451537) +TimelockControllerTest:testInvalidOperation() (gas: 10742) +TimelockControllerTest:testOperationAlreadyScheduled() (gas: 52535) +TimelockControllerTest:testOperationCancelFinished() (gas: 102187) +TimelockControllerTest:testOperationEqualAndGreaterMinimumDelay() (gas: 90810) +TimelockControllerTest:testOperationHasBeenExecuted() (gas: 100187) +TimelockControllerTest:testOperationHasNotBeenExecuted() (gas: 52733) +TimelockControllerTest:testOperationInsufficientDelay() (gas: 19660) +TimelockControllerTest:testOperationMinimumDelayUpdate() (gas: 61789) +TimelockControllerTest:testOperationOperationIsNotReady() (gas: 57913) +TimelockControllerTest:testOperationPendingIfExecuted() (gas: 98757) +TimelockControllerTest:testOperationPendingIfNotYetExecuted() (gas: 52797) +TimelockControllerTest:testOperationPredecessorInvalid() (gas: 63093) +TimelockControllerTest:testOperationPredecessorMultipleNotExecuted() (gas: 92753) +TimelockControllerTest:testOperationPredecessorNotExecuted() (gas: 99537) +TimelockControllerTest:testOperationPredecessorNotScheduled() (gas: 66995) +TimelockControllerTest:testOperationReadyAfterTheExecutionTime() (gas: 53352) +TimelockControllerTest:testOperationReadyBeforeTheExecutionTime() (gas: 53295) +TimelockControllerTest:testOperationReadyOnTheExecutionTime() (gas: 53189) +TimelockControllerTest:testOperationTargetRevert() (gas: 110441) +TimelockControllerTest:testOperationTimestampHasBeenExecuted() (gas: 98531) +TimelockControllerTest:testOperationTimestampHasNotBeenExecuted() (gas: 52584) +TimelockControllerTest:testProposerCanBatchSchedule() (gas: 3088695) +TimelockControllerTest:testProposerCanCancel() (gas: 20693) +TimelockControllerTest:testProposerCanSchedule() (gas: 75613) +TimelockControllerTest:testProposerCannotBatchExecute() (gas: 1490238) +TimelockControllerTest:testProposerCannotExecute() (gas: 24001) TimelockControllerTest:testReturnsLaterMinimumDelayForCalls() (gas: 20780) -TimelockControllerTest:testRevertWhenNotTimelock() (gas: 9249) -TimelockControllerTest:testScheduleAndExecuteWithEmptySalt() (gas: 107902) -TimelockControllerTest:testScheduleAndExecuteWithNonEmptySalt() (gas: 111259) -TimelockControllerTest:testStrangerCannotBatchExecute() (gas: 748795) -TimelockControllerTest:testStrangerCannotBatchSchedule() (gas: 746602) -TimelockControllerTest:testStrangerCannotCancel() (gas: 11494) -TimelockControllerTest:testStrangerCannotExecute() (gas: 16569) -TimelockControllerTest:testStrangerCannotSchedule() (gas: 14353) +TimelockControllerTest:testRevertWhenNotTimelock() (gas: 9295) +TimelockControllerTest:testScheduleAndExecuteWithEmptySalt() (gas: 108063) +TimelockControllerTest:testScheduleAndExecuteWithNonEmptySalt() (gas: 111420) +TimelockControllerTest:testStrangerCannotBatchExecute() (gas: 748818) +TimelockControllerTest:testStrangerCannotBatchSchedule() (gas: 746625) +TimelockControllerTest:testStrangerCannotCancel() (gas: 11517) +TimelockControllerTest:testStrangerCannotExecute() (gas: 16615) +TimelockControllerTest:testStrangerCannotSchedule() (gas: 14376) TimelockControllerTest:testSupportsInterfaceInvalidInterfaceId() (gas: 8490) TimelockControllerTest:testSupportsInterfaceInvalidInterfaceIdGasCost() (gas: 9280) TimelockControllerTest:testSupportsInterfaceSuccess() (gas: 10814) diff --git a/CHANGELOG.md b/CHANGELOG.md index b3d511b9..1335700b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ - [`AccessControl`](https://github.com/pcaversaccio/snekmate/blob/v0.1.0/src/snekmate/auth/AccessControl.vy): Make `AccessControl` module-friendly. ([#216](https://github.com/pcaversaccio/snekmate/pull/216)) - [`Ownable`](https://github.com/pcaversaccio/snekmate/blob/v0.1.0/src/snekmate/auth/Ownable.vy): Make `Ownable` module-friendly. ([#218](https://github.com/pcaversaccio/snekmate/pull/218)) - [`Ownable2Step`](https://github.com/pcaversaccio/snekmate/blob/v0.1.0/src/snekmate/auth/Ownable2Step.vy): Make `Ownable2Step` module-friendly. ([#219](https://github.com/pcaversaccio/snekmate/pull/219)) +- **Governance** + - [`TimelockController`](https://github.com/pcaversaccio/snekmate/blob/v0.1.0/src/snekmate/governance/TimelockController.vy): Make `TimelockController` module-friendly. ([#220](https://github.com/pcaversaccio/snekmate/pull/220)) - **Vyper Contract Deployer** - [`VyperDeployer`](https://github.com/pcaversaccio/snekmate/blob/v0.1.0/lib/utils/VyperDeployer.sol): Improve error message in the event of a Vyper compilation error. ([#219](https://github.com/pcaversaccio/snekmate/pull/219)) diff --git a/README.md b/README.md index 8b714cbd..ea287b69 100644 --- a/README.md +++ b/README.md @@ -21,15 +21,21 @@ src │ ├── Ownable — "Owner-Based Access Control Functions" │ ├── Ownable2Step — "2-Step Ownership Transfer Functions" │ ├── AccessControl — "Multi-Role-Based Access Control Functions" - │ └── interfaces - │ └── IAccessControl — "AccessControl Interface Definition" + │ ├── interfaces + │ │ └── IAccessControl — "AccessControl Interface Definition" + │ └── mocks + │ ├── OwnableMock — "Ownable Module Reference Implementation" + │ ├── Ownable2StepMock — "Ownable2Step Module Reference Implementation" + │ └── AccessControlMock — "AccessControl Module Reference Implementation" ├── extensions │ ├── ERC2981 — "ERC-721 and ERC-1155 Compatible ERC-2981 Reference Implementation" │ ├── ERC4626 — "Modern and Gas-Efficient ERC-4626 Tokenised Vault Implementation" │ └── interfaces │ └── IERC2981 — "EIP-2981 Interface Definition" ├── governance - │ └── TimelockController — "Multi-Role-Based Timelock Controller Reference Implementation" + │ ├── TimelockController — "Multi-Role-Based Timelock Controller Reference Implementation" + │ └── mocks + │ └── TimelockControllerMock — "TimelockController Module Reference Implementation" ├── tokens │ ├── ERC20 — "Modern and Gas-Efficient ERC-20 + EIP-2612 Implementation" │ ├── ERC721 — "Modern and Gas-Efficient ERC-721 + EIP-4494 Implementation" diff --git a/src/snekmate/auth/Ownable2Step.vy b/src/snekmate/auth/Ownable2Step.vy index c4c58615..87cefffa 100644 --- a/src/snekmate/auth/Ownable2Step.vy +++ b/src/snekmate/auth/Ownable2Step.vy @@ -7,9 +7,14 @@ @notice These functions can be used to implement a basic access control mechanism, where there is an account (an owner) that can be granted exclusive access to specific functions. - By default, the owner account will be the one that deploys - the contract. This can later be changed with `transfer_ownership` - and `accept_ownership`. + This extension to the {Ownable} contract includes a two-step + ownership transfer mechanism where the new owner must call + `accept_ownership` to replace the old one. This can help + avoid common mistakes, such as ownership transfers to incorrect + accounts or to contracts that are unable to interact with + the permission system. By default, the owner account will + be the one that deploys the contract. This can later be + changed with `transfer_ownership` and `accept_ownership`. The implementation is inspired by OpenZeppelin's implementation here: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable2Step.sol. """ diff --git a/src/snekmate/auth/mocks/AccessControlMock.vy b/src/snekmate/auth/mocks/AccessControlMock.vy index a2afca8d..67da78ad 100644 --- a/src/snekmate/auth/mocks/AccessControlMock.vy +++ b/src/snekmate/auth/mocks/AccessControlMock.vy @@ -25,14 +25,6 @@ from .. import AccessControl as ac initializes: ac -# @dev The 32-byte minter role. -MINTER_ROLE: public(constant(bytes32)) = keccak256("MINTER_ROLE") - - -# @dev The 32-byte pauser role. -PAUSER_ROLE: public(constant(bytes32)) = keccak256("PAUSER_ROLE") - - # @dev We export (i.e. the runtime bytecode exposes these # functions externally, allowing them to be called using # the ABI encoding specification) all `external` functions @@ -42,8 +34,8 @@ PAUSER_ROLE: public(constant(bytes32)) = keccak256("PAUSER_ROLE") # `immutable`, and state variables, for which Vyper automatically # generates an `external` getter function for the variable. exports: ( - ac.supportsInterface, ac.DEFAULT_ADMIN_ROLE, + ac.supportsInterface, ac.hasRole, ac.getRoleAdmin, ac.grantRole, @@ -53,6 +45,14 @@ exports: ( ) +# @dev The 32-byte minter role. +MINTER_ROLE: public(constant(bytes32)) = keccak256("MINTER_ROLE") + + +# @dev The 32-byte pauser role. +PAUSER_ROLE: public(constant(bytes32)) = keccak256("PAUSER_ROLE") + + @deploy @payable def __init__(): diff --git a/src/snekmate/governance/TimelockController.vy b/src/snekmate/governance/TimelockController.vy index 86a59104..b3cfa17a 100644 --- a/src/snekmate/governance/TimelockController.vy +++ b/src/snekmate/governance/TimelockController.vy @@ -67,15 +67,36 @@ from ..tokens.interfaces import IERC1155Receiver implements: IERC1155Receiver -# @dev The default 32-byte admin role. -# @notice If you declare a variable as `public`, -# Vyper automatically generates an `external` -# getter function for the variable. -DEFAULT_ADMIN_ROLE: public(constant(bytes32)) = empty(bytes32) +# @dev We import and use the `AccessControl` module. +from ..auth import AccessControl as access_control +uses: access_control + + +# @dev We export (i.e. the runtime bytecode exposes these +# functions externally, allowing them to be called using +# the ABI encoding specification) all `external` functions +# (with the exception of the `supportsInterface` function) +# from the `AccessControl` module. +# @notice Please note that you must always also export (if +# required by the contract logic) `public` declared `constant`, +# `immutable`, and state variables, for which Vyper automatically +# generates an `external` getter function for the variable. +exports: ( + access_control.DEFAULT_ADMIN_ROLE, + access_control.hasRole, + access_control.getRoleAdmin, + access_control.grantRole, + access_control.revokeRole, + access_control.renounceRole, + access_control.set_role_admin, +) # @dev The 32-byte proposer role. # @notice Responsible for proposing operations. +# If you declare a variable as `public`, +# Vyper automatically generates an `external` +# getter function for the variable. PROPOSER_ROLE: public(constant(bytes32)) = keccak256("PROPOSER_ROLE") @@ -153,14 +174,6 @@ get_timestamp: public(HashMap[bytes32, uint256]) get_minimum_delay: public(uint256) -# @dev Returns `True` if `account` has been granted `role`. -hasRole: public(HashMap[bytes32, HashMap[address, bool]]) - - -# @dev Returns the admin role that controls `role`. -getRoleAdmin: public(HashMap[bytes32, bytes32]) - - # @dev Emitted when a call is scheduled as part of # operation `id`. Note that `index` is the index # position of the proposal. If the proposal is @@ -206,39 +219,6 @@ event MinimumDelayChange: new_duration: uint256 -# @dev Emitted when `new_admin_role` is set as -# `role`'s admin role, replacing `previous_admin_role`. -# Note that `DEFAULT_ADMIN_ROLE` is the starting -# admin for all roles, despite `RoleAdminChanged` -# not being emitted signaling this. -event RoleAdminChanged: - role: indexed(bytes32) - previous_admin_role: indexed(bytes32) - new_admin_role: indexed(bytes32) - - -# @dev Emitted when `account` is granted `role`. -# Note that `sender` is the account (an admin -# role bearer) that originated the contract call. -event RoleGranted: - role: indexed(bytes32) - account: indexed(address) - sender: indexed(address) - - -# @dev Emitted when `account` is revoked `role`. -# Note that `sender` is the account that originated -# the contract call: -# - if using `revokeRole`, it is the admin role -# bearer, -# - if using `renounceRole`, it is the role bearer -# (i.e. `account`). -event RoleRevoked: - role: indexed(bytes32) - account: indexed(address) - sender: indexed(address) - - @deploy @payable def __init__(minimum_delay_: uint256, proposers_: DynArray[address, _DYNARRAY_BOUND], executors_: DynArray[address, _DYNARRAY_BOUND], admin_: address): @@ -271,20 +251,20 @@ def __init__(minimum_delay_: uint256, proposers_: DynArray[address, _DYNARRAY_BO role. """ # Configure the contract to be self-administered. - self._grant_role(DEFAULT_ADMIN_ROLE, self) + access_control._grant_role(access_control.DEFAULT_ADMIN_ROLE, self) # Set the optional admin. if (admin_ != empty(address)): - self._grant_role(DEFAULT_ADMIN_ROLE, admin_) + access_control._grant_role(access_control.DEFAULT_ADMIN_ROLE, admin_) # Register the proposers and cancellers. for proposer: address in proposers_: - self._grant_role(PROPOSER_ROLE, proposer) - self._grant_role(CANCELLER_ROLE, proposer) + access_control._grant_role(PROPOSER_ROLE, proposer) + access_control._grant_role(CANCELLER_ROLE, proposer) # Register the executors. for executor: address in executors_: - self._grant_role(EXECUTOR_ROLE, executor) + access_control._grant_role(EXECUTOR_ROLE, executor) # Set the minimum delay. self.get_minimum_delay = minimum_delay_ @@ -434,7 +414,7 @@ def schedule(target: address, amount: uint256, payload: Bytes[1_024], predecesso @param delay The 32-byte delay before the operation becomes valid. Must be greater than or equal to the minimum delay. """ - self._check_role(PROPOSER_ROLE, msg.sender) + access_control._check_role(PROPOSER_ROLE, msg.sender) id: bytes32 = self._hash_operation(target, amount, payload, predecessor, salt) self._schedule(id, delay) @@ -462,7 +442,7 @@ def schedule_batch(targets: DynArray[address, _DYNARRAY_BOUND], amounts: DynArra @param delay The 32-byte delay before the operation becomes valid. Must be greater than or equal to the minimum delay. """ - self._check_role(PROPOSER_ROLE, msg.sender) + access_control._check_role(PROPOSER_ROLE, msg.sender) assert len(targets) == len(amounts) and len(targets) == len(payloads), "TimelockController: length mismatch" id: bytes32 = self._hash_operation_batch(targets, amounts, payloads, predecessor, salt) @@ -486,7 +466,7 @@ def cancel(id: bytes32): @notice Note that the caller must have the `CANCELLER_ROLE` role. @param id The 32-byte operation identifier. """ - self._check_role(CANCELLER_ROLE, msg.sender) + access_control._check_role(CANCELLER_ROLE, msg.sender) assert self._is_operation_pending(id), "TimelockController: operation cannot be cancelled" self.get_timestamp[id] = empty(uint256) log Cancelled(id) @@ -575,50 +555,6 @@ def update_delay(new_delay: uint256): self.get_minimum_delay = new_delay -@external -def grantRole(role: bytes32, account: address): - """ - @dev Sourced from {AccessControl-grantRole}. - @notice See {AccessControl-grantRole} for the - function docstring. - """ - self._check_role(self.getRoleAdmin[role], msg.sender) - self._grant_role(role, account) - - -@external -def revokeRole(role: bytes32, account: address): - """ - @dev Sourced from {AccessControl-revokeRole}. - @notice See {AccessControl-revokeRole} for the - function docstring. - """ - self._check_role(self.getRoleAdmin[role], msg.sender) - self._revoke_role(role, account) - - -@external -def renounceRole(role: bytes32, account: address): - """ - @dev Sourced from {AccessControl-renounceRole}. - @notice See {AccessControl-renounceRole} for the - function docstring. - """ - assert account == msg.sender, "AccessControl: can only renounce roles for itself" - self._revoke_role(role, account) - - -@external -def set_role_admin(role: bytes32, admin_role: bytes32): - """ - @dev Sourced from {AccessControl-set_role_admin}. - @notice See {AccessControl-set_role_admin} for the - function docstring. - """ - self._check_role(self.getRoleAdmin[role], msg.sender) - self._set_role_admin(role, admin_role) - - @external def onERC721Received(operator: address, owner: address, token_id: uint256, data: Bytes[1_024]) -> bytes4: """ @@ -881,52 +817,5 @@ def _only_role_or_open_role(role: bytes32): enabling this role for everyone. @param role The 32-byte role definition. """ - if (not(self.hasRole[role][empty(address)])): - self._check_role(role, msg.sender) - - -@internal -@view -def _check_role(role: bytes32, account: address): - """ - @dev Sourced from {AccessControl-_check_role}. - @notice See {AccessControl-_check_role} for the - function docstring. - """ - assert self.hasRole[role][account], "AccessControl: account is missing role" - - -@internal -def _set_role_admin(role: bytes32, admin_role: bytes32): - """ - @dev Sourced from {AccessControl-_set_role_admin}. - @notice See {AccessControl-_set_role_admin} for the - function docstring. - """ - previous_admin_role: bytes32 = self.getRoleAdmin[role] - self.getRoleAdmin[role] = admin_role - log RoleAdminChanged(role, previous_admin_role, admin_role) - - -@internal -def _grant_role(role: bytes32, account: address): - """ - @dev Sourced from {AccessControl-_grant_role}. - @notice See {AccessControl-_grant_role} for the - function docstring. - """ - if (not(self.hasRole[role][account])): - self.hasRole[role][account] = True - log RoleGranted(role, account, msg.sender) - - -@internal -def _revoke_role(role: bytes32, account: address): - """ - @dev Sourced from {AccessControl-_revoke_role}. - @notice See {AccessControl-_revoke_role} for the - function docstring. - """ - if (self.hasRole[role][account]): - self.hasRole[role][account] = False - log RoleRevoked(role, account, msg.sender) + if (not(access_control.hasRole[role][empty(address)])): + access_control._check_role(role, msg.sender) diff --git a/src/snekmate/governance/mocks/TimelockControllerMock.vy b/src/snekmate/governance/mocks/TimelockControllerMock.vy new file mode 100644 index 00000000..3c0f99ad --- /dev/null +++ b/src/snekmate/governance/mocks/TimelockControllerMock.vy @@ -0,0 +1,126 @@ +# pragma version ~=0.4.0b5 +""" +@title TimelockController Module Reference Implementation +@custom:contract-name TimelockControllerMock +@license GNU Affero General Public License v3.0 only +@author pcaversaccio +""" + + +# @dev We import and implement the `IERC165` interface, +# which is a built-in interface of the Vyper compiler. +from ethereum.ercs import IERC165 +implements: IERC165 + + +# @dev We import and implement the `IAccessControl` +# interface, which is written using standard Vyper +# syntax. +from ...auth.interfaces import IAccessControl +implements: IAccessControl + + +# @dev We import and implement the `IERC721Receiver` +# interface, which is written using standard Vyper +# syntax. +from ...tokens.interfaces import IERC721Receiver +implements: IERC721Receiver + + +# @dev We import and implement the `IERC1155Receiver` +# interface, which is written using standard Vyper +# syntax. +from ...tokens.interfaces import IERC1155Receiver +implements: IERC1155Receiver + + +# @dev We import and initialise the `AccessControl` module. +from ...auth import AccessControl as ac +initializes: ac + + +# @dev We import and initialise the `TimelockController` module. +from .. import TimelockController as tc +initializes: tc[access_control := ac] + + +# @dev We export (i.e. the runtime bytecode exposes these +# functions externally, allowing them to be called using +# the ABI encoding specification) all `external` functions +# from the `TimelockController` module. +# @notice Please note that you must always also export (if +# required by the contract logic) `public` declared `constant`, +# `immutable`, and state variables, for which Vyper automatically +# generates an `external` getter function for the variable. +exports: ( + tc.DEFAULT_ADMIN_ROLE, + tc.PROPOSER_ROLE, + tc.EXECUTOR_ROLE, + tc.CANCELLER_ROLE, + tc.IERC721_TOKENRECEIVER_SELECTOR, + tc.IERC1155_TOKENRECEIVER_SINGLE_SELECTOR, + tc.IERC1155_TOKENRECEIVER_BATCH_SELECTOR, + tc.__default__, + tc.supportsInterface, + tc.onERC721Received, + tc.onERC1155Received, + tc.onERC1155BatchReceived, + tc.hasRole, + tc.getRoleAdmin, + tc.grantRole, + tc.revokeRole, + tc.renounceRole, + tc.set_role_admin, + tc.get_timestamp, + tc.get_minimum_delay, + tc.is_operation, + tc.is_operation_pending, + tc.is_operation_ready, + tc.is_operation_done, + tc.get_operation_state, + tc.hash_operation, + tc.hash_operation_batch, + tc.schedule, + tc.schedule_batch, + tc.cancel, + tc.execute, + tc.execute_batch, + tc.update_delay, +) + + +@deploy +@payable +def __init__(minimum_delay_: uint256, proposers_: DynArray[address, tc._DYNARRAY_BOUND], executors_: DynArray[address, tc._DYNARRAY_BOUND], admin_: address): + """ + @dev Initialises the contract with the following parameters: + - `minimum_delay_`: The initial minimum delay in seconds + for operations, + - `proposers_`: The accounts to be granted proposer and + canceller roles, + - `executors_`: The accounts to be granted executor role, + - `admin_`: The optional account to be granted admin role + (disable with the zero address). + + IMPORTANT: The optional admin can aid with initial + configuration of roles after deployment without being + subject to delay, but this role should be subsequently + renounced in favor of administration through timelocked + proposals. + + To omit the opcodes for checking the `msg.value` + in the creation-time EVM bytecode, the constructor + is declared as `payable`. + @param minimum_delay_ The 32-byte minimum delay in seconds + for operations. + @param proposers_ The 20-byte array of accounts to be granted + proposer and canceller roles. + @param executors_ The 20-byte array of accounts to be granted + executor role. + @param admin_ The 20-byte (optional) account to be granted admin + role. + """ + # The following line assigns the `DEFAULT_ADMIN_ROLE` + # to the `msg.sender`. + ac.__init__() + tc.__init__(minimum_delay_, proposers_, executors_, admin_) diff --git a/test/governance/TimelockController.t.sol b/test/governance/TimelockController.t.sol index 306f9e7d..1dec78dc 100644 --- a/test/governance/TimelockController.t.sol +++ b/test/governance/TimelockController.t.sol @@ -18,9 +18,9 @@ import {ITimelockController} from "./interfaces/ITimelockController.sol"; /** * @dev The standard access control functionalities are not tested as they - * were taken 1:1 from `AccessControl.vy`. See `AccessControl.t.sol` for the - * corresponding tests. However, please integrate these tests into your own - * test suite before deploying `TimelockController` into production! + * are imported via the `AccessControl` module. See `AccessControl.t.sol` + * for the corresponding tests. However, please integrate these tests into + * your own test suite before deploying `TimelockController` into production! */ contract TimelockControllerTest is Test { bytes32 private constant DEFAULT_ADMIN_ROLE = bytes32(0); @@ -102,8 +102,8 @@ contract TimelockControllerTest is Test { bytes memory args = abi.encode(MIN_DELAY, proposers_, executors_, self); timelockController = ITimelockController( vyperDeployer.deployContract( - "src/snekmate/governance/", - "TimelockController", + "src/snekmate/governance/mocks/", + "TimelockControllerMock", args ) ); @@ -272,8 +272,8 @@ contract TimelockControllerTest is Test { emit ITimelockController.MinimumDelayChange(0, MIN_DELAY); timelockControllerInitialEventEmptyAdmin = ITimelockController( vyperDeployer.deployContract( - "src/snekmate/governance/", - "TimelockController", + "src/snekmate/governance/mocks/", + "TimelockControllerMock", argsEmptyAdmin ) ); @@ -455,8 +455,8 @@ contract TimelockControllerTest is Test { emit ITimelockController.MinimumDelayChange(0, MIN_DELAY); timelockControllerInitialEventNonEmptyAdmin = ITimelockController( vyperDeployer.deployContract( - "src/snekmate/governance/", - "TimelockController", + "src/snekmate/governance/mocks/", + "TimelockControllerMock", argsNonEmptyAdmin ) ); @@ -4222,8 +4222,8 @@ contract TimelockControllerInvariants is Test { bytes memory args = abi.encode(minDelay, proposers, executors, self); timelockController = ITimelockController( vyperDeployer.deployContract( - "src/snekmate/governance/", - "TimelockController", + "src/snekmate/governance/mocks/", + "TimelockControllerMock", args ) ); @@ -4253,7 +4253,7 @@ contract TimelockControllerInvariants is Test { * @dev The number of scheduled transactions cannot exceed the number of * executed transactions. */ - function invariantExecutedLessThanOrEqualToScheduled() public view { + function statefulFuzzExecutedLessThanOrEqualToScheduled() public view { assertTrue( timelockControllerHandler.executeCount() <= timelockControllerHandler.scheduleCount() @@ -4263,7 +4263,7 @@ contract TimelockControllerInvariants is Test { /** * @dev The number of proposals executed must match the count number. */ - function invariantProposalsExecutedMatchCount() public view { + function statefulFuzzProposalsExecutedMatchCount() public view { assertEq( timelockControllerHandler.executeCount(), timelockControllerHandler.counter() @@ -4273,7 +4273,7 @@ contract TimelockControllerInvariants is Test { /** * @dev Proposals can only be scheduled and executed once. */ - function invariantOnceProposalExecution() public { + function statefulFuzzOnceProposalExecution() public { uint256[] memory executed = timelockControllerHandler.getExecuted(); for (uint256 i = 0; i < executed.length; ++i) { // Ensure that the executed proposal cannot be executed again. @@ -4294,7 +4294,7 @@ contract TimelockControllerInvariants is Test { * @dev The sum of the executed proposals and the cancelled proposals must * be less than or equal to the number of scheduled proposals. */ - function invariantSumOfProposals() public view { + function statefulFuzzSumOfProposals() public view { assertTrue( (timelockControllerHandler.cancelCount() + timelockControllerHandler.executeCount()) <= @@ -4305,7 +4305,7 @@ contract TimelockControllerInvariants is Test { /** * @dev The executed proposals cannot be cancelled. */ - function invariantExecutedProposalCancellation() public { + function statefulFuzzExecutedProposalCancellation() public { bytes32 operationId; uint256[] memory executed = timelockControllerHandler.getExecuted(); for (uint256 i = 0; i < executed.length; ++i) { @@ -4329,7 +4329,7 @@ contract TimelockControllerInvariants is Test { /** * @dev The execution of a proposal that has been cancelled is not possible. */ - function invariantExecutingCancelledProposal() public { + function statefulFuzzExecutingCancelledProposal() public { uint256[] memory cancelled = timelockControllerHandler.getCancelled(); for (uint256 i = 0; i < cancelled.length; ++i) { // Ensure that the cancelled proposal cannot be executed. @@ -4349,7 +4349,7 @@ contract TimelockControllerInvariants is Test { /** * @dev The execution of a proposal that is not ready is not possible. */ - function invariantExecutingNotReadyProposal() public { + function statefulFuzzExecutingNotReadyProposal() public { uint256[] memory pending = timelockControllerHandler.getPending(); for (uint256 i = 0; i < pending.length; ++i) { // Ensure that the pending proposal cannot be executed.