diff --git a/.gas-snapshot b/.gas-snapshot index 9f91dc85..994774a5 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -4,12 +4,12 @@ AccessControlTest:testFuzzGrantRoleAdminRoleSuccess(address) (runs: 256, μ: 437 AccessControlTest:testFuzzGrantRoleMultipleTimesSuccess(address) (runs: 256, μ: 48930, ~: 48930) AccessControlTest:testFuzzGrantRoleNonAdmin(address,address) (runs: 256, μ: 16544, ~: 16544) AccessControlTest:testFuzzGrantRoleSuccess(address) (runs: 256, μ: 43797, ~: 43797) -AccessControlTest:testFuzzRenounceRoleMultipleTimesSuccess(address) (runs: 256, μ: 44372, ~: 44356) +AccessControlTest:testFuzzRenounceRoleMultipleTimesSuccess(address) (runs: 256, μ: 44371, ~: 44356) AccessControlTest:testFuzzRenounceRoleNonMsgSender(address) (runs: 256, μ: 9556, ~: 9556) -AccessControlTest:testFuzzRenounceRoleSuccess(address) (runs: 256, μ: 40867, ~: 40851) -AccessControlTest:testFuzzRevokeRoleMultipleTimesSuccess(address) (runs: 256, μ: 40637, ~: 40620) +AccessControlTest:testFuzzRenounceRoleSuccess(address) (runs: 256, μ: 40866, ~: 40851) +AccessControlTest:testFuzzRevokeRoleMultipleTimesSuccess(address) (runs: 256, μ: 40636, ~: 40620) AccessControlTest:testFuzzRevokeRoleNonAdmin(address,address) (runs: 256, μ: 16498, ~: 16498) -AccessControlTest:testFuzzRevokeRoleSuccess(address) (runs: 256, μ: 39481, ~: 39464) +AccessControlTest:testFuzzRevokeRoleSuccess(address) (runs: 256, μ: 39480, ~: 39464) AccessControlTest:testFuzzSetRoleAdminPreviousAdminCallsGrantRole(address,address) (runs: 256, μ: 75747, ~: 75747) AccessControlTest:testFuzzSetRoleAdminPreviousAdminCallsRevokeRole(address,address) (runs: 256, μ: 101276, ~: 101276) AccessControlTest:testFuzzSetRoleAdminSuccess(address,address) (runs: 256, μ: 89648, ~: 89648) @@ -60,8 +60,8 @@ BatchDistributorTest:testDistributeTokenMultipleAddressesSuccess() (gas: 616202) BatchDistributorTest:testDistributeTokenOneAddressSuccess() (gas: 578527) BatchDistributorTest:testDistributeTokenRevertWithInsufficientAllowance() (gas: 574268) BatchDistributorTest:testDistributeTokenRevertWithInsufficientBalance() (gas: 575054) -BatchDistributorTest:testFuzzDistributeEtherMultipleAddressesSuccess(((address,uint256)[]),uint256) (runs: 256, μ: 1812214, ~: 1793268) -BatchDistributorTest:testFuzzDistributeTokenMultipleAddressesSuccess(((address,uint256)[]),address,uint256) (runs: 256, μ: 1337340, ~: 1321378) +BatchDistributorTest:testFuzzDistributeEtherMultipleAddressesSuccess(((address,uint256)[]),uint256) (runs: 256, μ: 1840920, ~: 1861320) +BatchDistributorTest:testFuzzDistributeTokenMultipleAddressesSuccess(((address,uint256)[]),address,uint256) (runs: 256, μ: 1337465, ~: 1321455) Create2AddressTest:testComputeAddress() (gas: 550599) Create2AddressTest:testComputeAddressSelf() (gas: 558922) Create2AddressTest:testFuzzComputeAddress(bytes32,address) (runs: 256, μ: 551194, ~: 551194) @@ -87,26 +87,26 @@ CreateAddressTest:testComputeAddressSelfNonceUint56() (gas: 539228) CreateAddressTest:testComputeAddressSelfNonceUint64() (gas: 539345) CreateAddressTest:testComputeAddressSelfNonceUint8() (gas: 538951) CreateAddressTest:testComputeAddressSelfRevertTooHighNonce() (gas: 8836) -CreateAddressTest:testFuzzComputeAddressNonce0x7f(uint64,address) (runs: 256, μ: 537963, ~: 538066) -CreateAddressTest:testFuzzComputeAddressNonceUint16(uint64,address) (runs: 256, μ: 537412, ~: 537681) -CreateAddressTest:testFuzzComputeAddressNonceUint24(uint64,address) (runs: 256, μ: 537536, ~: 537657) -CreateAddressTest:testFuzzComputeAddressNonceUint32(uint64,address) (runs: 256, μ: 537551, ~: 537664) -CreateAddressTest:testFuzzComputeAddressNonceUint40(uint64,address) (runs: 256, μ: 537662, ~: 537756) -CreateAddressTest:testFuzzComputeAddressNonceUint48(uint64,address) (runs: 256, μ: 537641, ~: 537740) -CreateAddressTest:testFuzzComputeAddressNonceUint56(uint64,address) (runs: 256, μ: 537687, ~: 537769) -CreateAddressTest:testFuzzComputeAddressNonceUint64(uint64,address) (runs: 256, μ: 537668, ~: 537873) -CreateAddressTest:testFuzzComputeAddressNonceUint8(uint64,address) (runs: 256, μ: 537454, ~: 537531) -CreateAddressTest:testFuzzComputeAddressRevertTooHighNonce(uint256,address) (runs: 256, μ: 12926, ~: 12857) -CreateAddressTest:testFuzzComputeAddressSelfNonce0x7f(uint64) (runs: 256, μ: 543802, ~: 543905) -CreateAddressTest:testFuzzComputeAddressSelfNonceUint16(uint64) (runs: 256, μ: 543016, ~: 542941) -CreateAddressTest:testFuzzComputeAddressSelfNonceUint24(uint64) (runs: 256, μ: 543292, ~: 543395) -CreateAddressTest:testFuzzComputeAddressSelfNonceUint32(uint64) (runs: 256, μ: 543372, ~: 543491) -CreateAddressTest:testFuzzComputeAddressSelfNonceUint40(uint64) (runs: 256, μ: 543390, ~: 543475) -CreateAddressTest:testFuzzComputeAddressSelfNonceUint48(uint64) (runs: 256, μ: 543438, ~: 543549) -CreateAddressTest:testFuzzComputeAddressSelfNonceUint56(uint64) (runs: 256, μ: 543527, ~: 543619) -CreateAddressTest:testFuzzComputeAddressSelfNonceUint64(uint64) (runs: 256, μ: 543599, ~: 543749) -CreateAddressTest:testFuzzComputeAddressSelfNonceUint8(uint64) (runs: 256, μ: 543108, ~: 543186) -CreateAddressTest:testFuzzComputeAddressSelfRevertTooHighNonce(uint256) (runs: 256, μ: 12722, ~: 12696) +CreateAddressTest:testFuzzComputeAddressNonce0x7f(uint64,address) (runs: 256, μ: 537964, ~: 538066) +CreateAddressTest:testFuzzComputeAddressNonceUint16(uint64,address) (runs: 256, μ: 537407, ~: 537680) +CreateAddressTest:testFuzzComputeAddressNonceUint24(uint64,address) (runs: 256, μ: 537539, ~: 537657) +CreateAddressTest:testFuzzComputeAddressNonceUint32(uint64,address) (runs: 256, μ: 537541, ~: 537664) +CreateAddressTest:testFuzzComputeAddressNonceUint40(uint64,address) (runs: 256, μ: 537665, ~: 537756) +CreateAddressTest:testFuzzComputeAddressNonceUint48(uint64,address) (runs: 256, μ: 537642, ~: 537740) +CreateAddressTest:testFuzzComputeAddressNonceUint56(uint64,address) (runs: 256, μ: 537685, ~: 537769) +CreateAddressTest:testFuzzComputeAddressNonceUint64(uint64,address) (runs: 256, μ: 537670, ~: 537873) +CreateAddressTest:testFuzzComputeAddressNonceUint8(uint64,address) (runs: 256, μ: 537453, ~: 537531) +CreateAddressTest:testFuzzComputeAddressRevertTooHighNonce(uint256,address) (runs: 256, μ: 12920, ~: 12857) +CreateAddressTest:testFuzzComputeAddressSelfNonce0x7f(uint64) (runs: 256, μ: 543801, ~: 543905) +CreateAddressTest:testFuzzComputeAddressSelfNonceUint16(uint64) (runs: 256, μ: 543043, ~: 542941) +CreateAddressTest:testFuzzComputeAddressSelfNonceUint24(uint64) (runs: 256, μ: 543302, ~: 543395) +CreateAddressTest:testFuzzComputeAddressSelfNonceUint32(uint64) (runs: 256, μ: 543380, ~: 543491) +CreateAddressTest:testFuzzComputeAddressSelfNonceUint40(uint64) (runs: 256, μ: 543394, ~: 543475) +CreateAddressTest:testFuzzComputeAddressSelfNonceUint48(uint64) (runs: 256, μ: 543437, ~: 543549) +CreateAddressTest:testFuzzComputeAddressSelfNonceUint56(uint64) (runs: 256, μ: 543525, ~: 543619) +CreateAddressTest:testFuzzComputeAddressSelfNonceUint64(uint64) (runs: 256, μ: 543574, ~: 543749) +CreateAddressTest:testFuzzComputeAddressSelfNonceUint8(uint64) (runs: 256, μ: 543109, ~: 543186) +CreateAddressTest:testFuzzComputeAddressSelfRevertTooHighNonce(uint256) (runs: 256, μ: 12735, ~: 12696) ECDSATest:testEthSignedMessageHash() (gas: 5846) ECDSATest:testFuzzEthSignedMessageHash(string) (runs: 256, μ: 6432, ~: 6426) ECDSATest:testFuzzRecoverWithInvalidSignature(bytes,string) (runs: 256, μ: 15159, ~: 15161) @@ -133,7 +133,7 @@ EIP712DomainSeparatorTest:testCachedDomainSeparatorV4() (gas: 7562) EIP712DomainSeparatorTest:testDomainSeparatorV4() (gas: 11305) EIP712DomainSeparatorTest:testEIP712Domain() (gas: 13629) EIP712DomainSeparatorTest:testFuzzDomainSeparatorV4(uint8) (runs: 256, μ: 11460, ~: 11480) -EIP712DomainSeparatorTest:testFuzzEIP712Domain(bytes1,uint8,bytes32,uint256[]) (runs: 256, μ: 19159, ~: 19122) +EIP712DomainSeparatorTest:testFuzzEIP712Domain(bytes1,uint8,bytes32,uint256[]) (runs: 256, μ: 19161, ~: 19122) EIP712DomainSeparatorTest:testFuzzHashTypedDataV4(address,address,uint256,uint256,uint64) (runs: 256, μ: 7698, ~: 7698) EIP712DomainSeparatorTest:testHashTypedDataV4() (gas: 13017) ERC1155Invariants:invariantOwner() (runs: 256, calls: 3840, reverts: 3437) @@ -163,35 +163,35 @@ ERC1155Test:testExistsAfterBatchMint() (gas: 143320) ERC1155Test:testExistsAfterSingleBurn() (gas: 137816) ERC1155Test:testExistsAfterSingleMint() (gas: 119400) ERC1155Test:testExistsBeforeMint() (gas: 7642) -ERC1155Test:testFuzzBurnBatchSuccess(address,uint256,uint256,uint256,uint256) (runs: 256, μ: 132323, ~: 132328) -ERC1155Test:testFuzzBurnSuccess(address,address,uint256) (runs: 256, μ: 140104, ~: 140134) +ERC1155Test:testFuzzBurnBatchSuccess(address,uint256,uint256,uint256,uint256) (runs: 256, μ: 132334, ~: 132343) +ERC1155Test:testFuzzBurnSuccess(address,address,uint256) (runs: 256, μ: 140114, ~: 140134) ERC1155Test:testFuzzRenounceOwnershipNonOwner(address) (runs: 256, μ: 13977, ~: 13977) -ERC1155Test:testFuzzRenounceOwnershipSuccess(address) (runs: 256, μ: 48433, ~: 48416) -ERC1155Test:testFuzzSafeBatchTransferFromByApprovedOperator(address,address,address,uint256,uint256,uint256,uint256,bytes) (runs: 256, μ: 225211, ~: 225191) +ERC1155Test:testFuzzRenounceOwnershipSuccess(address) (runs: 256, μ: 48432, ~: 48416) +ERC1155Test:testFuzzSafeBatchTransferFromByApprovedOperator(address,address,address,uint256,uint256,uint256,uint256,bytes) (runs: 256, μ: 225230, ~: 225191) ERC1155Test:testFuzzSafeBatchTransferFromEOAReceiver(address,address,uint256,uint256,uint256,uint256,bytes) (runs: 256, μ: 190347, ~: 190304) -ERC1155Test:testFuzzSafeBatchTransferFromNoData(address,uint256,uint256,uint256,uint256) (runs: 256, μ: 564164, ~: 564164) -ERC1155Test:testFuzzSafeBatchTransferFromWithData(address,uint256,uint256,uint256,uint256) (runs: 256, μ: 564718, ~: 564321) +ERC1155Test:testFuzzSafeBatchTransferFromNoData(address,uint256,uint256,uint256,uint256) (runs: 256, μ: 564154, ~: 564164) +ERC1155Test:testFuzzSafeBatchTransferFromWithData(address,uint256,uint256,uint256,uint256) (runs: 256, μ: 564729, ~: 564321) ERC1155Test:testFuzzSafeMintBatchEOAReceiver(address,address,uint256,uint256,uint256,uint256,bytes) (runs: 256, μ: 152243, ~: 152222) ERC1155Test:testFuzzSafeMintBatchNoData(address,uint256,uint256,uint256,uint256) (runs: 256, μ: 519951, ~: 519951) ERC1155Test:testFuzzSafeMintBatchNonMinter(address) (runs: 256, μ: 40926, ~: 40926) -ERC1155Test:testFuzzSafeMintBatchWithData(address,uint256,uint256,uint256,uint256) (runs: 256, μ: 520300, ~: 520066) +ERC1155Test:testFuzzSafeMintBatchWithData(address,uint256,uint256,uint256,uint256) (runs: 256, μ: 520295, ~: 520066) ERC1155Test:testFuzzSafeMintEOAReceiver(address,address,uint256,uint256,uint256,uint256,bytes) (runs: 256, μ: 151294, ~: 151259) ERC1155Test:testFuzzSafeMintNoData(address,uint256,uint256,uint256,uint256) (runs: 256, μ: 520118, ~: 520118) ERC1155Test:testFuzzSafeMintNonMinter(address) (runs: 256, μ: 33132, ~: 33132) ERC1155Test:testFuzzSafeMintWithData(address,uint256,uint256,uint256,uint256,bytes) (runs: 256, μ: 521416, ~: 521009) -ERC1155Test:testFuzzSafeTransferFromByApprovedOperator(address,address,address,uint256,uint256,uint256,uint256,bytes) (runs: 256, μ: 204219, ~: 204191) +ERC1155Test:testFuzzSafeTransferFromByApprovedOperator(address,address,address,uint256,uint256,uint256,uint256,bytes) (runs: 256, μ: 204239, ~: 204191) ERC1155Test:testFuzzSafeTransferFromEOAReceiver(address,address,uint256,uint256,uint256,uint256,bytes) (runs: 256, μ: 169443, ~: 169391) -ERC1155Test:testFuzzSafeTransferFromNoData(address,uint256,uint256,uint256,uint256) (runs: 256, μ: 606373, ~: 606383) -ERC1155Test:testFuzzSafeTransferFromWithData(address,uint256,uint256,uint256,uint256,bytes) (runs: 256, μ: 608365, ~: 607645) +ERC1155Test:testFuzzSafeTransferFromNoData(address,uint256,uint256,uint256,uint256) (runs: 256, μ: 606383, ~: 606383) +ERC1155Test:testFuzzSafeTransferFromWithData(address,uint256,uint256,uint256,uint256,bytes) (runs: 256, μ: 608346, ~: 607645) ERC1155Test:testFuzzSetApprovalForAllRevoke(address,address) (runs: 256, μ: 31053, ~: 31022) ERC1155Test:testFuzzSetApprovalForAllSuccess(address,address) (runs: 256, μ: 44181, ~: 44181) ERC1155Test:testFuzzSetMinterNonOwner(address,string) (runs: 256, μ: 15772, ~: 15699) ERC1155Test:testFuzzSetMinterSuccess(string) (runs: 256, μ: 33178, ~: 33178) ERC1155Test:testFuzzSetUriNonMinter(address) (runs: 256, μ: 14452, ~: 14452) -ERC1155Test:testFuzzTotalSupplyAfterBatchBurn(address,uint256,uint256,uint256,uint256) (runs: 256, μ: 126363, ~: 126368) -ERC1155Test:testFuzzTotalSupplyAfterBatchMint(address,uint256,uint256,uint256,uint256) (runs: 256, μ: 140987, ~: 140922) +ERC1155Test:testFuzzTotalSupplyAfterBatchBurn(address,uint256,uint256,uint256,uint256) (runs: 256, μ: 126374, ~: 126383) +ERC1155Test:testFuzzTotalSupplyAfterBatchMint(address,uint256,uint256,uint256,uint256) (runs: 256, μ: 140991, ~: 141055) ERC1155Test:testFuzzTotalSupplyAfterSingleBurn(address,uint256,bytes) (runs: 256, μ: 138036, ~: 138007) -ERC1155Test:testFuzzTotalSupplyAfterSingleMint(uint256,uint256,bytes) (runs: 256, μ: 91653, ~: 119836) +ERC1155Test:testFuzzTotalSupplyAfterSingleMint(uint256,uint256,bytes) (runs: 256, μ: 93285, ~: 119839) ERC1155Test:testFuzzTransferOwnershipNonOwner(address,address) (runs: 256, μ: 14086, ~: 14086) ERC1155Test:testFuzzTransferOwnershipSuccess(address,address) (runs: 256, μ: 73957, ~: 73928) ERC1155Test:testHasOwner() (gas: 9714) @@ -266,8 +266,8 @@ ERC1155Test:testUriBaseAndTokenUriNotSet() (gas: 2768301) ERC1155Test:testUriBaseAndTokenUriSet() (gas: 65517) ERC1155Test:testUriNoBaseURI() (gas: 2818373) ERC1155Test:testUriNoTokenUri() (gas: 17407) -ERC20Invariants:invariantOwner() (runs: 256, calls: 3840, reverts: 3392) -ERC20Invariants:invariantTotalSupply() (runs: 256, calls: 3840, reverts: 3392) +ERC20Invariants:invariantOwner() (runs: 256, calls: 3840, reverts: 3390) +ERC20Invariants:invariantTotalSupply() (runs: 256, calls: 3840, reverts: 3390) ERC20Test:testApproveExceedingBalanceCase1() (gas: 40223) ERC20Test:testApproveExceedingBalanceCase2() (gas: 46478) ERC20Test:testApproveFromZeroAddress() (gas: 13220) @@ -290,26 +290,26 @@ ERC20Test:testCachedDomainSeparator() (gas: 7699) ERC20Test:testDomainSeparator() (gas: 11449) ERC20Test:testEIP712Domain() (gas: 13739) ERC20Test:testFuzzApproveSuccess(address,uint256) (runs: 256, μ: 39193, ~: 40437) -ERC20Test:testFuzzBurnFromInsufficientAllowance(address,uint256,uint8) (runs: 256, μ: 175220, ~: 175283) -ERC20Test:testFuzzBurnFromSuccess(address,uint256) (runs: 256, μ: 279935, ~: 280986) +ERC20Test:testFuzzBurnFromInsufficientAllowance(address,uint256,uint8) (runs: 256, μ: 175155, ~: 175280) +ERC20Test:testFuzzBurnFromSuccess(address,uint256) (runs: 256, μ: 280125, ~: 281154) ERC20Test:testFuzzBurnInvalidAmount(address,uint256) (runs: 256, μ: 16454, ~: 16454) -ERC20Test:testFuzzBurnSuccessCase(uint256) (runs: 256, μ: 256007, ~: 255800) +ERC20Test:testFuzzBurnSuccessCase(uint256) (runs: 256, μ: 256021, ~: 255800) ERC20Test:testFuzzDomainSeparator(uint8) (runs: 256, μ: 11665, ~: 11685) -ERC20Test:testFuzzEIP712Domain(bytes1,uint8,bytes32,uint256[]) (runs: 256, μ: 19449, ~: 19409) +ERC20Test:testFuzzEIP712Domain(bytes1,uint8,bytes32,uint256[]) (runs: 256, μ: 19444, ~: 19409) ERC20Test:testFuzzMintNonMinter(string,uint256) (runs: 256, μ: 13152, ~: 13080) -ERC20Test:testFuzzMintSuccess(string,uint256) (runs: 256, μ: 54130, ~: 55192) +ERC20Test:testFuzzMintSuccess(string,uint256) (runs: 256, μ: 54206, ~: 55192) ERC20Test:testFuzzPermitInvalid(string,string,uint16) (runs: 256, μ: 45279, ~: 45277) ERC20Test:testFuzzPermitSuccess(string,string,uint16) (runs: 256, μ: 70299, ~: 70295) ERC20Test:testFuzzRenounceOwnershipNonOwner(address) (runs: 256, μ: 13979, ~: 13979) -ERC20Test:testFuzzRenounceOwnershipSuccess(address) (runs: 256, μ: 48360, ~: 48345) +ERC20Test:testFuzzRenounceOwnershipSuccess(address) (runs: 256, μ: 48361, ~: 48345) ERC20Test:testFuzzSetMinterNonOwner(address,string) (runs: 256, μ: 15777, ~: 15704) ERC20Test:testFuzzSetMinterSuccess(string) (runs: 256, μ: 33196, ~: 33196) -ERC20Test:testFuzzTransferFromInsufficientAllowance(address,address,uint256,uint8) (runs: 256, μ: 173301, ~: 173761) -ERC20Test:testFuzzTransferFromSuccess(address,address,uint256) (runs: 256, μ: 200334, ~: 202446) +ERC20Test:testFuzzTransferFromInsufficientAllowance(address,address,uint256,uint8) (runs: 256, μ: 173482, ~: 173761) +ERC20Test:testFuzzTransferFromSuccess(address,address,uint256) (runs: 256, μ: 200080, ~: 202152) ERC20Test:testFuzzTransferInvalidAmount(address,address,uint256) (runs: 256, μ: 16849, ~: 16849) ERC20Test:testFuzzTransferOwnershipNonOwner(address,address) (runs: 256, μ: 14068, ~: 14068) -ERC20Test:testFuzzTransferOwnershipSuccess(address,address) (runs: 256, μ: 73902, ~: 73874) -ERC20Test:testFuzzTransferSuccess(address,uint256) (runs: 256, μ: 172886, ~: 173875) +ERC20Test:testFuzzTransferOwnershipSuccess(address,address) (runs: 256, μ: 73901, ~: 73874) +ERC20Test:testFuzzTransferSuccess(address,uint256) (runs: 256, μ: 172825, ~: 173876) ERC20Test:testHasOwner() (gas: 9758) ERC20Test:testInitialSetup() (gas: 1467701) ERC20Test:testMintNonMinter() (gas: 12639) @@ -349,20 +349,20 @@ ERC2981Test:testDeleteDefaultRoyaltyNonOwner() (gas: 10537) ERC2981Test:testFuzzDeleteDefaultRoyaltyNonOwner(address) (runs: 256, μ: 13271, ~: 13271) ERC2981Test:testFuzzRenounceOwnershipNonOwner(address) (runs: 256, μ: 13972, ~: 13972) ERC2981Test:testFuzzRenounceOwnershipSuccess(address) (runs: 256, μ: 24598, ~: 24598) -ERC2981Test:testFuzzResetTokenRoyalty(address,address,uint256,uint256,uint96,uint256) (runs: 256, μ: 93597, ~: 94717) +ERC2981Test:testFuzzResetTokenRoyalty(address,address,uint256,uint256,uint96,uint256) (runs: 256, μ: 93338, ~: 94699) ERC2981Test:testFuzzResetTokenRoyaltyNonOwner(address) (runs: 256, μ: 13246, ~: 13246) -ERC2981Test:testFuzzRoyaltyInfoDefaultRoyalty(address,uint256,uint256,uint96,uint256) (runs: 256, μ: 77988, ~: 79183) -ERC2981Test:testFuzzRoyaltyInfoDeleteDefaultRoyalty(address,uint256,uint256,uint96,uint256) (runs: 256, μ: 61712, ~: 62571) -ERC2981Test:testFuzzRoyaltyInfoSetTokenRoyalty(address,address,uint256,uint256,uint96,uint256) (runs: 256, μ: 119876, ~: 121030) -ERC2981Test:testFuzzRoyaltyInfoSetTokenRoyaltyUpdate(address,address,uint256,uint256,uint96,uint256) (runs: 256, μ: 164296, ~: 165531) -ERC2981Test:testFuzzRoyaltyInfoUpdateDefaultRoyalty(address,address,uint256,uint256,uint96,uint256) (runs: 256, μ: 84143, ~: 84678) +ERC2981Test:testFuzzRoyaltyInfoDefaultRoyalty(address,uint256,uint256,uint96,uint256) (runs: 256, μ: 77900, ~: 79183) +ERC2981Test:testFuzzRoyaltyInfoDeleteDefaultRoyalty(address,uint256,uint256,uint96,uint256) (runs: 256, μ: 61643, ~: 62573) +ERC2981Test:testFuzzRoyaltyInfoSetTokenRoyalty(address,address,uint256,uint256,uint96,uint256) (runs: 256, μ: 118900, ~: 121030) +ERC2981Test:testFuzzRoyaltyInfoSetTokenRoyaltyUpdate(address,address,uint256,uint256,uint96,uint256) (runs: 256, μ: 162152, ~: 165531) +ERC2981Test:testFuzzRoyaltyInfoUpdateDefaultRoyalty(address,address,uint256,uint256,uint96,uint256) (runs: 256, μ: 83741, ~: 84678) ERC2981Test:testFuzzSetDefaultRoyaltyNonOwner(address) (runs: 256, μ: 15525, ~: 15525) -ERC2981Test:testFuzzSetDefaultRoyaltyTooHighFeeNumerator(uint96) (runs: 256, μ: 21342, ~: 21125) +ERC2981Test:testFuzzSetDefaultRoyaltyTooHighFeeNumerator(uint96) (runs: 256, μ: 21316, ~: 21125) ERC2981Test:testFuzzSetTokenRoyaltyInvalidReceiver(address) (runs: 256, μ: 18626, ~: 18626) ERC2981Test:testFuzzSetTokenRoyaltyNonOwner(address) (runs: 256, μ: 15565, ~: 15565) -ERC2981Test:testFuzzSetTokenRoyaltyTooHighFeeNumerator(uint96) (runs: 256, μ: 21383, ~: 21166) +ERC2981Test:testFuzzSetTokenRoyaltyTooHighFeeNumerator(uint96) (runs: 256, μ: 21357, ~: 21166) ERC2981Test:testFuzzTransferOwnershipNonOwner(address,address) (runs: 256, μ: 14039, ~: 14039) -ERC2981Test:testFuzzTransferOwnershipSuccess(address,address) (runs: 256, μ: 29566, ~: 29577) +ERC2981Test:testFuzzTransferOwnershipSuccess(address,address) (runs: 256, μ: 29577, ~: 29577) ERC2981Test:testHasOwner() (gas: 9688) ERC2981Test:testInitialSetup() (gas: 468824) ERC2981Test:testRenounceOwnershipNonOwner() (gas: 10871) @@ -388,8 +388,8 @@ ERC2981Test:testSupportsInterfaceSuccessGasCost() (gas: 6345) ERC2981Test:testTransferOwnershipNonOwner() (gas: 12649) ERC2981Test:testTransferOwnershipSuccess() (gas: 22091) ERC2981Test:testTransferOwnershipToZeroAddress() (gas: 15557) -ERC4626VaultInvariants:invariantTotalAssets() (runs: 256, calls: 3840, reverts: 3244) -ERC4626VaultInvariants:invariantTotalSupply() (runs: 256, calls: 3840, reverts: 3244) +ERC4626VaultInvariants:invariantTotalAssets() (runs: 256, calls: 3840, reverts: 3242) +ERC4626VaultInvariants:invariantTotalSupply() (runs: 256, calls: 3840, reverts: 3242) ERC4626VaultTest:testCachedDomainSeparator() (gas: 7676) ERC4626VaultTest:testDepositInsufficientAllowance() (gas: 82425) ERC4626VaultTest:testDepositWithNoApproval() (gas: 24740) @@ -400,10 +400,10 @@ ERC4626VaultTest:testEmptyVaultDeposit() (gas: 556789) ERC4626VaultTest:testEmptyVaultMint() (gas: 557600) ERC4626VaultTest:testEmptyVaultRedeem() (gas: 191988) ERC4626VaultTest:testEmptyVaultwithdraw() (gas: 204833) -ERC4626VaultTest:testFail_redeem((address[4],uint256[4],uint256[4],int256),uint256) (runs: 256, μ: 554348, ~: 557113) -ERC4626VaultTest:testFail_withdraw((address[4],uint256[4],uint256[4],int256),uint256) (runs: 256, μ: 556261, ~: 560088) +ERC4626VaultTest:testFail_redeem((address[4],uint256[4],uint256[4],int256),uint256) (runs: 256, μ: 554223, ~: 558247) +ERC4626VaultTest:testFail_withdraw((address[4],uint256[4],uint256[4],int256),uint256) (runs: 256, μ: 556033, ~: 560088) ERC4626VaultTest:testFuzzDomainSeparator(uint8) (runs: 256, μ: 11557, ~: 11577) -ERC4626VaultTest:testFuzzEIP712Domain(bytes1,uint8,bytes32,uint256[]) (runs: 256, μ: 19366, ~: 19328) +ERC4626VaultTest:testFuzzEIP712Domain(bytes1,uint8,bytes32,uint256[]) (runs: 256, μ: 19367, ~: 19328) ERC4626VaultTest:testFuzzPermitInvalid(string,string,uint16) (runs: 256, μ: 45211, ~: 45209) ERC4626VaultTest:testFuzzPermitSuccess(string,string,uint16) (runs: 256, μ: 70194, ~: 70190) ERC4626VaultTest:testInitialSetup() (gas: 5784605) @@ -424,30 +424,30 @@ ERC4626VaultTest:testVaultInteractionsForSomeoneElse() (gas: 221714) ERC4626VaultTest:testWithdrawInsufficientAllowance() (gas: 122543) ERC4626VaultTest:testWithdrawInsufficientAssets() (gas: 117830) ERC4626VaultTest:testWithdrawWithNoAssets() (gas: 21327) -ERC4626VaultTest:test_RT_deposit_redeem((address[4],uint256[4],uint256[4],int256),uint256) (runs: 256, μ: 473656, ~: 476219) -ERC4626VaultTest:test_RT_deposit_withdraw((address[4],uint256[4],uint256[4],int256),uint256) (runs: 256, μ: 475496, ~: 477934) -ERC4626VaultTest:test_RT_mint_redeem((address[4],uint256[4],uint256[4],int256),uint256) (runs: 256, μ: 474716, ~: 476410) -ERC4626VaultTest:test_RT_mint_withdraw((address[4],uint256[4],uint256[4],int256),uint256) (runs: 256, μ: 476458, ~: 478182) -ERC4626VaultTest:test_RT_redeem_deposit((address[4],uint256[4],uint256[4],int256),uint256) (runs: 256, μ: 473392, ~: 476462) -ERC4626VaultTest:test_RT_redeem_mint((address[4],uint256[4],uint256[4],int256),uint256) (runs: 256, μ: 473988, ~: 476655) -ERC4626VaultTest:test_RT_withdraw_deposit((address[4],uint256[4],uint256[4],int256),uint256) (runs: 256, μ: 475158, ~: 478033) -ERC4626VaultTest:test_RT_withdraw_mint((address[4],uint256[4],uint256[4],int256),uint256) (runs: 256, μ: 476278, ~: 478681) -ERC4626VaultTest:test_asset((address[4],uint256[4],uint256[4],int256)) (runs: 256, μ: 423325, ~: 427727) -ERC4626VaultTest:test_convertToAssets((address[4],uint256[4],uint256[4],int256),uint256) (runs: 256, μ: 429198, ~: 432743) -ERC4626VaultTest:test_convertToShares((address[4],uint256[4],uint256[4],int256),uint256) (runs: 256, μ: 428579, ~: 432510) -ERC4626VaultTest:test_deposit((address[4],uint256[4],uint256[4],int256),uint256,uint256) (runs: 256, μ: 468470, ~: 472341) -ERC4626VaultTest:test_maxDeposit((address[4],uint256[4],uint256[4],int256)) (runs: 256, μ: 423337, ~: 427740) -ERC4626VaultTest:test_maxMint((address[4],uint256[4],uint256[4],int256)) (runs: 256, μ: 423362, ~: 427765) -ERC4626VaultTest:test_maxRedeem((address[4],uint256[4],uint256[4],int256)) (runs: 256, μ: 423477, ~: 427879) -ERC4626VaultTest:test_maxWithdraw((address[4],uint256[4],uint256[4],int256)) (runs: 256, μ: 425030, ~: 429549) -ERC4626VaultTest:test_mint((address[4],uint256[4],uint256[4],int256),uint256,uint256) (runs: 256, μ: 469577, ~: 472557) -ERC4626VaultTest:test_previewDeposit((address[4],uint256[4],uint256[4],int256),uint256) (runs: 256, μ: 466374, ~: 468752) -ERC4626VaultTest:test_previewMint((address[4],uint256[4],uint256[4],int256),uint256) (runs: 256, μ: 467591, ~: 469131) -ERC4626VaultTest:test_previewRedeem((address[4],uint256[4],uint256[4],int256),uint256) (runs: 256, μ: 467897, ~: 470574) -ERC4626VaultTest:test_previewWithdraw((address[4],uint256[4],uint256[4],int256),uint256) (runs: 256, μ: 470745, ~: 472090) -ERC4626VaultTest:test_redeem((address[4],uint256[4],uint256[4],int256),uint256,uint256) (runs: 256, μ: 472309, ~: 475022) -ERC4626VaultTest:test_totalAssets((address[4],uint256[4],uint256[4],int256)) (runs: 256, μ: 423927, ~: 428329) -ERC4626VaultTest:test_withdraw((address[4],uint256[4],uint256[4],int256),uint256,uint256) (runs: 256, μ: 473454, ~: 476196) +ERC4626VaultTest:test_RT_deposit_redeem((address[4],uint256[4],uint256[4],int256),uint256) (runs: 256, μ: 473608, ~: 475944) +ERC4626VaultTest:test_RT_deposit_withdraw((address[4],uint256[4],uint256[4],int256),uint256) (runs: 256, μ: 475389, ~: 477933) +ERC4626VaultTest:test_RT_mint_redeem((address[4],uint256[4],uint256[4],int256),uint256) (runs: 256, μ: 474274, ~: 476284) +ERC4626VaultTest:test_RT_mint_withdraw((address[4],uint256[4],uint256[4],int256),uint256) (runs: 256, μ: 476630, ~: 478151) +ERC4626VaultTest:test_RT_redeem_deposit((address[4],uint256[4],uint256[4],int256),uint256) (runs: 256, μ: 473670, ~: 476737) +ERC4626VaultTest:test_RT_redeem_mint((address[4],uint256[4],uint256[4],int256),uint256) (runs: 256, μ: 473963, ~: 476539) +ERC4626VaultTest:test_RT_withdraw_deposit((address[4],uint256[4],uint256[4],int256),uint256) (runs: 256, μ: 475427, ~: 478372) +ERC4626VaultTest:test_RT_withdraw_mint((address[4],uint256[4],uint256[4],int256),uint256) (runs: 256, μ: 476380, ~: 478560) +ERC4626VaultTest:test_asset((address[4],uint256[4],uint256[4],int256)) (runs: 256, μ: 422368, ~: 427654) +ERC4626VaultTest:test_convertToAssets((address[4],uint256[4],uint256[4],int256),uint256) (runs: 256, μ: 429078, ~: 432395) +ERC4626VaultTest:test_convertToShares((address[4],uint256[4],uint256[4],int256),uint256) (runs: 256, μ: 428584, ~: 432529) +ERC4626VaultTest:test_deposit((address[4],uint256[4],uint256[4],int256),uint256,uint256) (runs: 256, μ: 468672, ~: 472487) +ERC4626VaultTest:test_maxDeposit((address[4],uint256[4],uint256[4],int256)) (runs: 256, μ: 422381, ~: 427667) +ERC4626VaultTest:test_maxMint((address[4],uint256[4],uint256[4],int256)) (runs: 256, μ: 422406, ~: 427692) +ERC4626VaultTest:test_maxRedeem((address[4],uint256[4],uint256[4],int256)) (runs: 256, μ: 422521, ~: 427806) +ERC4626VaultTest:test_maxWithdraw((address[4],uint256[4],uint256[4],int256)) (runs: 256, μ: 424074, ~: 429427) +ERC4626VaultTest:test_mint((address[4],uint256[4],uint256[4],int256),uint256,uint256) (runs: 256, μ: 469793, ~: 472627) +ERC4626VaultTest:test_previewDeposit((address[4],uint256[4],uint256[4],int256),uint256) (runs: 256, μ: 465930, ~: 468818) +ERC4626VaultTest:test_previewMint((address[4],uint256[4],uint256[4],int256),uint256) (runs: 256, μ: 467656, ~: 469248) +ERC4626VaultTest:test_previewRedeem((address[4],uint256[4],uint256[4],int256),uint256) (runs: 256, μ: 468133, ~: 470638) +ERC4626VaultTest:test_previewWithdraw((address[4],uint256[4],uint256[4],int256),uint256) (runs: 256, μ: 470392, ~: 471970) +ERC4626VaultTest:test_redeem((address[4],uint256[4],uint256[4],int256),uint256,uint256) (runs: 256, μ: 472120, ~: 474925) +ERC4626VaultTest:test_totalAssets((address[4],uint256[4],uint256[4],int256)) (runs: 256, μ: 422971, ~: 428256) +ERC4626VaultTest:test_withdraw((address[4],uint256[4],uint256[4],int256),uint256,uint256) (runs: 256, μ: 473584, ~: 476530) ERC721Invariants:invariantOwner() (runs: 256, calls: 3840, reverts: 3469) ERC721Invariants:invariantTotalSupply() (runs: 256, calls: 3840, reverts: 3469) ERC721Test:testApproveClearingApprovalWithNoPriorApproval() (gas: 177294) @@ -469,31 +469,31 @@ ERC721Test:testBurnSuccessViaApproveAndSetApprovalForAll() (gas: 371421) ERC721Test:testCachedDomainSeparator() (gas: 7699) ERC721Test:testDomainSeparator() (gas: 11450) ERC721Test:testEIP712Domain() (gas: 13877) -ERC721Test:testFuzzApproveClearingApprovalWithNoPriorApproval(address,address) (runs: 256, μ: 196799, ~: 196799) +ERC721Test:testFuzzApproveClearingApprovalWithNoPriorApproval(address,address) (runs: 256, μ: 196789, ~: 196799) ERC721Test:testFuzzApproveClearingApprovalWithPriorApproval(address,address) (runs: 256, μ: 184072, ~: 184072) ERC721Test:testFuzzApproveFromNonOwner(address) (runs: 256, μ: 172895, ~: 172895) ERC721Test:testFuzzApproveFromOperatorAddress(address,address,address) (runs: 256, μ: 222958, ~: 222958) -ERC721Test:testFuzzApproveWithNoPriorApproval(address,address) (runs: 256, μ: 196754, ~: 196754) -ERC721Test:testFuzzApproveWithPriorApproval(address,address) (runs: 256, μ: 203884, ~: 203884) +ERC721Test:testFuzzApproveWithNoPriorApproval(address,address) (runs: 256, μ: 196744, ~: 196754) +ERC721Test:testFuzzApproveWithPriorApproval(address,address) (runs: 256, μ: 203874, ~: 203884) ERC721Test:testFuzzBurnSuccess(address) (runs: 256, μ: 305615, ~: 305600) -ERC721Test:testFuzzDomainSeparator(uint8) (runs: 256, μ: 11641, ~: 11663) +ERC721Test:testFuzzDomainSeparator(uint8) (runs: 256, μ: 11643, ~: 11663) ERC721Test:testFuzzEIP712Domain(bytes1,uint8,bytes32,uint256[]) (runs: 256, μ: 19581, ~: 19542) -ERC721Test:testFuzzGetApprovedApprovedTokenId(address,address) (runs: 256, μ: 194385, ~: 194385) +ERC721Test:testFuzzGetApprovedApprovedTokenId(address,address) (runs: 256, μ: 194375, ~: 194385) ERC721Test:testFuzzPermitInvalid(string,string,uint16) (runs: 256, μ: 201667, ~: 201665) ERC721Test:testFuzzPermitSuccess(string,string,uint16) (runs: 256, μ: 227137, ~: 227133) ERC721Test:testFuzzRenounceOwnershipNonOwner(address) (runs: 256, μ: 13938, ~: 13938) -ERC721Test:testFuzzRenounceOwnershipSuccess(address) (runs: 256, μ: 48380, ~: 48364) +ERC721Test:testFuzzRenounceOwnershipSuccess(address) (runs: 256, μ: 48379, ~: 48364) ERC721Test:testFuzzSafeMintNonMinter(address) (runs: 256, μ: 15894, ~: 15894) -ERC721Test:testFuzzSafeMintSuccess(address[]) (runs: 256, μ: 21015630, ~: 20441661) -ERC721Test:testFuzzSafeTransferFromWithData(address,address,address,bytes) (runs: 256, μ: 1428908, ~: 1425307) +ERC721Test:testFuzzSafeMintSuccess(address[]) (runs: 256, μ: 20855146, ~: 20004472) +ERC721Test:testFuzzSafeTransferFromWithData(address,address,address,bytes) (runs: 256, μ: 1429054, ~: 1429893) ERC721Test:testFuzzSetApprovalForAllSuccess(address,address) (runs: 256, μ: 188481, ~: 188481) ERC721Test:testFuzzSetMinterNonOwner(address,string) (runs: 256, μ: 15822, ~: 15749) ERC721Test:testFuzzSetMinterSuccess(string) (runs: 256, μ: 33217, ~: 33216) -ERC721Test:testFuzzTokenByIndex(address,string[]) (runs: 256, μ: 21390285, ~: 21253069) -ERC721Test:testFuzzTotalSupply(address,string[]) (runs: 256, μ: 21261017, ~: 21123504) -ERC721Test:testFuzzTransferFrom(address,address,address) (runs: 256, μ: 560100, ~: 560080) +ERC721Test:testFuzzTokenByIndex(address,string[]) (runs: 256, μ: 21399845, ~: 21253069) +ERC721Test:testFuzzTotalSupply(address,string[]) (runs: 256, μ: 21270507, ~: 21123504) +ERC721Test:testFuzzTransferFrom(address,address,address) (runs: 256, μ: 560097, ~: 560080) ERC721Test:testFuzzTransferOwnershipNonOwner(address,address) (runs: 256, μ: 14090, ~: 14090) -ERC721Test:testFuzzTransferOwnershipSuccess(address,address) (runs: 256, μ: 73920, ~: 73892) +ERC721Test:testFuzzTransferOwnershipSuccess(address,address) (runs: 256, μ: 73922, ~: 73892) ERC721Test:testGetApprovedApprovedTokenId() (gas: 194287) ERC721Test:testGetApprovedInvalidTokenId() (gas: 11097) ERC721Test:testGetApprovedNotApprovedTokenId() (gas: 170525) @@ -556,19 +556,19 @@ ERC721Test:testTransferOwnershipToZeroAddress() (gas: 15608) MathTest:testCbrtRoundDown() (gas: 41159) MathTest:testCbrtRoundUp() (gas: 41806) MathTest:testCeilDiv() (gas: 12481) -MathTest:testFuzzCbrt(uint256,bool) (runs: 256, μ: 19431, ~: 19249) +MathTest:testFuzzCbrt(uint256,bool) (runs: 256, μ: 19443, ~: 19288) MathTest:testFuzzCeilDiv(uint256,uint256) (runs: 256, μ: 8962, ~: 8979) MathTest:testFuzzInt256Average(int256,int256) (runs: 256, μ: 5683, ~: 5683) MathTest:testFuzzLog10(uint256,bool) (runs: 256, μ: 6971, ~: 6961) -MathTest:testFuzzLog2(uint256,bool) (runs: 256, μ: 6825, ~: 6803) -MathTest:testFuzzLog256(uint256,bool) (runs: 256, μ: 6853, ~: 6831) +MathTest:testFuzzLog2(uint256,bool) (runs: 256, μ: 6826, ~: 6803) +MathTest:testFuzzLog256(uint256,bool) (runs: 256, μ: 6849, ~: 6827) MathTest:testFuzzMulDiv(uint256,uint256,uint256) (runs: 256, μ: 13038, ~: 12781) -MathTest:testFuzzMulDivDomain(uint256,uint256,uint256) (runs: 256, μ: 10485, ~: 10570) +MathTest:testFuzzMulDivDomain(uint256,uint256,uint256) (runs: 256, μ: 10488, ~: 10570) MathTest:testFuzzSignum(int256) (runs: 256, μ: 5578, ~: 5570) MathTest:testFuzzUint256Average(uint256,uint256) (runs: 256, μ: 5763, ~: 5763) -MathTest:testFuzzWadCbrt(uint256) (runs: 256, μ: 18973, ~: 18650) -MathTest:testFuzzWadExp(int256) (runs: 256, μ: 14492, ~: 14597) -MathTest:testFuzzWadLn(int256) (runs: 256, μ: 15990, ~: 15780) +MathTest:testFuzzWadCbrt(uint256) (runs: 256, μ: 18982, ~: 18667) +MathTest:testFuzzWadExp(int256) (runs: 256, μ: 14484, ~: 14597) +MathTest:testFuzzWadLn(int256) (runs: 256, μ: 15982, ~: 15766) MathTest:testInt256Average() (gas: 12010) MathTest:testLog10RoundDown() (gas: 17685) MathTest:testLog10RoundUp() (gas: 18866) @@ -589,8 +589,8 @@ MathTest:testWadExp() (gas: 24809) MathTest:testWadExpOverflow() (gas: 11127) MathTest:testWadLn() (gas: 24196) MathTest:testWadLnNegativeValues() (gas: 11130) -MerkleProofVerificationTest:testFuzzMultiProofVerifySingleLeaf(bytes32[],uint256) (runs: 256, μ: 1649976154, ~: 1649972494) -MerkleProofVerificationTest:testFuzzVerify(bytes32[],uint256) (runs: 256, μ: 135974866, ~: 135971282) +MerkleProofVerificationTest:testFuzzMultiProofVerifySingleLeaf(bytes32[],uint256) (runs: 256, μ: 1649976152, ~: 1649972613) +MerkleProofVerificationTest:testFuzzVerify(bytes32[],uint256) (runs: 256, μ: 135974864, ~: 135971401) MerkleProofVerificationTest:testFuzzVerifyMultiProofMultipleLeaves(bool,bool,bool) (runs: 256, μ: 412472467, ~: 412472462) MerkleProofVerificationTest:testInvalidMerkleMultiProof() (gas: 412478415) MerkleProofVerificationTest:testInvalidMerkleProof() (gas: 33970390) @@ -615,7 +615,7 @@ Ownable2StepInvariants:invariantPendingOwner() (runs: 256, calls: 3840, reverts: Ownable2StepTest:testAcceptOwnershipNonPendingOwner() (gas: 46604) Ownable2StepTest:testAcceptOwnershipSuccess() (gas: 39404) Ownable2StepTest:testFuzzAcceptOwnershipNonPendingOwner(address) (runs: 256, μ: 45814, ~: 45814) -Ownable2StepTest:testFuzzAcceptOwnershipSuccess(address,address) (runs: 256, μ: 65112, ~: 65084) +Ownable2StepTest:testFuzzAcceptOwnershipSuccess(address,address) (runs: 256, μ: 65113, ~: 65084) Ownable2StepTest:testFuzzPendingOwnerResetAfterRenounceOwnership(address) (runs: 256, μ: 38751, ~: 38735) Ownable2StepTest:testFuzzRenounceOwnershipNonOwner(address) (runs: 256, μ: 13954, ~: 13954) Ownable2StepTest:testFuzzRenounceOwnershipSuccess(address) (runs: 256, μ: 43684, ~: 43668) @@ -657,4 +657,96 @@ SignatureCheckerTest:testFuzzEIP1271WithValidSignature(string) (runs: 256, μ: 3 SignatureCheckerTest:testFuzzEOAWithInvalidSignature(bytes,string) (runs: 256, μ: 16837, ~: 16836) SignatureCheckerTest:testFuzzEOAWithInvalidSigner(string,string) (runs: 256, μ: 21466, ~: 21527) SignatureCheckerTest:testFuzzEOAWithValidSignature(string,string) (runs: 256, μ: 20585, ~: 20646) -SignatureCheckerTest:testInitialSetup() (gas: 5415) \ No newline at end of file +SignatureCheckerTest:testInitialSetup() (gas: 5415) +TimelockControllerInvariants:invariantExecutedLessThanOrEqualToScheduled() (runs: 256, calls: 3840, reverts: 1528) +TimelockControllerInvariants:invariantExecutedProposalCancellation() (runs: 256, calls: 3840, reverts: 1531) +TimelockControllerInvariants:invariantExecutingCancelledProposal() (runs: 256, calls: 3840, reverts: 1528) +TimelockControllerInvariants:invariantExecutingNotReadyProposal() (runs: 256, calls: 3840, reverts: 1535) +TimelockControllerInvariants:invariantOnceProposalExecution() (runs: 256, calls: 3840, reverts: 1485) +TimelockControllerInvariants:invariantProposalsExecutedMatchCount() (runs: 256, calls: 3840, reverts: 1528) +TimelockControllerInvariants:invariantSumOfProposals() (runs: 256, calls: 3840, reverts: 1528) +TimelockControllerTest:testAdminCannotBatchExecute() (gas: 750573) +TimelockControllerTest:testAdminCannotBatchSchedule() (gas: 748359) +TimelockControllerTest:testAdminCannotCancel() (gas: 13280) +TimelockControllerTest:testAdminCannotExecute() (gas: 18397) +TimelockControllerTest:testAdminCannotSchedule() (gas: 16069) +TimelockControllerTest:testBatchCancelFinished() (gas: 4641370) +TimelockControllerTest:testBatchEqualAndGreaterMinimumDelay() (gas: 6144529) +TimelockControllerTest:testBatchHasBeenExecuted() (gas: 4638874) +TimelockControllerTest:testBatchHasNotBeenExecuted() (gas: 3078509) +TimelockControllerTest:testBatchInsufficientDelay() (gas: 1532717) +TimelockControllerTest:testBatchMinimumDelayUpdate() (gas: 3085797) +TimelockControllerTest:testBatchOperationAlreadyScheduled() (gas: 4593338) +TimelockControllerTest:testBatchOperationIsNotReady() (gas: 4598655) +TimelockControllerTest:testBatchPendingIfExecuted() (gas: 4637848) +TimelockControllerTest:testBatchPendingIfNotYetExecuted() (gas: 3078517) +TimelockControllerTest:testBatchPredecessorInvalid() (gas: 4601007) +TimelockControllerTest:testBatchPredecessorMultipleNotExecuted() (gas: 6141737) +TimelockControllerTest:testBatchPredecessorNotExecuted() (gas: 7662956) +TimelockControllerTest:testBatchPredecessorNotScheduled() (gas: 6116865) +TimelockControllerTest:testBatchReadyAfterTheExecutionTime() (gas: 3079082) +TimelockControllerTest:testBatchReadyBeforeTheExecutionTime() (gas: 3079099) +TimelockControllerTest:testBatchReadyOnTheExecutionTime() (gas: 3078985) +TimelockControllerTest:testBatchScheduleAndExecuteWithEmptySalt() (gas: 4642907) +TimelockControllerTest:testBatchScheduleAndExecuteWithNonEmptySalt() (gas: 4646352) +TimelockControllerTest:testBatchTargetRevert() (gas: 9186500) +TimelockControllerTest:testBatchTimestampHasBeenExecuted() (gas: 4637665) +TimelockControllerTest:testBatchTimestampHasNotBeenExecuted() (gas: 3078316) +TimelockControllerTest:testCanReceiveEther() (gas: 12124) +TimelockControllerTest:testCancellerCanCancelOperation() (gas: 3064799) +TimelockControllerTest:testCompleteOperationWithAssignExecutorRoleToZeroAddress() (gas: 124673) +TimelockControllerTest:testCompletePipelineOperationMinimumDelayUpdate() (gas: 73442) +TimelockControllerTest:testCompletePipelineOperationSetRoleAdmin() (gas: 100466) +TimelockControllerTest:testExecutorCanBatchExecute() (gas: 3049249) +TimelockControllerTest:testExecutorCanExecute() (gas: 29920) +TimelockControllerTest:testExecutorCannotBatchSchedule() (gas: 1485375) +TimelockControllerTest:testExecutorCannotCancel() (gas: 15281) +TimelockControllerTest:testExecutorCannotSchedule() (gas: 19073) +TimelockControllerTest:testFuzzBatchValue(uint256) (runs: 256, μ: 4652693, ~: 4652826) +TimelockControllerTest:testFuzzHashOperation(address,uint256,bytes,bytes32,bytes32) (runs: 256, μ: 8261, ~: 8159) +TimelockControllerTest:testFuzzHashOperationBatch(address[],uint256[],bytes[],bytes32,bytes32) (runs: 256, μ: 1881454, ~: 1889945) +TimelockControllerTest:testFuzzOperationValue(uint256) (runs: 256, μ: 113151, ~: 113263) +TimelockControllerTest:testHandleERC1155() (gas: 41505969) +TimelockControllerTest:testHandleERC721() (gas: 7056837) +TimelockControllerTest:testHashOperation() (gas: 10233) +TimelockControllerTest:testHashOperationBatch() (gas: 1523421) +TimelockControllerTest:testInitialSetup() (gas: 4245443) +TimelockControllerTest:testInvalidOperation() (gas: 7856) +TimelockControllerTest:testOperationAlreadyScheduled() (gas: 52465) +TimelockControllerTest:testOperationCancelFinished() (gas: 101892) +TimelockControllerTest:testOperationEqualAndGreaterMinimumDelay() (gas: 90244) +TimelockControllerTest:testOperationHasBeenExecuted() (gas: 99429) +TimelockControllerTest:testOperationHasNotBeenExecuted() (gas: 52365) +TimelockControllerTest:testOperationInsufficientDelay() (gas: 19490) +TimelockControllerTest:testOperationMinimumDelayUpdate() (gas: 61101) +TimelockControllerTest:testOperationOperationIsNotReady() (gas: 57820) +TimelockControllerTest:testOperationPendingIfExecuted() (gas: 98359) +TimelockControllerTest:testOperationPendingIfNotYetExecuted() (gas: 52429) +TimelockControllerTest:testOperationPredecessorInvalid() (gas: 62855) +TimelockControllerTest:testOperationPredecessorMultipleNotExecuted() (gas: 92559) +TimelockControllerTest:testOperationPredecessorNotExecuted() (gas: 99291) +TimelockControllerTest:testOperationPredecessorNotScheduled() (gas: 66755) +TimelockControllerTest:testOperationReadyAfterTheExecutionTime() (gas: 52984) +TimelockControllerTest:testOperationReadyBeforeTheExecutionTime() (gas: 52927) +TimelockControllerTest:testOperationReadyOnTheExecutionTime() (gas: 52821) +TimelockControllerTest:testOperationTargetRevert() (gas: 110365) +TimelockControllerTest:testOperationTimestampHasBeenExecuted() (gas: 98112) +TimelockControllerTest:testOperationTimestampHasNotBeenExecuted() (gas: 52195) +TimelockControllerTest:testProposerCanBatchSchedule() (gas: 3088645) +TimelockControllerTest:testProposerCanCancel() (gas: 20179) +TimelockControllerTest:testProposerCanSchedule() (gas: 75659) +TimelockControllerTest:testProposerCannotBatchExecute() (gas: 1489786) +TimelockControllerTest:testProposerCannotExecute() (gas: 23599) +TimelockControllerTest:testReturnsLaterMinimumDelayForCalls() (gas: 20365) +TimelockControllerTest:testRevertWhenNotTimelock() (gas: 9053) +TimelockControllerTest:testScheduleAndExecuteWithEmptySalt() (gas: 103471) +TimelockControllerTest:testScheduleAndExecuteWithNonEmptySalt() (gas: 106828) +TimelockControllerTest:testStrangerCannotBatchExecute() (gas: 748592) +TimelockControllerTest:testStrangerCannotBatchSchedule() (gas: 746399) +TimelockControllerTest:testStrangerCannotCancel() (gas: 11276) +TimelockControllerTest:testStrangerCannotExecute() (gas: 16414) +TimelockControllerTest:testStrangerCannotSchedule() (gas: 14198) +TimelockControllerTest:testSupportsInterfaceInvalidInterfaceId() (gas: 5628) +TimelockControllerTest:testSupportsInterfaceInvalidInterfaceIdGasCost() (gas: 6418) +TimelockControllerTest:testSupportsInterfaceSuccess() (gas: 7234) +TimelockControllerTest:testSupportsInterfaceSuccessGasCost() (gas: 6525) \ No newline at end of file diff --git a/.gitignore b/.gitignore index 67e04804..7a633eeb 100644 --- a/.gitignore +++ b/.gitignore @@ -57,3 +57,6 @@ venv # Python build files dist *.egg-info + +# Ape build files +.build diff --git a/.solhint.json b/.solhint.json index 127600a2..aeadcf84 100644 --- a/.solhint.json +++ b/.solhint.json @@ -4,6 +4,7 @@ "func-visibility": ["warn", { "ignoreConstructors": true }], "func-name-mixedcase": "off", "private-vars-leading-underscore": "off", - "one-contract-per-file": "off" + "one-contract-per-file": "off", + "max-states-count": "off" } } diff --git a/CHANGELOG.md b/CHANGELOG.md index aac8a0d0..be8ef13f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ## [`0.0.5`](https://github.com/pcaversaccio/snekmate/releases/tag/v0.0.5) (Unreleased) +### 💥 New Features + +- **Governance** + - [`TimelockController`](https://github.com/pcaversaccio/snekmate/blob/v0.0.5/src/governance/TimelockController.vy): A multi-role-based timelock controller reference implementation. ([#195](https://github.com/pcaversaccio/snekmate/pull/195)) + ### ♻️ Refactoring - **Utility Functions** diff --git a/README.md b/README.md index e0c77f0b..72d02b0b 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,8 @@ src │ ├── 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" ├── tokens │ ├── ERC20 — "Modern and Gas-Efficient ERC-20 + EIP-2612 Implementation" │ ├── ERC721 — "Modern and Gas-Efficient ERC-721 + EIP-4494 Implementation" @@ -113,6 +115,7 @@ This repository contains [Foundry](https://github.com/foundry-rs/foundry)-based | `AccessControl` | ✅ | ✅ | ✅ | | `ERC2981` | ✅ | ✅ | ✅ | | `ERC4626` | ✅ | ✅ | ✅ | +| `TimelockController` | ✅ | ✅ | ✅ | | `ERC20` | ✅ | ✅ | ✅ | | `ERC721` | ✅ | ✅ | ✅ | | `ERC1155` | ✅ | ✅ | ✅ | diff --git a/package.json b/package.json index 51f36c25..b6e8fe9a 100644 --- a/package.json +++ b/package.json @@ -22,10 +22,10 @@ "url": "https://github.com/pcaversaccio/snekmate/issues" }, "scripts": { - "prettier:check": "npx prettier -c **/*.{md,sol,js,json,yml,yaml}", - "prettier:fix": "npx prettier -w **/*.{md,sol,js,json,yml,yaml}", - "solhint:check": "npx solhint '**/*.sol'", - "solhint:fix": "npx solhint '**/*.sol' --fix", + "prettier:check": "npx prettier -c \"**/*.{md,sol,js,json,yml,yaml}\"", + "prettier:fix": "npx prettier -w \"**/*.{md,sol,js,json,yml,yaml}\"", + "solhint:check": "npx solhint \"**/*.sol\"", + "solhint:fix": "npx solhint \"**/*.sol\" --fix", "lint:check": "pnpm prettier:check && pnpm solhint:check && npx eslint --ext .js .", "lint:fix": "pnpm prettier:fix && pnpm solhint:fix && npx eslint --ext .js . --fix" }, diff --git a/src/auth/AccessControl.vy b/src/auth/AccessControl.vy index 0f6eec57..e4a5b14f 100644 --- a/src/auth/AccessControl.vy +++ b/src/auth/AccessControl.vy @@ -63,6 +63,9 @@ implements: IAccessControl # @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) @@ -93,9 +96,6 @@ _SUPPORTED_INTERFACES: constant(bytes4[2]) = [ # @dev Returns `True` if `account` has been granted `role`. -# @notice If you declare a variable as `public`, -# Vyper automatically generates an `external` -# getter function for the variable. hasRole: public(HashMap[bytes32, HashMap[address, bool]]) @@ -226,6 +226,7 @@ def set_role_admin(role: bytes32, admin_role: bytes32): @internal +@view def _check_role(role: bytes32, account: address): """ @dev Reverts with a standard message if `account` diff --git a/src/auth/Ownable.vy b/src/auth/Ownable.vy index b5f07671..256b2cb3 100644 --- a/src/auth/Ownable.vy +++ b/src/auth/Ownable.vy @@ -17,6 +17,9 @@ # @dev Returns the address of the current owner. +# @notice If you declare a variable as `public`, +# Vyper automatically generates an `external` +# getter function for the variable. owner: public(address) diff --git a/src/auth/Ownable2Step.vy b/src/auth/Ownable2Step.vy index 9a09bbb5..d4aa02c5 100644 --- a/src/auth/Ownable2Step.vy +++ b/src/auth/Ownable2Step.vy @@ -16,6 +16,9 @@ # @dev Returns the address of the current owner. +# @notice If you declare a variable as `public`, +# Vyper automatically generates an `external` +# getter function for the variable. owner: public(address) diff --git a/src/extensions/ERC2981.vy b/src/extensions/ERC2981.vy index 390c510d..32e6cf65 100644 --- a/src/extensions/ERC2981.vy +++ b/src/extensions/ERC2981.vy @@ -67,6 +67,9 @@ _SUPPORTED_INTERFACES: constant(bytes4[2]) = [ # @dev Returns the address of the current owner. +# @notice If you declare a variable as `public`, +# Vyper automatically generates an `external` +# getter function for the variable. owner: public(address) diff --git a/src/governance/TimelockController.vy b/src/governance/TimelockController.vy new file mode 100644 index 00000000..0e904e45 --- /dev/null +++ b/src/governance/TimelockController.vy @@ -0,0 +1,932 @@ +# pragma version ^0.3.10 +""" +@title Multi-Role-Based Timelock Controller Reference Implementation +@custom:contract-name TimelockController +@license GNU Affero General Public License v3.0 only +@author pcaversaccio +@custom:coauthor cairoeth +@notice This module enables the timelocking of operations by scheduling + and executing transactions. By leveraging `AccessControl`, the + `TimelockController` contract introduces three roles: + 1. proposer (`PROPOSER_ROLE`), + 2. executor (`EXECUTOR_ROLE`), and + 3. canceller (`CANCELLER_ROLE`). + The proposer role is responsible for proposing operations, the + executor role is responsible for executing scheduled proposal(s), + and the canceller is responsible for cancelling proposal(s). This + contract is self-administered by default (unless an optional admin + account is granted at construction), meaning administration tasks + (e.g. grant or revoke roles) have to go through the timelock process. + At contract creation time, proposers are granted the proposer and + canceller roles. + + The proposal(s) must be scheduled with a delay that is greater + than or equal to the minimum delay `get_minimum_delay`, which can + be updated via a proposal to itself and is measured in seconds. + Additionally, proposal(s) can be linked to preceding proposal(s) + that must be executed before the proposal can be executed. + + Ready proposal(s) can be executed by the executor, who is solely + responsible for calling the `execute` or `execute_batch` functions. + Eventually, the proposal(s) can be batched individually or in batches. + The latter is useful for processes that have to be executed in the + same block. + + Please note that the `TimelockController` contract is able to receive + and transfer ERC-721 and ERC-1155 tokens. + + The implementation is inspired by OpenZeppelin's implementation here: + https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/governance/TimelockController.sol. +""" + + +# @dev We import and implement the `ERC165` interface, +# which is a built-in interface of the Vyper compiler. +from vyper.interfaces import ERC165 +implements: ERC165 + + +# @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 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 The 32-byte proposer role. +# @notice Responsible for proposing operations. +PROPOSER_ROLE: public(constant(bytes32)) = keccak256("PROPOSER_ROLE") + + +# @dev The 32-byte executor role. +# @notice Responsible for executing scheduled proposals. +EXECUTOR_ROLE: public(constant(bytes32)) = keccak256("EXECUTOR_ROLE") + + +# @dev The 32-byte canceller role. +# @notice Responsible for cancelling proposals. +CANCELLER_ROLE: public(constant(bytes32)) = keccak256("CANCELLER_ROLE") + + +# @dev The 4-byte function selector of `onERC721Received(address,address,uint256,bytes)`. +IERC721_TOKENRECEIVER_SELECTOR: public(constant(bytes4)) = 0x150B7A02 + + +# @dev The 4-byte function selector of `onERC1155Received(address,address,uint256,uint256,bytes)`. +IERC1155_TOKENRECEIVER_SINGLE_SELECTOR: public(constant(bytes4)) = 0xF23A6E61 + + +# @dev The 4-byte function selector of `onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)`. +IERC1155_TOKENRECEIVER_BATCH_SELECTOR: public(constant(bytes4)) = 0xBC197C81 + + +# @dev Stores the ERC-165 interface identifier for each +# imported interface. The ERC-165 interface identifier +# is defined as the XOR of all function selectors in the +# interface. +# @notice Note that the ERC-165 interface identifier for +# the `ERC721TokenReceiver` interface is not included as +# it is not required by the EIP: +# https://eips.ethereum.org/EIPS/eip-721#specification. +_SUPPORTED_INTERFACES: constant(bytes4[3]) = [ + 0x01FFC9A7, # The ERC-165 identifier for ERC-165. + 0x7965DB0B, # The ERC-165 identifier for `IAccessControl`. + 0x4E2312E0, # The ERC-165 identifier for `IERC1155Receiver`. +] + + +# @dev The 32-byte timestamp that specifies whether a +# proposal has been executed. +_DONE_TIMESTAMP: constant(uint256) = 1 + + +# @dev Stores the 1-byte upper bound for the dynamic arrays. +_DYNARRAY_BOUND: constant(uint8) = max_value(uint8) + + +# @dev The possible states of a proposal. +# @notice Enums are treated differently in Vyper and +# Solidity. The members are represented by `uint256` +# values (in Solidity the values are of type `uint8`) +# in the form `2**n`, where `n` is the index of the +# member in the range `0 <= n <= 255` (i.e. the first +# index value is `1`). For further insights also, see +# the following Twitter thread: +# https://twitter.com/pcaversaccio/status/1626514029094047747. +enum OperationState: + UNSET + WAITING + READY + DONE + + +# @dev Returns the timestamp at which an operation +# becomes ready (`0` for `UNSET` operations, `1` for +# `DONE` operations). +get_timestamp: public(HashMap[bytes32, uint256]) + + +# @dev Returns the minimum delay in seconds for an +# operation to become valid. This value can be changed +# by executing an operation that invokes `update_delay`. +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 +# individual, the `index` is `0`. +event CallScheduled: + id: indexed(bytes32) + index: indexed(uint256) + target: address + amount: uint256 + payload: Bytes[1_024] + predecessor: bytes32 + delay: uint256 + + +# @dev Emitted when a call is performed as part of +# operation `id`. Note that `index` is the index +# position of the proposal. If the proposal is +# individual, the `index` is `0`. +event CallExecuted: + id: indexed(bytes32) + index: indexed(uint256) + target: address + amount: uint256 + payload: Bytes[1_024] + + +# @dev Emitted when new proposal is scheduled with +# non-zero salt. +event CallSalt: + id: indexed(bytes32) + salt: bytes32 + + +# @dev Emitted when operation `id` is cancelled. +event Cancelled: + id: indexed(bytes32) + + +# @dev Emitted when the minimum delay for future +# operations is modified. +event MinimumDelayChange: + old_duration: uint256 + 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) + + +@external +@payable +def __init__(minimum_delay_: uint256, proposers_: DynArray[address, _DYNARRAY_BOUND], executors_: DynArray[address, _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. + """ + # Configure the contract to be self-administered. + self._grant_role(DEFAULT_ADMIN_ROLE, self) + + # Set the optional admin. + if (admin_ != empty(address)): + self._grant_role(DEFAULT_ADMIN_ROLE, admin_) + + # Register the proposers and cancellers. + for proposer in proposers_: + self._grant_role(PROPOSER_ROLE, proposer) + self._grant_role(CANCELLER_ROLE, proposer) + + # Register the executors. + for executor in executors_: + self._grant_role(EXECUTOR_ROLE, executor) + + # Set the minimum delay. + self.get_minimum_delay = minimum_delay_ + log MinimumDelayChange(empty(uint256), minimum_delay_) + + +@external +@payable +def __default__(): + """ + @dev This contract might receive/hold ETH as part + of the maintenance process. + """ + pass + + +@external +@view +def supportsInterface(interface_id: bytes4) -> bool: + """ + @dev Returns `True` if this contract implements the + interface defined by `interface_id`. + @param interface_id The 4-byte interface identifier. + @return bool The verification whether the contract + implements the interface or not. + """ + return interface_id in _SUPPORTED_INTERFACES + + +@external +@view +def is_operation(id: bytes32) -> bool: + """ + @dev Returns whether an `id` corresponds to a registered + operation. This includes both `WAITING`, `READY`, and + `DONE` operations. + @param id The 32-byte operation identifier. + @return bool The verification whether the `id` + corresponds to a registered operation or not. + """ + return self._is_operation(id) + + +@external +@view +def is_operation_pending(id: bytes32) -> bool: + """ + @dev Returns whether an operation is pending or not. + Note that a "pending" operation may also be + "ready". + @param id The 32-byte operation identifier. + @return bool The verification whether the operation + is pending or not. + """ + return self._is_operation_pending(id) + + +@external +@view +def is_operation_ready(id: bytes32) -> bool: + """ + @dev Returns whether an operation is ready for + execution. Note that a "ready" operation is + also "pending". + @param id The 32-byte operation identifier. + @return bool The verification whether the operation + is ready or not. + """ + return self._is_operation_ready(id) + + +@external +@view +def is_operation_done(id: bytes32) -> bool: + """ + @dev Returns whether an operation is done or not. + @param id The 32-byte operation identifier. + @return bool The verification whether the operation + is done or not. + """ + return self._is_operation_done(id) + + +@external +@view +def get_operation_state(id: bytes32) -> OperationState: + """ + @dev Returns the state of an operation. + @param id The 32-byte operation identifier. + @return OperationState The 32-byte state of the + operation. + """ + return self._get_operation_state(id) + + +@external +@pure +def hash_operation(target: address, amount: uint256, payload: Bytes[1_024], predecessor: bytes32, salt: bytes32) -> bytes32: + """ + @dev Returns the identifier of an operation containing + a single transaction. + @param target The 20-bytes address of the target contract. + @param amount The 32-byte amount of native tokens to transfer + with the call. + @param payload The maximum 1,024-byte ABI-encoded calldata. + @param predecessor The 32-byte hash of the preceding operation + (optional with empty bytes). + @param salt The 32-byte salt of the operation. + @return bytes32 The 32-byte hash of the operation. + """ + return self._hash_operation(target, amount, payload, predecessor, salt) + + +@external +@pure +def hash_operation_batch(targets: DynArray[address, _DYNARRAY_BOUND], amounts: DynArray[uint256, _DYNARRAY_BOUND], payloads: DynArray[Bytes[1_024], _DYNARRAY_BOUND], + predecessor: bytes32, salt: bytes32) -> bytes32: + """ + @dev Returns the identifier of an operation containing + a batch of transactions. + @param targets The 20-byte array of the target contracts. + @param amounts The 32-byte array of native tokens amounts to + transfer with each call. + @param payloads The maximum 1,024-byte byte array of ABI-encoded + calldata. + @param predecessor The 32-byte hash of the preceding operation + (optional with empty bytes). + @param salt The 32-byte salt of the operation. + @return bytes32 The 32-byte hash of the operation. + """ + return self._hash_operation_batch(targets, amounts, payloads, predecessor, salt) + + +@external +def schedule(target: address, amount: uint256, payload: Bytes[1_024], predecessor: bytes32, salt: bytes32, delay: uint256): + """ + @dev Schedules an operation containing a single transaction. + Emits `CallScheduled` and `CallSalt` if the salt is non-zero. + @notice Note that the caller must have the `PROPOSER_ROLE` role. + @param target The 20-byte address of the target contract. + @param amount The 32-byte amount of native tokens to transfer + with the call. + @param payload The maximum 1,024-byte ABI-encoded calldata. + @param predecessor The 32-byte hash of the preceding operation + (optional with empty bytes). + @param salt The 32-byte salt of the operation. + @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) + id: bytes32 = self._hash_operation(target, amount, payload, predecessor, salt) + + self._schedule(id, delay) + log CallScheduled(id, empty(uint256), target, amount, payload, predecessor, delay) + if (salt != empty(bytes32)): + log CallSalt(id, salt) + + +@external +def schedule_batch(targets: DynArray[address, _DYNARRAY_BOUND], amounts: DynArray[uint256, _DYNARRAY_BOUND], payloads: DynArray[Bytes[1_024], _DYNARRAY_BOUND], + predecessor: bytes32, salt: bytes32, delay: uint256): + """ + @dev Schedules an operation containing a batch of transactions. + Emits one `CallScheduled` event per transaction in the + batch and `CallSalt` if the salt is non-zero. + @notice Note that the caller must have the `PROPOSER_ROLE` role. + @param targets The 20-byte array of the target contracts. + @param amounts The 32-byte array of native tokens amounts to + transfer with each call. + @param payloads The maximum 1,024-byte byte array of ABI-encoded + calldata. + @param predecessor The 32-byte hash of the preceding operation + (optional with empty bytes). + @param salt The 32-byte salt of the operation. + @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) + assert len(targets) == len(amounts) and len(targets) == len(payloads), "TimelockController: length mismatch" + id: bytes32 = self._hash_operation_batch(targets, amounts, payloads, predecessor, salt) + + self._schedule(id, delay) + idx: uint256 = empty(uint256) + for target in targets: + log CallScheduled(id, idx, target, amounts[idx], payloads[idx], predecessor, delay) + # The following line cannot overflow because we have + # limited the dynamic array `targets` by the `constant` + # parameter `_DYNARRAY_BOUND`, which is bounded by the + # maximum value of `uint8`. + idx = unsafe_add(idx, 1) + if (salt != empty(bytes32)): + log CallSalt(id, salt) + + +@external +def cancel(id: bytes32): + """ + @dev Cancels an operation. + @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) + assert self._is_operation_pending(id), "TimelockController: operation cannot be cancelled" + self.get_timestamp[id] = empty(uint256) + log Cancelled(id) + + +@external +@payable +def execute(target: address, amount: uint256, payload: Bytes[1_024], predecessor: bytes32, salt: bytes32): + """ + @dev Executes a ready operation containing a single transaction. + Emits a `CallExecuted` event. + @notice Note that the caller must have the `EXECUTOR_ROLE` role. + @param target The 20-byte address of the target contract. + @param amount The 32-byte amount of native tokens to transfer + with the call. + @param payload The maximum 1,024-byte ABI-encoded calldata. + @param predecessor The 32-byte hash of the preceding operation + (optional with empty bytes). + @param salt The 32-byte salt of the operation. + @custom:security This function can reenter, but it doesn't pose + a risk because `_after_call` checks that the + proposal is pending, thus any modifications to + the operation during reentrancy are caught. + """ + self._only_role_or_open_role(EXECUTOR_ROLE) + id: bytes32 = self._hash_operation(target, amount, payload, predecessor, salt) + + self._before_call(id, predecessor) + self._execute(target, amount, payload) + log CallExecuted(id, empty(uint256), target, amount, payload) + self._after_call(id) + + +@external +@payable +def execute_batch(targets: DynArray[address, _DYNARRAY_BOUND], amounts: DynArray[uint256, _DYNARRAY_BOUND], payloads: DynArray[Bytes[1_024], _DYNARRAY_BOUND], + predecessor: bytes32, salt: bytes32): + """ + @dev Executes a ready operation containing a batch of transactions. + Emits one `CallExecuted` event per transaction in the batch. + @notice Note that the caller must have the `EXECUTOR_ROLE` role. + @param targets The 20-byte array of the target contracts. + @param amounts The 32-byte array of native tokens amounts to + transfer with each call. + @param payloads The maximum 1,024-byte byte array of ABI-encoded + calldata. + @param predecessor The 32-byte hash of the preceding operation + (optional with empty bytes). + @param salt The 32-byte salt of the operation. + @custom:security This function can reenter, but it doesn't pose + a risk because `_after_call` checks that the + proposal is pending, thus any modifications to + the operation during reentrancy are caught. + """ + self._only_role_or_open_role(EXECUTOR_ROLE) + assert len(targets) == len(amounts) and len(targets) == len(payloads), "TimelockController: length mismatch" + id: bytes32 = self._hash_operation_batch(targets, amounts, payloads, predecessor, salt) + + self._before_call(id, predecessor) + idx: uint256 = empty(uint256) + for target in targets: + self._execute(target, amounts[idx], payloads[idx]) + log CallExecuted(id, idx, target, amounts[idx], payloads[idx]) + # The following line cannot overflow because we have + # limited the dynamic array `targets` by the `constant` + # parameter `_DYNARRAY_BOUND`, which is bounded by the + # maximum value of `uint8`. + idx = unsafe_add(idx, 1) + self._after_call(id) + + +@external +def update_delay(new_delay: uint256): + """ + @dev Changes the minimum timelock duration for future + operations. Emits a `MinimumDelayChange` event. + @notice Note that the caller must be the `TimelockController` + contract itself. This can only be achieved by scheduling + and later executing an operation where the `TimelockController` + contract is the target and the payload is the ABI-encoded + call to this function. + @param new_delay The new 32-byte minimum delay in seconds. + """ + assert msg.sender == self, "TimelockController: caller must be timelock" + log MinimumDelayChange(self.get_minimum_delay, new_delay) + 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: + """ + @dev Whenever a `token_id` token is transferred to + this contract via ERC-721 `safeTransferFrom` by + `operator` from `owner`, this function is called. + @notice It must return its function selector to + confirm the token transfer. If any other value + is returned or the interface is not implemented + by the recipient, the transfer will be reverted. + @param operator The 20-byte address which called + the `safeTransferFrom` function. + @param owner The 20-byte address which previously + owned the token. + @param token_id The 32-byte identifier of the token. + @param data The maximum 1,024-byte additional data + with no specified format. + @return bytes4 The 4-byte function selector of `onERC721Received`. + """ + return IERC721_TOKENRECEIVER_SELECTOR + + +@external +def onERC1155Received(operator: address, owner: address, id: uint256, amount: uint256, data: Bytes[1_024]) -> bytes4: + """ + @dev Handles the receipt of a single ERC-1155 token type. + This function is called at the end of a `safeTransferFrom` + after the balance has been updated. + @notice It must return its function selector to + confirm the token transfer. If any other value + is returned or the interface is not implemented + by the recipient, the transfer will be reverted. + @param operator The 20-byte address which called + the `safeTransferFrom` function. + @param owner The 20-byte address which previously + owned the token. + @param id The 32-byte identifier of the token. + @param amount The 32-byte token amount that is + being transferred. + @param data The maximum 1,024-byte additional data + with no specified format. + @return bytes4 The 4-byte function selector of `onERC1155Received`. + """ + return IERC1155_TOKENRECEIVER_SINGLE_SELECTOR + + +@external +def onERC1155BatchReceived(operator: address, owner: address, ids: DynArray[uint256, 65_535], amounts: DynArray[uint256, 65_535], + data: Bytes[1_024]) -> bytes4: + """ + @dev Handles the receipt of multiple ERC-1155 token types. + This function is called at the end of a `safeBatchTransferFrom` + after the balances have been updated. + @notice It must return its function selector to + confirm the token transfers. If any other value + is returned or the interface is not implemented + by the recipient, the transfers will be reverted. + @param operator The 20-byte address which called + the `safeBatchTransferFrom` function. + @param owner The 20-byte address which previously + owned the tokens. + @param ids The 32-byte array of token identifiers. Note + that the order and length must match the 32-byte + `amounts` array. + @param amounts The 32-byte array of token amounts that are + being transferred. Note that the order and length must + match the 32-byte `ids` array. + @param data The maximum 1,024-byte additional data + with no specified format. + @return bytes4 The 4-byte function selector of `onERC1155BatchReceived`. + """ + return IERC1155_TOKENRECEIVER_BATCH_SELECTOR + + +@internal +@view +def _is_operation(id: bytes32) -> bool: + """ + @dev Returns whether an `id` corresponds to a registered + operation. This includes both `WAITING`, `READY`, and + `DONE` operations. + @param id The 32-byte operation identifier. + @return bool The verification whether the `id` + corresponds to a registered operation or not. + """ + return self._get_operation_state(id) != OperationState.UNSET + + +@internal +@view +def _is_operation_pending(id: bytes32) -> bool: + """ + @dev Returns whether an operation is pending or not. + Note that a "pending" operation may also be + "ready". + @param id The 32-byte operation identifier. + @return bool The verification whether the operation + is pending or not. + """ + state: OperationState = self._get_operation_state(id) + return state == OperationState.WAITING or state == OperationState.READY + + +@internal +@view +def _is_operation_ready(id: bytes32) -> bool: + """ + @dev Returns whether an operation is ready for + execution. Note that a "ready" operation is + also "pending". + @param id The 32-byte operation identifier. + @return bool The verification whether the operation + is ready or not. + """ + return self._get_operation_state(id) == OperationState.READY + + +@internal +@view +def _is_operation_done(id: bytes32) -> bool: + """ + @dev Returns whether an operation is done or not. + @param id The 32-byte operation identifier. + @return bool The verification whether the operation + is done or not. + """ + return self._get_operation_state(id) == OperationState.DONE + + +@internal +@view +def _get_operation_state(id: bytes32) -> OperationState: + """ + @dev Returns the state of an operation. + @param id The 32-byte operation identifier. + @return OperationState The 32-byte state of the + operation. + """ + timestamp: uint256 = self.get_timestamp[id] + if (timestamp == empty(uint256)): + return OperationState.UNSET + elif (timestamp == _DONE_TIMESTAMP): + return OperationState.DONE + elif (timestamp > block.timestamp): + return OperationState.WAITING + else: + return OperationState.READY + + +@internal +@pure +def _hash_operation(target: address, amount: uint256, payload: Bytes[1_024], predecessor: bytes32, salt: bytes32) -> bytes32: + """ + @dev Returns the identifier of an operation containing + a single transaction. + @param target The 20-bytes address of the target contract. + @param amount The 32-byte amount of native tokens to transfer + with the call. + @param payload The maximum 1,024-byte ABI-encoded calldata. + @param predecessor The 32-byte hash of the preceding operation + (optional with empty bytes). + @param salt The 32-byte salt of the operation. + @return bytes32 The 32-byte hash of the operation. + """ + return keccak256(_abi_encode(target, amount, payload, predecessor, salt)) + + +@internal +@pure +def _hash_operation_batch(targets: DynArray[address, _DYNARRAY_BOUND], amounts: DynArray[uint256, _DYNARRAY_BOUND], payloads: DynArray[Bytes[1_024], _DYNARRAY_BOUND], + predecessor: bytes32, salt: bytes32) -> bytes32: + """ + @dev Returns the identifier of an operation containing + a batch of transactions. + @param targets The 20-byte array of the target contracts. + @param amounts The 32-byte array of native tokens amounts to + transfer with each call. + @param payloads The maximum 1,024-byte byte array of ABI-encoded + calldata. + @param predecessor The 32-byte hash of the preceding operation + (optional with empty bytes). + @param salt The 32-byte salt of the operation. + @return bytes32 The 32-byte hash of the operation. + """ + return keccak256(_abi_encode(targets, amounts, payloads, predecessor, salt)) + + +@internal +def _schedule(id: bytes32, delay: uint256): + """ + @dev Schedules an operation that is to become valid + after a given delay. + @notice This is an `internal` function without access + restriction. + @param id The 32-byte operation identifier. + @param delay The 32-byte delay before the operation + becomes valid. Must be greater than or equal + to the minimum delay. + """ + assert not(self._is_operation(id)), "TimelockController: operation already scheduled" + assert delay >= self.get_minimum_delay, "TimelockController: insufficient delay" + self.get_timestamp[id] = block.timestamp + delay + + +@internal +def _execute(target: address, amount: uint256, payload: Bytes[1_024]): + """ + @dev Executes an operation call. + @notice This is an `internal` function without access + restriction. + @param target The 20-byte address of the target contract. + @param amount The 32-byte amount of native tokens to transfer + with the call. + @param payload The maximum 1,024-byte ABI-encoded calldata. + """ + return_data: Bytes[max_value(uint8)] = b"" + success: bool = empty(bool) + success, return_data = raw_call(target, payload, max_outsize=255, value=amount, revert_on_failure=False) + if (not(success)): + if len(return_data) != empty(uint256): + # Bubble up the revert reason. + raw_revert(return_data) + else: + raise "TimelockController: underlying transaction reverted" + + +@internal +@view +def _before_call(id: bytes32, predecessor: bytes32): + """ + @dev Implements safety checks that must succeed before + executing (an) operation call(s). + @param id The 32-byte operation identifier. + @param predecessor The 32-byte hash of the preceding + operation. + """ + assert self._is_operation_ready(id), "TimelockController: operation is not ready" + assert predecessor == empty(bytes32) or self._is_operation_done(predecessor), "TimelockController: missing dependency" + + +@internal +def _after_call(id: bytes32): + """ + @dev Implements safety checks that must succeed after + executing (an) operation call(s). + @param id The 32-byte operation identifier. + """ + assert self._is_operation_ready(id), "TimelockController: operation is not ready" + self.get_timestamp[id] = _DONE_TIMESTAMP + + +@internal +@view +def _only_role_or_open_role(role: bytes32): + """ + @dev Limits a function to be callable only by a certain + role. In addition to checking the sender's role, + the zero address `empty(address)` is also considered. + Granting a role to `empty(address)` is equivalent to + 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) diff --git a/src/tokens/ERC1155.vy b/src/tokens/ERC1155.vy index 4b96fbfc..1741e467 100644 --- a/src/tokens/ERC1155.vy +++ b/src/tokens/ERC1155.vy @@ -76,6 +76,9 @@ _BASE_URI: immutable(String[80]) # @dev Mapping from owner to operator approvals. +# @notice If you declare a variable as `public`, +# Vyper automatically generates an `external` +# getter function for the variable. isApprovedForAll: public(HashMap[address, HashMap[address, bool]]) diff --git a/src/utils/Create2Address.vy b/src/utils/Create2Address.vy index 70b11833..4effe643 100644 --- a/src/utils/Create2Address.vy +++ b/src/utils/Create2Address.vy @@ -12,6 +12,9 @@ """ +# @dev The 1-byte `CREATE2` offset constant used to prevent +# collisions with addresses created using the traditional +# `keccak256(rlp([sender, nonce]))` formula. _COLLISION_OFFSET: constant(bytes1) = 0xFF diff --git a/src/utils/ECDSA.vy b/src/utils/ECDSA.vy index e5d85aae..78f1f9d1 100644 --- a/src/utils/ECDSA.vy +++ b/src/utils/ECDSA.vy @@ -18,6 +18,7 @@ """ +# @dev Constants used as part of the ECDSA recovery function. _MALLEABILITY_THRESHOLD: constant(bytes32) = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0 _SIGNATURE_INCREMENT: constant(bytes32) = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF diff --git a/src/utils/SignatureChecker.vy b/src/utils/SignatureChecker.vy index 71e6c859..68371c15 100644 --- a/src/utils/SignatureChecker.vy +++ b/src/utils/SignatureChecker.vy @@ -22,7 +22,14 @@ """ +# @dev The 4-byte function selector of `isValidSignature(bytes32,bytes)`. +# @notice If you declare a variable as `public`, +# Vyper automatically generates an `external` +# getter function for the variable. IERC1271_ISVALIDSIGNATURE_SELECTOR: public(constant(bytes4)) = 0x1626BA7E + + +# @dev Constants used as part of the ECDSA recovery function. _MALLEABILITY_THRESHOLD: constant(bytes32) = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0 _SIGNATURE_INCREMENT: constant(bytes32) = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF diff --git a/test/extensions/ERC4626.t.sol b/test/extensions/ERC4626.t.sol index 7c526658..e1e8e3d1 100644 --- a/test/extensions/ERC4626.t.sol +++ b/test/extensions/ERC4626.t.sol @@ -1596,7 +1596,6 @@ contract ERC4626VaultTest is ERC4626Test { address spender = makeAddr("spender"); uint256 amount = 100; uint256 nonce = ERC4626ExtendedDecimalsOffset0.nonces(owner); - // solhint-disable-next-line not-rely-on-time uint256 deadline = block.timestamp + 100_000; bytes32 domainSeparator = ERC4626ExtendedDecimalsOffset0 .DOMAIN_SEPARATOR(); @@ -1642,7 +1641,6 @@ contract ERC4626VaultTest is ERC4626Test { address spender = makeAddr("spender"); uint256 amount = 100; uint256 nonce = ERC4626ExtendedDecimalsOffset0.nonces(owner); - // solhint-disable-next-line not-rely-on-time uint256 deadline = block.timestamp + 100_000; bytes32 domainSeparator = ERC4626ExtendedDecimalsOffset0 .DOMAIN_SEPARATOR(); @@ -1693,7 +1691,6 @@ contract ERC4626VaultTest is ERC4626Test { address spender = makeAddr("spender"); uint256 amount = 100; uint256 nonce = ERC4626ExtendedDecimalsOffset0.nonces(owner); - // solhint-disable-next-line not-rely-on-time uint256 deadline = block.timestamp + 100_000; bytes32 domainSeparator = ERC4626ExtendedDecimalsOffset0 .DOMAIN_SEPARATOR(); @@ -1733,7 +1730,6 @@ contract ERC4626VaultTest is ERC4626Test { address spender = makeAddr("spender"); uint256 amount = 100; uint256 nonce = ERC4626ExtendedDecimalsOffset0.nonces(owner); - // solhint-disable-next-line not-rely-on-time uint256 deadline = block.timestamp + 100_000; bytes32 domainSeparator = keccak256( abi.encode( @@ -1780,7 +1776,6 @@ contract ERC4626VaultTest is ERC4626Test { address spender = makeAddr("spender"); uint256 amount = 100; uint256 nonce = 1; - // solhint-disable-next-line not-rely-on-time uint256 deadline = block.timestamp + 100_000; bytes32 domainSeparator = ERC4626ExtendedDecimalsOffset0 .DOMAIN_SEPARATOR(); @@ -1820,7 +1815,6 @@ contract ERC4626VaultTest is ERC4626Test { address spender = makeAddr("spender"); uint256 amount = 100; uint256 nonce = ERC4626ExtendedDecimalsOffset0.nonces(owner); - // solhint-disable-next-line not-rely-on-time uint256 deadline = block.timestamp - 1; bytes32 domainSeparator = ERC4626ExtendedDecimalsOffset0 .DOMAIN_SEPARATOR(); @@ -1915,7 +1909,6 @@ contract ERC4626VaultTest is ERC4626Test { address spenderAddr = makeAddr(spender); uint256 amount = block.number; uint256 nonce = ERC4626ExtendedDecimalsOffset0.nonces(ownerAddr); - // solhint-disable-next-line not-rely-on-time uint256 deadline = block.timestamp + increment; bytes32 domainSeparator = ERC4626ExtendedDecimalsOffset0 .DOMAIN_SEPARATOR(); @@ -1969,7 +1962,6 @@ contract ERC4626VaultTest is ERC4626Test { address spenderAddr = makeAddr(spender); uint256 amount = block.number; uint256 nonce = ERC4626ExtendedDecimalsOffset0.nonces(ownerAddr); - // solhint-disable-next-line not-rely-on-time uint256 deadline = block.timestamp + increment; bytes32 domainSeparator = ERC4626ExtendedDecimalsOffset0 .DOMAIN_SEPARATOR(); diff --git a/test/governance/TimelockController.t.sol b/test/governance/TimelockController.t.sol new file mode 100644 index 00000000..21ab477e --- /dev/null +++ b/test/governance/TimelockController.t.sol @@ -0,0 +1,4454 @@ +// SPDX-License-Identifier: WTFPL +pragma solidity ^0.8.23; + +import {Test} from "forge-std/Test.sol"; +import {VyperDeployer} from "utils/VyperDeployer.sol"; + +import {IERC165} from "openzeppelin/utils/introspection/IERC165.sol"; +import {IAccessControl} from "openzeppelin/access/IAccessControl.sol"; +import {IERC1155Receiver} from "openzeppelin/token/ERC1155/IERC1155Receiver.sol"; + +import {ERC721ReceiverMock} from "../tokens/mocks/ERC721ReceiverMock.sol"; +import {ERC1155ReceiverMock} from "../tokens/mocks/ERC1155ReceiverMock.sol"; +import {CallReceiverMock} from "./mocks/CallReceiverMock.sol"; + +import {IERC721Extended} from "../tokens/interfaces/IERC721Extended.sol"; +import {IERC1155Extended} from "../tokens/interfaces/IERC1155Extended.sol"; +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! + */ +contract TimelockControllerTest is Test { + bytes32 private constant DEFAULT_ADMIN_ROLE = bytes32(0); + bytes32 private constant PROPOSER_ROLE = keccak256("PROPOSER_ROLE"); + bytes32 private constant EXECUTOR_ROLE = keccak256("EXECUTOR_ROLE"); + bytes32 private constant CANCELLER_ROLE = keccak256("CANCELLER_ROLE"); + + bytes4 private constant IERC721_TOKENRECEIVER_SELECTOR = + ERC721ReceiverMock.onERC721Received.selector; + bytes4 private constant IERC1155_TOKENRECEIVER_SINGLE_SELECTOR = + ERC1155ReceiverMock.onERC1155Received.selector; + bytes4 private constant IERC1155_TOKENRECEIVER_BATCH_SELECTOR = + ERC1155ReceiverMock.onERC1155BatchReceived.selector; + + uint256 private constant MIN_DELAY = 2 days; + uint256 private constant DONE_TIMESTAMP = 1; + + address private constant PROPOSER_ONE = + address(uint160(uint256(keccak256(abi.encodePacked("PROPOSER_ONE"))))); + address private constant PROPOSER_TWO = + address(uint160(uint256(keccak256(abi.encodePacked("PROPOSER_TWO"))))); + address private constant EXECUTOR_ONE = + address(uint160(uint256(keccak256(abi.encodePacked("EXECUTOR_ONE"))))); + address private constant EXECUTOR_TWO = + address(uint160(uint256(keccak256(abi.encodePacked("EXECUTOR_TWO"))))); + address private constant STRANGER = + address(uint160(uint256(keccak256(abi.encodePacked("STRANGER"))))); + + bytes32 private constant NO_PREDECESSOR = bytes32(""); + bytes32 private constant EMPTY_SALT = bytes32(""); + bytes32 private constant SALT = keccak256("WAGMI"); + + VyperDeployer private vyperDeployer = new VyperDeployer(); + CallReceiverMock private callReceiverMock = new CallReceiverMock(); + + ITimelockController private timelockController; + ITimelockController private timelockControllerInitialEventEmptyAdmin; + ITimelockController private timelockControllerInitialEventNonEmptyAdmin; + IERC721Extended private erc721Mock; + IERC1155Extended private erc1155Mock; + + address private deployer = address(vyperDeployer); + address private self = address(this); + address private zeroAddress = zeroAddress; + address private target = address(callReceiverMock); + address private timelockControllerAddr; + address private timelockControllerInitialEventEmptyAdminAddr; + address private timelockControllerInitialEventNonEmptyAdminAddr; + + address[2] private proposers = [PROPOSER_ONE, PROPOSER_TWO]; + address[2] private executors = [EXECUTOR_ONE, EXECUTOR_TWO]; + + /** + * @dev An `internal` helper function to check whether a specific role `role` + * is not assigned to an array of addresses with the length 2. + * @param accessControl The contract that implements the `IAccessControl` interface. + * @param role The 32-byte role definition. + * @param addresses The 20-byte array with the length 2 of accounts to be checked. + */ + function checkRoleNotSetForAddresses( + IAccessControl accessControl, + bytes32 role, + address[2] storage addresses + ) internal { + assertTrue( + !accessControl.hasRole(role, addresses[0]) && + !accessControl.hasRole(role, addresses[1]) + ); + } + + function setUp() public { + address[] memory proposers_ = new address[](2); + proposers_[0] = proposers[0]; + proposers_[1] = proposers[1]; + address[] memory executors_ = new address[](2); + executors_[0] = executors[0]; + executors_[1] = executors[1]; + + bytes memory args = abi.encode(MIN_DELAY, proposers_, executors_, self); + timelockController = ITimelockController( + vyperDeployer.deployContract( + "src/governance/", + "TimelockController", + args + ) + ); + timelockControllerAddr = address(timelockController); + } + + function testInitialSetup() public { + assertEq(timelockController.DEFAULT_ADMIN_ROLE(), DEFAULT_ADMIN_ROLE); + assertEq(timelockController.PROPOSER_ROLE(), PROPOSER_ROLE); + assertEq(timelockController.EXECUTOR_ROLE(), EXECUTOR_ROLE); + assertEq(timelockController.CANCELLER_ROLE(), CANCELLER_ROLE); + assertEq( + timelockController.IERC721_TOKENRECEIVER_SELECTOR(), + IERC721_TOKENRECEIVER_SELECTOR + ); + assertEq( + timelockController.IERC1155_TOKENRECEIVER_SINGLE_SELECTOR(), + IERC1155_TOKENRECEIVER_SINGLE_SELECTOR + ); + assertEq( + timelockController.IERC1155_TOKENRECEIVER_BATCH_SELECTOR(), + IERC1155_TOKENRECEIVER_BATCH_SELECTOR + ); + assertTrue( + timelockController.hasRole( + timelockController.DEFAULT_ADMIN_ROLE(), + timelockControllerAddr + ) + ); + assertTrue( + timelockController.hasRole( + timelockController.DEFAULT_ADMIN_ROLE(), + self + ) + ); + assertTrue( + timelockController.hasRole( + timelockController.PROPOSER_ROLE(), + PROPOSER_ONE + ) + ); + assertTrue( + timelockController.hasRole( + timelockController.PROPOSER_ROLE(), + PROPOSER_TWO + ) + ); + assertTrue( + timelockController.hasRole( + timelockController.CANCELLER_ROLE(), + PROPOSER_ONE + ) + ); + assertTrue( + timelockController.hasRole( + timelockController.CANCELLER_ROLE(), + PROPOSER_TWO + ) + ); + assertTrue( + timelockController.hasRole( + timelockController.EXECUTOR_ROLE(), + EXECUTOR_ONE + ) + ); + assertTrue( + timelockController.hasRole( + timelockController.EXECUTOR_ROLE(), + EXECUTOR_TWO + ) + ); + assertTrue( + !timelockController.hasRole( + timelockController.PROPOSER_ROLE(), + self + ) + ); + assertTrue( + !timelockController.hasRole( + timelockController.CANCELLER_ROLE(), + self + ) + ); + assertTrue( + !timelockController.hasRole( + timelockController.EXECUTOR_ROLE(), + self + ) + ); + assertTrue( + !timelockController.hasRole( + timelockController.PROPOSER_ROLE(), + timelockControllerAddr + ) + ); + assertTrue( + !timelockController.hasRole( + timelockController.CANCELLER_ROLE(), + timelockControllerAddr + ) + ); + assertTrue( + !timelockController.hasRole( + timelockController.EXECUTOR_ROLE(), + timelockControllerAddr + ) + ); + checkRoleNotSetForAddresses( + timelockController, + timelockController.DEFAULT_ADMIN_ROLE(), + proposers + ); + checkRoleNotSetForAddresses( + timelockController, + timelockController.EXECUTOR_ROLE(), + proposers + ); + checkRoleNotSetForAddresses( + timelockController, + timelockController.DEFAULT_ADMIN_ROLE(), + executors + ); + checkRoleNotSetForAddresses( + timelockController, + timelockController.PROPOSER_ROLE(), + executors + ); + checkRoleNotSetForAddresses( + timelockController, + timelockController.CANCELLER_ROLE(), + executors + ); + assertEq(timelockController.get_minimum_delay(), MIN_DELAY); + + address[] memory proposers_ = new address[](2); + proposers_[0] = proposers[0]; + proposers_[1] = proposers[1]; + address[] memory executors_ = new address[](2); + executors_[0] = executors[0]; + executors_[1] = executors[1]; + bytes memory argsEmptyAdmin = abi.encode( + MIN_DELAY, + proposers_, + executors_, + zeroAddress + ); + vm.expectEmit(true, true, true, false); + emit IAccessControl.RoleGranted( + DEFAULT_ADMIN_ROLE, + vm.computeCreateAddress(deployer, vm.getNonce(deployer)), + deployer + ); + vm.expectEmit(true, true, true, false); + emit IAccessControl.RoleGranted(PROPOSER_ROLE, proposers[0], deployer); + vm.expectEmit(true, true, true, false); + emit IAccessControl.RoleGranted(CANCELLER_ROLE, proposers[0], deployer); + vm.expectEmit(true, true, true, false); + emit IAccessControl.RoleGranted(PROPOSER_ROLE, proposers[1], deployer); + vm.expectEmit(true, true, true, false); + emit IAccessControl.RoleGranted(CANCELLER_ROLE, proposers[1], deployer); + vm.expectEmit(true, true, true, false); + emit IAccessControl.RoleGranted(EXECUTOR_ROLE, executors[0], deployer); + vm.expectEmit(true, true, true, false); + emit IAccessControl.RoleGranted(EXECUTOR_ROLE, executors[1], deployer); + vm.expectEmit(true, true, false, false); + emit ITimelockController.MinimumDelayChange(0, MIN_DELAY); + timelockControllerInitialEventEmptyAdmin = ITimelockController( + vyperDeployer.deployContract( + "src/governance/", + "TimelockController", + argsEmptyAdmin + ) + ); + timelockControllerInitialEventEmptyAdminAddr = address( + timelockControllerInitialEventEmptyAdmin + ); + assertEq( + timelockControllerInitialEventEmptyAdmin.DEFAULT_ADMIN_ROLE(), + DEFAULT_ADMIN_ROLE + ); + assertEq( + timelockControllerInitialEventEmptyAdmin.PROPOSER_ROLE(), + PROPOSER_ROLE + ); + assertEq( + timelockControllerInitialEventEmptyAdmin.EXECUTOR_ROLE(), + EXECUTOR_ROLE + ); + assertEq( + timelockControllerInitialEventEmptyAdmin.CANCELLER_ROLE(), + CANCELLER_ROLE + ); + assertEq( + timelockControllerInitialEventEmptyAdmin + .IERC721_TOKENRECEIVER_SELECTOR(), + IERC721_TOKENRECEIVER_SELECTOR + ); + assertEq( + timelockControllerInitialEventEmptyAdmin + .IERC1155_TOKENRECEIVER_SINGLE_SELECTOR(), + IERC1155_TOKENRECEIVER_SINGLE_SELECTOR + ); + assertEq( + timelockControllerInitialEventEmptyAdmin + .IERC1155_TOKENRECEIVER_BATCH_SELECTOR(), + IERC1155_TOKENRECEIVER_BATCH_SELECTOR + ); + assertTrue( + timelockControllerInitialEventEmptyAdmin.hasRole( + timelockControllerInitialEventEmptyAdmin.DEFAULT_ADMIN_ROLE(), + timelockControllerInitialEventEmptyAdminAddr + ) + ); + assertTrue( + !timelockControllerInitialEventEmptyAdmin.hasRole( + timelockControllerInitialEventEmptyAdmin.DEFAULT_ADMIN_ROLE(), + self + ) + ); + assertTrue( + timelockControllerInitialEventEmptyAdmin.hasRole( + timelockControllerInitialEventEmptyAdmin.PROPOSER_ROLE(), + PROPOSER_ONE + ) + ); + assertTrue( + timelockControllerInitialEventEmptyAdmin.hasRole( + timelockControllerInitialEventEmptyAdmin.PROPOSER_ROLE(), + PROPOSER_TWO + ) + ); + assertTrue( + timelockControllerInitialEventEmptyAdmin.hasRole( + timelockControllerInitialEventEmptyAdmin.CANCELLER_ROLE(), + PROPOSER_ONE + ) + ); + assertTrue( + timelockControllerInitialEventEmptyAdmin.hasRole( + timelockControllerInitialEventEmptyAdmin.CANCELLER_ROLE(), + PROPOSER_TWO + ) + ); + assertTrue( + timelockControllerInitialEventEmptyAdmin.hasRole( + timelockControllerInitialEventEmptyAdmin.EXECUTOR_ROLE(), + EXECUTOR_ONE + ) + ); + assertTrue( + timelockControllerInitialEventEmptyAdmin.hasRole( + timelockControllerInitialEventEmptyAdmin.EXECUTOR_ROLE(), + EXECUTOR_TWO + ) + ); + assertTrue( + !timelockControllerInitialEventEmptyAdmin.hasRole( + timelockControllerInitialEventEmptyAdmin.PROPOSER_ROLE(), + self + ) + ); + assertTrue( + !timelockControllerInitialEventEmptyAdmin.hasRole( + timelockControllerInitialEventEmptyAdmin.CANCELLER_ROLE(), + self + ) + ); + assertTrue( + !timelockControllerInitialEventEmptyAdmin.hasRole( + timelockControllerInitialEventEmptyAdmin.EXECUTOR_ROLE(), + self + ) + ); + assertTrue( + !timelockControllerInitialEventEmptyAdmin.hasRole( + timelockControllerInitialEventEmptyAdmin.PROPOSER_ROLE(), + timelockControllerInitialEventEmptyAdminAddr + ) + ); + assertTrue( + !timelockControllerInitialEventEmptyAdmin.hasRole( + timelockControllerInitialEventEmptyAdmin.CANCELLER_ROLE(), + timelockControllerInitialEventEmptyAdminAddr + ) + ); + assertTrue( + !timelockControllerInitialEventEmptyAdmin.hasRole( + timelockControllerInitialEventEmptyAdmin.EXECUTOR_ROLE(), + timelockControllerInitialEventEmptyAdminAddr + ) + ); + checkRoleNotSetForAddresses( + timelockControllerInitialEventEmptyAdmin, + timelockControllerInitialEventEmptyAdmin.DEFAULT_ADMIN_ROLE(), + proposers + ); + checkRoleNotSetForAddresses( + timelockControllerInitialEventEmptyAdmin, + timelockControllerInitialEventEmptyAdmin.EXECUTOR_ROLE(), + proposers + ); + checkRoleNotSetForAddresses( + timelockControllerInitialEventEmptyAdmin, + timelockControllerInitialEventEmptyAdmin.DEFAULT_ADMIN_ROLE(), + executors + ); + checkRoleNotSetForAddresses( + timelockControllerInitialEventEmptyAdmin, + timelockControllerInitialEventEmptyAdmin.PROPOSER_ROLE(), + executors + ); + checkRoleNotSetForAddresses( + timelockControllerInitialEventEmptyAdmin, + timelockControllerInitialEventEmptyAdmin.CANCELLER_ROLE(), + executors + ); + assertEq( + timelockControllerInitialEventEmptyAdmin.get_minimum_delay(), + MIN_DELAY + ); + + bytes memory argsNonEmptyAdmin = abi.encode( + MIN_DELAY, + proposers_, + executors_, + self + ); + vm.expectEmit(true, true, true, false); + emit IAccessControl.RoleGranted( + DEFAULT_ADMIN_ROLE, + vm.computeCreateAddress(deployer, vm.getNonce(deployer)), + deployer + ); + vm.expectEmit(true, true, true, false); + emit IAccessControl.RoleGranted(DEFAULT_ADMIN_ROLE, self, deployer); + vm.expectEmit(true, true, true, false); + emit IAccessControl.RoleGranted(PROPOSER_ROLE, proposers[0], deployer); + vm.expectEmit(true, true, true, false); + emit IAccessControl.RoleGranted(CANCELLER_ROLE, proposers[0], deployer); + vm.expectEmit(true, true, true, false); + emit IAccessControl.RoleGranted(PROPOSER_ROLE, proposers[1], deployer); + vm.expectEmit(true, true, true, false); + emit IAccessControl.RoleGranted(CANCELLER_ROLE, proposers[1], deployer); + vm.expectEmit(true, true, true, false); + emit IAccessControl.RoleGranted(EXECUTOR_ROLE, executors[0], deployer); + vm.expectEmit(true, true, true, false); + emit IAccessControl.RoleGranted(EXECUTOR_ROLE, executors[1], deployer); + vm.expectEmit(true, true, false, false); + emit ITimelockController.MinimumDelayChange(0, MIN_DELAY); + timelockControllerInitialEventNonEmptyAdmin = ITimelockController( + vyperDeployer.deployContract( + "src/governance/", + "TimelockController", + argsNonEmptyAdmin + ) + ); + timelockControllerInitialEventNonEmptyAdminAddr = address( + timelockControllerInitialEventNonEmptyAdmin + ); + assertEq( + timelockControllerInitialEventNonEmptyAdmin.DEFAULT_ADMIN_ROLE(), + DEFAULT_ADMIN_ROLE + ); + assertEq( + timelockControllerInitialEventNonEmptyAdmin.PROPOSER_ROLE(), + PROPOSER_ROLE + ); + assertEq( + timelockControllerInitialEventNonEmptyAdmin.EXECUTOR_ROLE(), + EXECUTOR_ROLE + ); + assertEq( + timelockControllerInitialEventNonEmptyAdmin.CANCELLER_ROLE(), + CANCELLER_ROLE + ); + assertEq( + timelockControllerInitialEventNonEmptyAdmin + .IERC721_TOKENRECEIVER_SELECTOR(), + IERC721_TOKENRECEIVER_SELECTOR + ); + assertEq( + timelockControllerInitialEventNonEmptyAdmin + .IERC1155_TOKENRECEIVER_SINGLE_SELECTOR(), + IERC1155_TOKENRECEIVER_SINGLE_SELECTOR + ); + assertEq( + timelockControllerInitialEventNonEmptyAdmin + .IERC1155_TOKENRECEIVER_BATCH_SELECTOR(), + IERC1155_TOKENRECEIVER_BATCH_SELECTOR + ); + assertTrue( + timelockControllerInitialEventNonEmptyAdmin.hasRole( + timelockControllerInitialEventNonEmptyAdmin + .DEFAULT_ADMIN_ROLE(), + timelockControllerInitialEventNonEmptyAdminAddr + ) + ); + assertTrue( + timelockControllerInitialEventNonEmptyAdmin.hasRole( + timelockControllerInitialEventNonEmptyAdmin + .DEFAULT_ADMIN_ROLE(), + self + ) + ); + assertTrue( + timelockControllerInitialEventNonEmptyAdmin.hasRole( + timelockControllerInitialEventNonEmptyAdmin.PROPOSER_ROLE(), + PROPOSER_ONE + ) + ); + assertTrue( + timelockControllerInitialEventNonEmptyAdmin.hasRole( + timelockControllerInitialEventNonEmptyAdmin.PROPOSER_ROLE(), + PROPOSER_TWO + ) + ); + assertTrue( + timelockControllerInitialEventNonEmptyAdmin.hasRole( + timelockControllerInitialEventNonEmptyAdmin.CANCELLER_ROLE(), + PROPOSER_ONE + ) + ); + assertTrue( + timelockControllerInitialEventNonEmptyAdmin.hasRole( + timelockControllerInitialEventNonEmptyAdmin.CANCELLER_ROLE(), + PROPOSER_TWO + ) + ); + assertTrue( + timelockControllerInitialEventNonEmptyAdmin.hasRole( + timelockControllerInitialEventNonEmptyAdmin.EXECUTOR_ROLE(), + EXECUTOR_ONE + ) + ); + assertTrue( + timelockControllerInitialEventNonEmptyAdmin.hasRole( + timelockControllerInitialEventNonEmptyAdmin.EXECUTOR_ROLE(), + EXECUTOR_TWO + ) + ); + assertTrue( + !timelockControllerInitialEventNonEmptyAdmin.hasRole( + timelockControllerInitialEventNonEmptyAdmin.PROPOSER_ROLE(), + self + ) + ); + assertTrue( + !timelockControllerInitialEventNonEmptyAdmin.hasRole( + timelockControllerInitialEventNonEmptyAdmin.CANCELLER_ROLE(), + self + ) + ); + assertTrue( + !timelockControllerInitialEventNonEmptyAdmin.hasRole( + timelockControllerInitialEventNonEmptyAdmin.EXECUTOR_ROLE(), + self + ) + ); + assertTrue( + !timelockControllerInitialEventNonEmptyAdmin.hasRole( + timelockControllerInitialEventNonEmptyAdmin.PROPOSER_ROLE(), + timelockControllerInitialEventNonEmptyAdminAddr + ) + ); + assertTrue( + !timelockControllerInitialEventNonEmptyAdmin.hasRole( + timelockControllerInitialEventNonEmptyAdmin.CANCELLER_ROLE(), + timelockControllerInitialEventNonEmptyAdminAddr + ) + ); + assertTrue( + !timelockControllerInitialEventNonEmptyAdmin.hasRole( + timelockControllerInitialEventNonEmptyAdmin.EXECUTOR_ROLE(), + timelockControllerInitialEventNonEmptyAdminAddr + ) + ); + checkRoleNotSetForAddresses( + timelockControllerInitialEventNonEmptyAdmin, + timelockControllerInitialEventNonEmptyAdmin.DEFAULT_ADMIN_ROLE(), + proposers + ); + checkRoleNotSetForAddresses( + timelockControllerInitialEventNonEmptyAdmin, + timelockControllerInitialEventNonEmptyAdmin.EXECUTOR_ROLE(), + proposers + ); + checkRoleNotSetForAddresses( + timelockControllerInitialEventNonEmptyAdmin, + timelockControllerInitialEventNonEmptyAdmin.DEFAULT_ADMIN_ROLE(), + executors + ); + checkRoleNotSetForAddresses( + timelockControllerInitialEventNonEmptyAdmin, + timelockControllerInitialEventNonEmptyAdmin.PROPOSER_ROLE(), + executors + ); + checkRoleNotSetForAddresses( + timelockControllerInitialEventNonEmptyAdmin, + timelockControllerInitialEventNonEmptyAdmin.CANCELLER_ROLE(), + executors + ); + assertEq( + timelockControllerInitialEventNonEmptyAdmin.get_minimum_delay(), + MIN_DELAY + ); + } + + function testCanReceiveEther() public { + payable(timelockControllerAddr).transfer(0.5 ether); + assertEq(timelockControllerAddr.balance, 0.5 ether); + } + + function testSupportsInterfaceSuccess() public { + assertTrue( + timelockController.supportsInterface(type(IERC165).interfaceId) + ); + assertTrue( + timelockController.supportsInterface( + type(IAccessControl).interfaceId + ) + ); + assertTrue( + timelockController.supportsInterface( + type(IERC1155Receiver).interfaceId + ) + ); + } + + function testSupportsInterfaceSuccessGasCost() public { + uint256 startGas = gasleft(); + timelockController.supportsInterface(type(IERC165).interfaceId); + uint256 gasUsed = startGas - gasleft(); + assertTrue( + gasUsed <= 30_000 && + timelockController.supportsInterface(type(IERC165).interfaceId) + ); + } + + function testSupportsInterfaceInvalidInterfaceId() public { + assertTrue(!timelockController.supportsInterface(0x0011bbff)); + } + + function testSupportsInterfaceInvalidInterfaceIdGasCost() public { + uint256 startGas = gasleft(); + timelockController.supportsInterface(0x0011bbff); + uint256 gasUsed = startGas - gasleft(); + assertTrue( + gasUsed <= 30_000 && + !timelockController.supportsInterface(0x0011bbff) + ); + } + + function testHashOperation() public { + uint256 amount = 0; + bytes32 slot = bytes32(uint256(1337)); + bytes32 value = bytes32(uint256(6699)); + bytes memory payload = abi.encodeWithSelector( + callReceiverMock.mockFunctionWritesStorage.selector, + slot, + value + ); + bytes32 operationId = timelockController.hash_operation( + target, + amount, + payload, + NO_PREDECESSOR, + EMPTY_SALT + ); + assertEq( + operationId, + keccak256( + abi.encode(target, amount, payload, NO_PREDECESSOR, EMPTY_SALT) + ) + ); + } + + function testScheduleAndExecuteWithEmptySalt() public { + uint256 amount = 0; + bytes32 slot = bytes32(uint256(1337)); + bytes32 value = bytes32(uint256(6699)); + bytes memory payload = abi.encodeWithSelector( + callReceiverMock.mockFunctionWritesStorage.selector, + slot, + value + ); + bytes32 operationId = timelockController.hash_operation( + target, + amount, + payload, + NO_PREDECESSOR, + EMPTY_SALT + ); + assertEq(vm.load(target, slot), bytes32(uint256(0))); + assertTrue(!timelockController.is_operation(operationId)); + assertEq(timelockController.get_operation_state(operationId), 1); + assertEq(timelockController.get_timestamp(operationId), 0); + + vm.startPrank(PROPOSER_ONE); + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallScheduled( + operationId, + 0, + target, + amount, + payload, + NO_PREDECESSOR, + MIN_DELAY + ); + timelockController.schedule( + target, + amount, + payload, + NO_PREDECESSOR, + EMPTY_SALT, + MIN_DELAY + ); + assertTrue(timelockController.is_operation(operationId)); + assertEq( + timelockController.get_timestamp(operationId), + block.timestamp + MIN_DELAY + ); + vm.stopPrank(); + + assertEq(timelockController.get_operation_state(operationId), 2); + vm.warp(block.timestamp + MIN_DELAY); + assertEq(timelockController.get_operation_state(operationId), 4); + vm.startPrank(EXECUTOR_ONE); + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallExecuted( + operationId, + 0, + target, + amount, + payload + ); + timelockController.execute( + target, + amount, + payload, + NO_PREDECESSOR, + EMPTY_SALT + ); + assertEq(vm.load(target, slot), value); + assertTrue(timelockController.is_operation(operationId)); + assertEq(timelockController.get_timestamp(operationId), DONE_TIMESTAMP); + assertEq(timelockController.get_operation_state(operationId), 8); + vm.stopPrank(); + } + + function testScheduleAndExecuteWithNonEmptySalt() public { + uint256 amount = 0; + bytes32 slot = bytes32(uint256(1337)); + bytes32 value = bytes32(uint256(6699)); + bytes memory payload = abi.encodeWithSelector( + callReceiverMock.mockFunctionWritesStorage.selector, + slot, + value + ); + bytes32 operationId = timelockController.hash_operation( + target, + amount, + payload, + NO_PREDECESSOR, + SALT + ); + assertEq(vm.load(target, slot), bytes32(uint256(0))); + assertTrue(!timelockController.is_operation(operationId)); + assertEq(timelockController.get_operation_state(operationId), 1); + assertEq(timelockController.get_timestamp(operationId), 0); + + vm.startPrank(PROPOSER_ONE); + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallScheduled( + operationId, + 0, + target, + amount, + payload, + NO_PREDECESSOR, + MIN_DELAY + ); + vm.expectEmit(true, false, false, true); + emit ITimelockController.CallSalt(operationId, SALT); + timelockController.schedule( + target, + amount, + payload, + NO_PREDECESSOR, + SALT, + MIN_DELAY + ); + assertTrue(timelockController.is_operation(operationId)); + assertEq( + timelockController.get_timestamp(operationId), + block.timestamp + MIN_DELAY + ); + vm.stopPrank(); + + assertEq(timelockController.get_operation_state(operationId), 2); + vm.warp(block.timestamp + MIN_DELAY); + assertEq(timelockController.get_operation_state(operationId), 4); + vm.startPrank(EXECUTOR_ONE); + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallExecuted( + operationId, + 0, + target, + amount, + payload + ); + timelockController.execute( + target, + amount, + payload, + NO_PREDECESSOR, + SALT + ); + assertEq(vm.load(target, slot), value); + assertTrue(timelockController.is_operation(operationId)); + assertEq(timelockController.get_timestamp(operationId), DONE_TIMESTAMP); + assertEq(timelockController.get_operation_state(operationId), 8); + vm.stopPrank(); + } + + function testOperationAlreadyScheduled() public { + uint256 amount = 0; + bytes memory payload = new bytes(0); + bytes32 operationId = timelockController.hash_operation( + target, + amount, + payload, + NO_PREDECESSOR, + EMPTY_SALT + ); + vm.startPrank(PROPOSER_ONE); + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallScheduled( + operationId, + 0, + target, + amount, + payload, + NO_PREDECESSOR, + MIN_DELAY + ); + timelockController.schedule( + target, + amount, + payload, + NO_PREDECESSOR, + EMPTY_SALT, + MIN_DELAY + ); + vm.expectRevert("TimelockController: operation already scheduled"); + timelockController.schedule( + target, + amount, + payload, + NO_PREDECESSOR, + EMPTY_SALT, + MIN_DELAY + ); + vm.stopPrank(); + } + + function testOperationInsufficientDelay() public { + vm.expectRevert("TimelockController: insufficient delay"); + vm.prank(PROPOSER_ONE); + timelockController.schedule( + target, + 0, + new bytes(0), + NO_PREDECESSOR, + EMPTY_SALT, + MIN_DELAY - 1 + ); + } + + function testOperationEqualAndGreaterMinimumDelay() public { + uint256 amount = 0; + bytes memory payload = new bytes(0); + bytes32 operationId = timelockController.hash_operation( + zeroAddress, + amount, + payload, + NO_PREDECESSOR, + EMPTY_SALT + ); + + vm.startPrank(PROPOSER_ONE); + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallScheduled( + operationId, + 0, + zeroAddress, + amount, + payload, + NO_PREDECESSOR, + MIN_DELAY + ); + timelockController.schedule( + zeroAddress, + amount, + payload, + NO_PREDECESSOR, + EMPTY_SALT, + MIN_DELAY + ); + assertTrue(timelockController.is_operation(operationId)); + vm.stopPrank(); + + operationId = timelockController.hash_operation( + target, + amount, + payload, + NO_PREDECESSOR, + SALT + ); + vm.startPrank(PROPOSER_ONE); + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallScheduled( + operationId, + 0, + target, + amount, + payload, + NO_PREDECESSOR, + MIN_DELAY + 1 + ); + timelockController.schedule( + target, + amount, + payload, + NO_PREDECESSOR, + SALT, + MIN_DELAY + 1 + ); + assertTrue(timelockController.is_operation(operationId)); + vm.stopPrank(); + } + + function testOperationMinimumDelayUpdate() public { + uint256 amount = 0; + bytes memory payload = new bytes(0); + bytes32 operationId = timelockController.hash_operation( + zeroAddress, + amount, + payload, + NO_PREDECESSOR, + EMPTY_SALT + ); + + vm.startPrank(PROPOSER_ONE); + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallScheduled( + operationId, + 0, + zeroAddress, + amount, + payload, + NO_PREDECESSOR, + MIN_DELAY + ); + timelockController.schedule( + zeroAddress, + amount, + payload, + NO_PREDECESSOR, + EMPTY_SALT, + MIN_DELAY + ); + uint256 operationTimestampBefore = timelockController.get_timestamp( + operationId + ); + vm.stopPrank(); + + vm.startPrank(timelockControllerAddr); + vm.expectEmit(true, true, false, false); + emit ITimelockController.MinimumDelayChange( + MIN_DELAY, + MIN_DELAY + 31 days + ); + timelockController.update_delay(MIN_DELAY + 31 days); + uint256 operationTimestampAfter = timelockController.get_timestamp( + operationId + ); + assertEq(timelockController.get_minimum_delay(), MIN_DELAY + 31 days); + assertEq(operationTimestampAfter, operationTimestampBefore); + vm.stopPrank(); + } + + function testCompletePipelineOperationMinimumDelayUpdate() public { + uint256 amount = 0; + bytes memory payload = abi.encodeWithSelector( + timelockController.update_delay.selector, + MIN_DELAY + 31 days + ); + bytes32 operationId = timelockController.hash_operation( + timelockControllerAddr, + amount, + payload, + NO_PREDECESSOR, + EMPTY_SALT + ); + + vm.startPrank(PROPOSER_ONE); + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallScheduled( + operationId, + 0, + timelockControllerAddr, + amount, + payload, + NO_PREDECESSOR, + MIN_DELAY + ); + timelockController.schedule( + timelockControllerAddr, + amount, + payload, + NO_PREDECESSOR, + EMPTY_SALT, + MIN_DELAY + ); + vm.stopPrank(); + + vm.warp(block.timestamp + MIN_DELAY); + vm.startPrank(EXECUTOR_ONE); + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallExecuted( + operationId, + 0, + timelockControllerAddr, + amount, + payload + ); + timelockController.execute( + timelockControllerAddr, + amount, + payload, + NO_PREDECESSOR, + EMPTY_SALT + ); + assertEq(timelockController.get_minimum_delay(), MIN_DELAY + 31 days); + vm.stopPrank(); + } + + function testOperationOperationIsNotReady() public { + uint256 amount = 0; + bytes memory payload = new bytes(0); + bytes32 operationId = timelockController.hash_operation( + zeroAddress, + amount, + payload, + NO_PREDECESSOR, + EMPTY_SALT + ); + + vm.startPrank(PROPOSER_ONE); + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallScheduled( + operationId, + 0, + zeroAddress, + amount, + payload, + NO_PREDECESSOR, + MIN_DELAY + ); + timelockController.schedule( + zeroAddress, + amount, + payload, + NO_PREDECESSOR, + EMPTY_SALT, + MIN_DELAY + ); + vm.stopPrank(); + + vm.warp(block.timestamp + MIN_DELAY - 2 days); + vm.expectRevert("TimelockController: operation is not ready"); + vm.prank(EXECUTOR_ONE); + timelockController.execute( + zeroAddress, + amount, + payload, + NO_PREDECESSOR, + EMPTY_SALT + ); + } + + function testOperationPredecessorNotExecuted() public { + uint256 amount = 0; + bytes32 slot = bytes32(uint256(1337)); + bytes32 value = bytes32(uint256(6699)); + bytes memory payload = abi.encodeWithSelector( + callReceiverMock.mockFunctionWritesStorage.selector, + slot, + value + ); + bytes32 operationId1 = timelockController.hash_operation( + target, + amount, + payload, + NO_PREDECESSOR, + EMPTY_SALT + ); + bytes32 operationId2 = timelockController.hash_operation( + target, + amount, + payload, + operationId1, + SALT + ); + + vm.startPrank(PROPOSER_ONE); + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallScheduled( + operationId1, + 0, + target, + amount, + payload, + NO_PREDECESSOR, + MIN_DELAY + ); + timelockController.schedule( + target, + amount, + payload, + NO_PREDECESSOR, + EMPTY_SALT, + MIN_DELAY + ); + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallScheduled( + operationId2, + 0, + target, + amount, + payload, + operationId1, + MIN_DELAY + ); + timelockController.schedule( + target, + amount, + payload, + operationId1, + SALT, + MIN_DELAY + ); + vm.stopPrank(); + + vm.warp(block.timestamp + MIN_DELAY + 2 days); + vm.expectRevert("TimelockController: missing dependency"); + vm.prank(EXECUTOR_ONE); + timelockController.execute(target, amount, payload, operationId1, SALT); + } + + function testOperationPredecessorNotScheduled() public { + uint256 amount = 0; + bytes32 slot = bytes32(uint256(1337)); + bytes32 value = bytes32(uint256(6699)); + bytes memory payload = abi.encodeWithSelector( + callReceiverMock.mockFunctionWritesStorage.selector, + slot, + value + ); + bytes32 operationId1 = timelockController.hash_operation( + target, + amount, + payload, + NO_PREDECESSOR, + EMPTY_SALT + ); + bytes32 operationId2 = timelockController.hash_operation( + target, + amount, + payload, + operationId1, + SALT + ); + + vm.startPrank(PROPOSER_ONE); + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallScheduled( + operationId2, + 0, + target, + amount, + payload, + operationId1, + MIN_DELAY + ); + timelockController.schedule( + target, + amount, + payload, + operationId1, + SALT, + MIN_DELAY + ); + vm.stopPrank(); + + vm.warp(block.timestamp + MIN_DELAY + 2 days); + vm.expectRevert("TimelockController: missing dependency"); + vm.prank(EXECUTOR_ONE); + timelockController.execute(target, amount, payload, operationId1, SALT); + } + + function testOperationPredecessorInvalid() public { + uint256 amount = 0; + bytes32 slot = bytes32(uint256(1337)); + bytes32 value = bytes32(uint256(6699)); + bytes memory payload = abi.encodeWithSelector( + callReceiverMock.mockFunctionWritesStorage.selector, + slot, + value + ); + bytes32 invalidPredecessor = keccak256("Invalid Predecessor"); + bytes32 operationId = timelockController.hash_operation( + target, + amount, + payload, + invalidPredecessor, + EMPTY_SALT + ); + + vm.startPrank(PROPOSER_ONE); + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallScheduled( + operationId, + 0, + target, + amount, + payload, + invalidPredecessor, + MIN_DELAY + ); + timelockController.schedule( + target, + amount, + payload, + invalidPredecessor, + EMPTY_SALT, + MIN_DELAY + ); + vm.stopPrank(); + + vm.warp(block.timestamp + MIN_DELAY + 2 days); + vm.expectRevert("TimelockController: missing dependency"); + vm.prank(EXECUTOR_ONE); + timelockController.execute( + target, + amount, + payload, + invalidPredecessor, + EMPTY_SALT + ); + } + + function testOperationTargetRevert() public { + uint256 amount = 0; + bytes memory payload1 = abi.encodeWithSelector( + callReceiverMock.mockFunctionRevertsWithReason.selector + ); + bytes memory payload2 = abi.encodeWithSelector( + callReceiverMock.mockFunctionRevertsWithEmptyReason.selector + ); + bytes32 operationId1 = timelockController.hash_operation( + target, + amount, + payload1, + NO_PREDECESSOR, + EMPTY_SALT + ); + bytes32 operationId2 = timelockController.hash_operation( + target, + amount, + payload2, + NO_PREDECESSOR, + SALT + ); + + vm.startPrank(PROPOSER_ONE); + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallScheduled( + operationId1, + 0, + target, + amount, + payload1, + NO_PREDECESSOR, + MIN_DELAY + ); + timelockController.schedule( + target, + amount, + payload1, + NO_PREDECESSOR, + EMPTY_SALT, + MIN_DELAY + ); + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallScheduled( + operationId2, + 0, + target, + amount, + payload2, + NO_PREDECESSOR, + MIN_DELAY + ); + timelockController.schedule( + target, + amount, + payload2, + NO_PREDECESSOR, + SALT, + MIN_DELAY + ); + vm.stopPrank(); + + vm.warp(block.timestamp + MIN_DELAY + 2 days); + vm.expectRevert("CallReceiverMock: reverting"); + vm.startPrank(EXECUTOR_ONE); + timelockController.execute( + target, + amount, + payload1, + NO_PREDECESSOR, + EMPTY_SALT + ); + vm.expectRevert("TimelockController: underlying transaction reverted"); + timelockController.execute( + target, + amount, + payload2, + NO_PREDECESSOR, + SALT + ); + vm.stopPrank(); + } + + function testOperationPredecessorMultipleNotExecuted() public { + uint256 amount = 0; + bytes32 slot = bytes32(uint256(1337)); + bytes32 value = bytes32(uint256(6699)); + bytes memory payload = abi.encodeWithSelector( + callReceiverMock.mockFunctionWritesStorage.selector, + slot, + value + ); + bytes32 operationId = timelockController.hash_operation( + target, + amount, + payload, + NO_PREDECESSOR, + EMPTY_SALT + ); + + vm.startPrank(PROPOSER_ONE); + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallScheduled( + operationId, + 0, + target, + amount, + payload, + NO_PREDECESSOR, + MIN_DELAY + ); + timelockController.schedule( + target, + amount, + payload, + NO_PREDECESSOR, + EMPTY_SALT, + MIN_DELAY + ); + vm.stopPrank(); + + payload = abi.encodeWithSelector( + callReceiverMock.mockFunction.selector + ); + vm.startPrank(PROPOSER_TWO); + timelockController.schedule( + target, + amount, + payload, + operationId, + EMPTY_SALT, + MIN_DELAY + ); + vm.stopPrank(); + + vm.warp(block.timestamp + MIN_DELAY + 2 days); + vm.expectRevert("TimelockController: missing dependency"); + vm.prank(EXECUTOR_ONE); + timelockController.execute( + target, + amount, + payload, + operationId, + EMPTY_SALT + ); + } + + function testOperationCancelFinished() public { + uint256 amount = 0; + bytes32 slot = bytes32(uint256(1337)); + bytes32 value = bytes32(uint256(6699)); + bytes memory payload = abi.encodeWithSelector( + callReceiverMock.mockFunctionWritesStorage.selector, + slot, + value + ); + bytes32 operationId = timelockController.hash_operation( + target, + amount, + payload, + NO_PREDECESSOR, + EMPTY_SALT + ); + + vm.startPrank(PROPOSER_ONE); + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallScheduled( + operationId, + 0, + target, + amount, + payload, + NO_PREDECESSOR, + MIN_DELAY + ); + timelockController.schedule( + target, + amount, + payload, + NO_PREDECESSOR, + EMPTY_SALT, + MIN_DELAY + ); + vm.stopPrank(); + + vm.warp(block.timestamp + MIN_DELAY + 1); + vm.startPrank(EXECUTOR_ONE); + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallExecuted( + operationId, + 0, + target, + amount, + payload + ); + timelockController.execute( + target, + amount, + payload, + NO_PREDECESSOR, + EMPTY_SALT + ); + vm.stopPrank(); + + vm.prank(PROPOSER_ONE); + vm.expectRevert("TimelockController: operation cannot be cancelled"); + timelockController.cancel(operationId); + } + + function testOperationPendingIfNotYetExecuted() public { + uint256 amount = 0; + bytes32 slot = bytes32(uint256(1337)); + bytes32 value = bytes32(uint256(6699)); + bytes memory payload = abi.encodeWithSelector( + callReceiverMock.mockFunctionWritesStorage.selector, + slot, + value + ); + bytes32 operationId = timelockController.hash_operation( + target, + amount, + payload, + NO_PREDECESSOR, + EMPTY_SALT + ); + + vm.startPrank(PROPOSER_ONE); + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallScheduled( + operationId, + 0, + target, + amount, + payload, + NO_PREDECESSOR, + MIN_DELAY + ); + timelockController.schedule( + target, + amount, + payload, + NO_PREDECESSOR, + EMPTY_SALT, + MIN_DELAY + ); + assertTrue(timelockController.is_operation_pending(operationId)); + vm.stopPrank(); + } + + function testOperationPendingIfExecuted() public { + uint256 amount = 0; + bytes32 slot = bytes32(uint256(1337)); + bytes32 value = bytes32(uint256(6699)); + bytes memory payload = abi.encodeWithSelector( + callReceiverMock.mockFunctionWritesStorage.selector, + slot, + value + ); + bytes32 operationId = timelockController.hash_operation( + target, + amount, + payload, + NO_PREDECESSOR, + EMPTY_SALT + ); + + vm.startPrank(PROPOSER_ONE); + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallScheduled( + operationId, + 0, + target, + amount, + payload, + NO_PREDECESSOR, + MIN_DELAY + ); + timelockController.schedule( + target, + amount, + payload, + NO_PREDECESSOR, + EMPTY_SALT, + MIN_DELAY + ); + vm.stopPrank(); + + vm.warp(block.timestamp + MIN_DELAY); + vm.startPrank(EXECUTOR_ONE); + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallExecuted( + operationId, + 0, + target, + amount, + payload + ); + timelockController.execute( + target, + amount, + payload, + NO_PREDECESSOR, + EMPTY_SALT + ); + assertTrue(!timelockController.is_operation_pending(operationId)); + vm.stopPrank(); + } + + function testOperationReadyOnTheExecutionTime() public { + uint256 amount = 0; + bytes32 slot = bytes32(uint256(1337)); + bytes32 value = bytes32(uint256(6699)); + bytes memory payload = abi.encodeWithSelector( + callReceiverMock.mockFunctionWritesStorage.selector, + slot, + value + ); + bytes32 operationId = timelockController.hash_operation( + target, + amount, + payload, + NO_PREDECESSOR, + EMPTY_SALT + ); + + vm.startPrank(PROPOSER_ONE); + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallScheduled( + operationId, + 0, + target, + amount, + payload, + NO_PREDECESSOR, + MIN_DELAY + ); + timelockController.schedule( + target, + amount, + payload, + NO_PREDECESSOR, + EMPTY_SALT, + MIN_DELAY + ); + vm.warp(block.timestamp + MIN_DELAY); + assertTrue(timelockController.is_operation_ready(operationId)); + vm.stopPrank(); + } + + function testOperationReadyAfterTheExecutionTime() public { + uint256 amount = 0; + bytes32 slot = bytes32(uint256(1337)); + bytes32 value = bytes32(uint256(6699)); + bytes memory payload = abi.encodeWithSelector( + callReceiverMock.mockFunctionWritesStorage.selector, + slot, + value + ); + bytes32 operationId = timelockController.hash_operation( + target, + amount, + payload, + NO_PREDECESSOR, + EMPTY_SALT + ); + + vm.startPrank(PROPOSER_ONE); + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallScheduled( + operationId, + 0, + target, + amount, + payload, + NO_PREDECESSOR, + MIN_DELAY + ); + timelockController.schedule( + target, + amount, + payload, + NO_PREDECESSOR, + EMPTY_SALT, + MIN_DELAY + ); + vm.warp(block.timestamp + MIN_DELAY + 1 days); + assertTrue(timelockController.is_operation_ready(operationId)); + vm.stopPrank(); + } + + function testOperationReadyBeforeTheExecutionTime() public { + uint256 amount = 0; + bytes32 slot = bytes32(uint256(1337)); + bytes32 value = bytes32(uint256(6699)); + bytes memory payload = abi.encodeWithSelector( + callReceiverMock.mockFunctionWritesStorage.selector, + slot, + value + ); + bytes32 operationId = timelockController.hash_operation( + target, + amount, + payload, + NO_PREDECESSOR, + EMPTY_SALT + ); + + vm.startPrank(PROPOSER_ONE); + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallScheduled( + operationId, + 0, + target, + amount, + payload, + NO_PREDECESSOR, + MIN_DELAY + ); + timelockController.schedule( + target, + amount, + payload, + NO_PREDECESSOR, + EMPTY_SALT, + MIN_DELAY + ); + vm.warp(block.timestamp + MIN_DELAY - 1 days); + assertTrue(!timelockController.is_operation_ready(operationId)); + vm.stopPrank(); + } + + function testOperationHasBeenExecuted() public { + uint256 amount = 0; + bytes32 slot = bytes32(uint256(1337)); + bytes32 value = bytes32(uint256(6699)); + bytes memory payload = abi.encodeWithSelector( + callReceiverMock.mockFunctionWritesStorage.selector, + slot, + value + ); + bytes32 operationId = timelockController.hash_operation( + target, + amount, + payload, + NO_PREDECESSOR, + EMPTY_SALT + ); + + vm.startPrank(PROPOSER_ONE); + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallScheduled( + operationId, + 0, + target, + amount, + payload, + NO_PREDECESSOR, + MIN_DELAY + ); + timelockController.schedule( + target, + amount, + payload, + NO_PREDECESSOR, + EMPTY_SALT, + MIN_DELAY + ); + vm.stopPrank(); + + vm.warp(block.timestamp + MIN_DELAY); + vm.startPrank(EXECUTOR_ONE); + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallExecuted( + operationId, + 0, + target, + amount, + payload + ); + timelockController.execute( + target, + amount, + payload, + NO_PREDECESSOR, + EMPTY_SALT + ); + assertTrue(!timelockController.is_operation_ready(operationId)); + assertTrue(timelockController.is_operation_done(operationId)); + vm.stopPrank(); + } + + function testOperationHasNotBeenExecuted() public { + uint256 amount = 0; + bytes32 slot = bytes32(uint256(1337)); + bytes32 value = bytes32(uint256(6699)); + bytes memory payload = abi.encodeWithSelector( + callReceiverMock.mockFunctionWritesStorage.selector, + slot, + value + ); + bytes32 operationId = timelockController.hash_operation( + target, + amount, + payload, + NO_PREDECESSOR, + EMPTY_SALT + ); + + vm.startPrank(PROPOSER_ONE); + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallScheduled( + operationId, + 0, + target, + amount, + payload, + NO_PREDECESSOR, + MIN_DELAY + ); + timelockController.schedule( + target, + amount, + payload, + NO_PREDECESSOR, + EMPTY_SALT, + MIN_DELAY + ); + assertTrue(!timelockController.is_operation_done(operationId)); + vm.stopPrank(); + } + + function testOperationTimestampHasNotBeenExecuted() public { + uint256 amount = 0; + bytes32 slot = bytes32(uint256(1337)); + bytes32 value = bytes32(uint256(6699)); + bytes memory payload = abi.encodeWithSelector( + callReceiverMock.mockFunctionWritesStorage.selector, + slot, + value + ); + bytes32 operationId = timelockController.hash_operation( + target, + amount, + payload, + NO_PREDECESSOR, + EMPTY_SALT + ); + + vm.startPrank(PROPOSER_ONE); + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallScheduled( + operationId, + 0, + target, + amount, + payload, + NO_PREDECESSOR, + MIN_DELAY + ); + timelockController.schedule( + target, + amount, + payload, + NO_PREDECESSOR, + EMPTY_SALT, + MIN_DELAY + ); + assertEq( + timelockController.get_timestamp(operationId), + block.timestamp + MIN_DELAY + ); + vm.stopPrank(); + } + + function testOperationTimestampHasBeenExecuted() public { + uint256 amount = 0; + bytes32 slot = bytes32(uint256(1337)); + bytes32 value = bytes32(uint256(6699)); + bytes memory payload = abi.encodeWithSelector( + callReceiverMock.mockFunctionWritesStorage.selector, + slot, + value + ); + bytes32 operationId = timelockController.hash_operation( + target, + amount, + payload, + NO_PREDECESSOR, + EMPTY_SALT + ); + + vm.startPrank(PROPOSER_ONE); + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallScheduled( + operationId, + 0, + target, + amount, + payload, + NO_PREDECESSOR, + MIN_DELAY + ); + timelockController.schedule( + target, + amount, + payload, + NO_PREDECESSOR, + EMPTY_SALT, + MIN_DELAY + ); + vm.stopPrank(); + + vm.warp(block.timestamp + MIN_DELAY); + vm.startPrank(EXECUTOR_ONE); + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallExecuted( + operationId, + 0, + target, + amount, + payload + ); + timelockController.execute( + target, + amount, + payload, + NO_PREDECESSOR, + EMPTY_SALT + ); + assertEq(timelockController.get_timestamp(operationId), DONE_TIMESTAMP); + vm.stopPrank(); + } + + function testHashOperationBatch() public { + address[] memory targets = new address[](1); + targets[0] = target; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 0; + bytes32 slot = bytes32(uint256(1337)); + bytes32 value = bytes32(uint256(6699)); + bytes[] memory payloads = new bytes[](1); + payloads[0] = abi.encodeWithSelector( + callReceiverMock.mockFunctionWritesStorage.selector, + slot, + value + ); + bytes32 batchedOperationId = timelockController.hash_operation_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT + ); + assertEq( + batchedOperationId, + keccak256( + abi.encode( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT + ) + ) + ); + } + + function testBatchScheduleAndExecuteWithEmptySalt() public { + address[] memory targets = new address[](1); + targets[0] = target; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 0; + bytes32 slot = bytes32(uint256(1337)); + bytes32 value = bytes32(uint256(6699)); + bytes[] memory payloads = new bytes[](1); + payloads[0] = abi.encodeWithSelector( + callReceiverMock.mockFunctionWritesStorage.selector, + slot, + value + ); + bytes32 batchedOperationId = timelockController.hash_operation_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT + ); + assertEq(vm.load(target, slot), bytes32(uint256(0))); + assertTrue(!timelockController.is_operation(batchedOperationId)); + assertEq(timelockController.get_operation_state(batchedOperationId), 1); + assertEq(timelockController.get_timestamp(batchedOperationId), 0); + + vm.startPrank(PROPOSER_ONE); + for (uint256 i = 0; i < targets.length; ++i) { + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallScheduled( + batchedOperationId, + i, + targets[i], + amounts[i], + payloads[i], + NO_PREDECESSOR, + MIN_DELAY + ); + } + timelockController.schedule_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT, + MIN_DELAY + ); + assertTrue(timelockController.is_operation(batchedOperationId)); + assertEq( + timelockController.get_timestamp(batchedOperationId), + block.timestamp + MIN_DELAY + ); + vm.stopPrank(); + + assertEq(timelockController.get_operation_state(batchedOperationId), 2); + vm.warp(block.timestamp + MIN_DELAY); + assertEq(timelockController.get_operation_state(batchedOperationId), 4); + vm.startPrank(EXECUTOR_ONE); + for (uint256 i = 0; i < targets.length; ++i) { + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallExecuted( + batchedOperationId, + i, + targets[i], + amounts[i], + payloads[i] + ); + } + timelockController.execute_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT + ); + assertEq(vm.load(target, slot), value); + assertTrue(timelockController.is_operation(batchedOperationId)); + assertEq( + timelockController.get_timestamp(batchedOperationId), + DONE_TIMESTAMP + ); + assertEq(timelockController.get_operation_state(batchedOperationId), 8); + vm.stopPrank(); + } + + function testBatchScheduleAndExecuteWithNonEmptySalt() public { + address[] memory targets = new address[](1); + targets[0] = target; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 0; + bytes32 slot = bytes32(uint256(1337)); + bytes32 value = bytes32(uint256(6699)); + bytes[] memory payloads = new bytes[](1); + payloads[0] = abi.encodeWithSelector( + callReceiverMock.mockFunctionWritesStorage.selector, + slot, + value + ); + bytes32 batchedOperationId = timelockController.hash_operation_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + SALT + ); + assertEq(vm.load(target, slot), bytes32(uint256(0))); + assertTrue(!timelockController.is_operation(batchedOperationId)); + assertEq(timelockController.get_operation_state(batchedOperationId), 1); + assertEq(timelockController.get_timestamp(batchedOperationId), 0); + + vm.startPrank(PROPOSER_ONE); + for (uint256 i = 0; i < targets.length; ++i) { + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallScheduled( + batchedOperationId, + i, + targets[i], + amounts[i], + payloads[i], + NO_PREDECESSOR, + MIN_DELAY + ); + vm.expectEmit(true, false, false, true); + emit ITimelockController.CallSalt(batchedOperationId, SALT); + } + timelockController.schedule_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + SALT, + MIN_DELAY + ); + assertTrue(timelockController.is_operation(batchedOperationId)); + assertEq( + timelockController.get_timestamp(batchedOperationId), + block.timestamp + MIN_DELAY + ); + vm.stopPrank(); + + assertEq(timelockController.get_operation_state(batchedOperationId), 2); + vm.warp(block.timestamp + MIN_DELAY); + assertEq(timelockController.get_operation_state(batchedOperationId), 4); + vm.startPrank(EXECUTOR_ONE); + for (uint256 i = 0; i < targets.length; ++i) { + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallExecuted( + batchedOperationId, + i, + targets[i], + amounts[i], + payloads[i] + ); + } + timelockController.execute_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + SALT + ); + assertEq(vm.load(target, slot), value); + assertTrue(timelockController.is_operation(batchedOperationId)); + assertEq( + timelockController.get_timestamp(batchedOperationId), + DONE_TIMESTAMP + ); + assertEq(timelockController.get_operation_state(batchedOperationId), 8); + vm.stopPrank(); + } + + function testBatchOperationAlreadyScheduled() public { + address[] memory targets = new address[](1); + targets[0] = target; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 0; + bytes32 slot = bytes32(uint256(1337)); + bytes32 value = bytes32(uint256(6699)); + bytes[] memory payloads = new bytes[](1); + payloads[0] = abi.encodeWithSelector( + callReceiverMock.mockFunctionWritesStorage.selector, + slot, + value + ); + bytes32 batchedOperationId = timelockController.hash_operation_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT + ); + + vm.startPrank(PROPOSER_ONE); + for (uint256 i = 0; i < targets.length; ++i) { + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallScheduled( + batchedOperationId, + i, + targets[i], + amounts[i], + payloads[i], + NO_PREDECESSOR, + MIN_DELAY + ); + } + timelockController.schedule_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT, + MIN_DELAY + ); + vm.expectRevert("TimelockController: operation already scheduled"); + timelockController.schedule_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT, + MIN_DELAY + ); + vm.stopPrank(); + } + + function testBatchInsufficientDelay() public { + address[] memory targets = new address[](1); + targets[0] = target; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 0; + bytes32 slot = bytes32(uint256(1337)); + bytes32 value = bytes32(uint256(6699)); + bytes[] memory payloads = new bytes[](1); + payloads[0] = abi.encodeWithSelector( + callReceiverMock.mockFunctionWritesStorage.selector, + slot, + value + ); + vm.expectRevert("TimelockController: insufficient delay"); + vm.prank(PROPOSER_ONE); + timelockController.schedule_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT, + MIN_DELAY - 1 + ); + } + + function testBatchEqualAndGreaterMinimumDelay() public { + address[] memory targets = new address[](1); + targets[0] = target; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 0; + bytes32 slot = bytes32(uint256(1337)); + bytes32 value = bytes32(uint256(6699)); + bytes[] memory payloads = new bytes[](1); + payloads[0] = abi.encodeWithSelector( + callReceiverMock.mockFunctionWritesStorage.selector, + slot, + value + ); + bytes32 batchedOperationId = timelockController.hash_operation_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT + ); + + vm.startPrank(PROPOSER_ONE); + for (uint256 i = 0; i < targets.length; ++i) { + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallScheduled( + batchedOperationId, + i, + targets[i], + amounts[i], + payloads[i], + NO_PREDECESSOR, + MIN_DELAY + ); + } + timelockController.schedule_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT, + MIN_DELAY + ); + assertTrue(timelockController.is_operation(batchedOperationId)); + vm.stopPrank(); + + batchedOperationId = timelockController.hash_operation_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + SALT + ); + vm.startPrank(PROPOSER_ONE); + for (uint256 i = 0; i < targets.length; ++i) { + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallScheduled( + batchedOperationId, + i, + targets[i], + amounts[i], + payloads[i], + NO_PREDECESSOR, + MIN_DELAY + 1 + ); + } + timelockController.schedule_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + SALT, + MIN_DELAY + 1 + ); + assertTrue(timelockController.is_operation(batchedOperationId)); + vm.stopPrank(); + } + + function testBatchMinimumDelayUpdate() public { + address[] memory targets = new address[](1); + targets[0] = target; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 0; + bytes32 slot = bytes32(uint256(1337)); + bytes32 value = bytes32(uint256(6699)); + bytes[] memory payloads = new bytes[](1); + payloads[0] = abi.encodeWithSelector( + callReceiverMock.mockFunctionWritesStorage.selector, + slot, + value + ); + bytes32 batchedOperationId = timelockController.hash_operation_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT + ); + + vm.startPrank(PROPOSER_ONE); + for (uint256 i = 0; i < targets.length; ++i) { + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallScheduled( + batchedOperationId, + i, + targets[i], + amounts[i], + payloads[i], + NO_PREDECESSOR, + MIN_DELAY + ); + } + timelockController.schedule_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT, + MIN_DELAY + ); + uint256 operationTimestampBefore = timelockController.get_timestamp( + batchedOperationId + ); + vm.stopPrank(); + + vm.startPrank(address(timelockController)); + timelockController.update_delay(MIN_DELAY + 31 days); + uint256 operationTimestampAfter = timelockController.get_timestamp( + batchedOperationId + ); + assertEq(timelockController.get_minimum_delay(), MIN_DELAY + 31 days); + assertEq(operationTimestampAfter, operationTimestampBefore); + vm.stopPrank(); + } + + function testBatchOperationIsNotReady() public { + address[] memory targets = new address[](1); + targets[0] = target; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 0; + bytes32 slot = bytes32(uint256(1337)); + bytes32 value = bytes32(uint256(6699)); + bytes[] memory payloads = new bytes[](1); + payloads[0] = abi.encodeWithSelector( + callReceiverMock.mockFunctionWritesStorage.selector, + slot, + value + ); + bytes32 batchedOperationId = timelockController.hash_operation_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT + ); + + vm.startPrank(PROPOSER_ONE); + for (uint256 i = 0; i < targets.length; ++i) { + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallScheduled( + batchedOperationId, + i, + targets[i], + amounts[i], + payloads[i], + NO_PREDECESSOR, + MIN_DELAY + ); + } + timelockController.schedule_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT, + MIN_DELAY + ); + vm.stopPrank(); + + vm.warp(block.timestamp + MIN_DELAY - 2 days); + vm.expectRevert("TimelockController: operation is not ready"); + vm.prank(EXECUTOR_ONE); + timelockController.execute_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT + ); + } + + function testBatchPredecessorNotExecuted() public { + address[] memory targets = new address[](1); + targets[0] = target; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 0; + bytes32 slot = bytes32(uint256(1337)); + bytes32 value = bytes32(uint256(6699)); + bytes[] memory payloads = new bytes[](1); + payloads[0] = abi.encodeWithSelector( + callReceiverMock.mockFunctionWritesStorage.selector, + slot, + value + ); + bytes32 batchedOperationId1 = timelockController.hash_operation_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT + ); + bytes32 batchedOperationId2 = timelockController.hash_operation_batch( + targets, + amounts, + payloads, + batchedOperationId1, + SALT + ); + + vm.startPrank(PROPOSER_ONE); + for (uint256 i = 0; i < targets.length; ++i) { + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallScheduled( + batchedOperationId1, + i, + targets[i], + amounts[i], + payloads[i], + NO_PREDECESSOR, + MIN_DELAY + ); + } + timelockController.schedule_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT, + MIN_DELAY + ); + for (uint256 i = 0; i < targets.length; ++i) { + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallScheduled( + batchedOperationId2, + i, + targets[i], + amounts[i], + payloads[i], + batchedOperationId1, + MIN_DELAY + ); + } + timelockController.schedule_batch( + targets, + amounts, + payloads, + batchedOperationId1, + SALT, + MIN_DELAY + ); + vm.stopPrank(); + + vm.warp(block.timestamp + MIN_DELAY + 2 days); + vm.expectRevert("TimelockController: missing dependency"); + vm.prank(EXECUTOR_ONE); + timelockController.execute_batch( + targets, + amounts, + payloads, + batchedOperationId1, + SALT + ); + } + + function testBatchPredecessorNotScheduled() public { + address[] memory targets = new address[](1); + targets[0] = target; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 0; + bytes32 slot = bytes32(uint256(1337)); + bytes32 value = bytes32(uint256(6699)); + bytes[] memory payloads = new bytes[](1); + payloads[0] = abi.encodeWithSelector( + callReceiverMock.mockFunctionWritesStorage.selector, + slot, + value + ); + bytes32 batchedOperationId1 = timelockController.hash_operation_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT + ); + bytes32 batchedOperationId2 = timelockController.hash_operation_batch( + targets, + amounts, + payloads, + batchedOperationId1, + SALT + ); + + vm.startPrank(PROPOSER_ONE); + for (uint256 i = 0; i < targets.length; ++i) { + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallScheduled( + batchedOperationId2, + i, + targets[i], + amounts[i], + payloads[i], + batchedOperationId1, + MIN_DELAY + ); + } + timelockController.schedule_batch( + targets, + amounts, + payloads, + batchedOperationId1, + SALT, + MIN_DELAY + ); + vm.stopPrank(); + + vm.warp(block.timestamp + MIN_DELAY + 2 days); + vm.expectRevert("TimelockController: missing dependency"); + vm.prank(EXECUTOR_ONE); + timelockController.execute_batch( + targets, + amounts, + payloads, + batchedOperationId1, + SALT + ); + } + + function testBatchPredecessorInvalid() public { + address[] memory targets = new address[](1); + targets[0] = target; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 0; + bytes32 slot = bytes32(uint256(1337)); + bytes32 value = bytes32(uint256(6699)); + bytes[] memory payloads = new bytes[](1); + payloads[0] = abi.encodeWithSelector( + callReceiverMock.mockFunctionWritesStorage.selector, + slot, + value + ); + bytes32 invalidPredecessor = keccak256("Invalid Predecessor"); + bytes32 batchedOperationId = timelockController.hash_operation_batch( + targets, + amounts, + payloads, + invalidPredecessor, + EMPTY_SALT + ); + + vm.startPrank(PROPOSER_ONE); + for (uint256 i = 0; i < targets.length; ++i) { + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallScheduled( + batchedOperationId, + i, + targets[i], + amounts[i], + payloads[i], + invalidPredecessor, + MIN_DELAY + ); + } + timelockController.schedule_batch( + targets, + amounts, + payloads, + invalidPredecessor, + EMPTY_SALT, + MIN_DELAY + ); + vm.stopPrank(); + + vm.warp(block.timestamp + MIN_DELAY + 2 days); + vm.expectRevert("TimelockController: missing dependency"); + vm.prank(EXECUTOR_ONE); + timelockController.execute_batch( + targets, + amounts, + payloads, + invalidPredecessor, + EMPTY_SALT + ); + } + + function testBatchTargetRevert() public { + address[] memory targets = new address[](1); + targets[0] = target; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 0; + bytes[] memory payloads1 = new bytes[](1); + payloads1[0] = abi.encodeWithSelector( + callReceiverMock.mockFunctionRevertsWithReason.selector + ); + bytes[] memory payloads2 = new bytes[](1); + payloads2[0] = abi.encodeWithSelector( + callReceiverMock.mockFunctionRevertsWithEmptyReason.selector + ); + bytes32 batchedOperationId1 = timelockController.hash_operation_batch( + targets, + amounts, + payloads1, + NO_PREDECESSOR, + EMPTY_SALT + ); + bytes32 batchedOperationId2 = timelockController.hash_operation_batch( + targets, + amounts, + payloads2, + NO_PREDECESSOR, + SALT + ); + + vm.startPrank(PROPOSER_ONE); + for (uint256 i = 0; i < targets.length; ++i) { + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallScheduled( + batchedOperationId1, + i, + targets[i], + amounts[i], + payloads1[i], + NO_PREDECESSOR, + MIN_DELAY + ); + } + timelockController.schedule_batch( + targets, + amounts, + payloads1, + NO_PREDECESSOR, + EMPTY_SALT, + MIN_DELAY + ); + for (uint256 i = 0; i < targets.length; ++i) { + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallScheduled( + batchedOperationId2, + i, + targets[i], + amounts[i], + payloads2[i], + NO_PREDECESSOR, + MIN_DELAY + ); + } + timelockController.schedule_batch( + targets, + amounts, + payloads2, + NO_PREDECESSOR, + SALT, + MIN_DELAY + ); + vm.stopPrank(); + + vm.warp(block.timestamp + MIN_DELAY + 2 days); + vm.expectRevert("CallReceiverMock: reverting"); + vm.startPrank(EXECUTOR_ONE); + timelockController.execute_batch( + targets, + amounts, + payloads1, + NO_PREDECESSOR, + EMPTY_SALT + ); + vm.expectRevert("TimelockController: underlying transaction reverted"); + timelockController.execute_batch( + targets, + amounts, + payloads2, + NO_PREDECESSOR, + SALT + ); + vm.stopPrank(); + } + + function testBatchPredecessorMultipleNotExecuted() public { + address[] memory targets = new address[](1); + targets[0] = target; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 0; + bytes32 slot = bytes32(uint256(1337)); + bytes32 value = bytes32(uint256(6699)); + bytes[] memory payloads = new bytes[](1); + payloads[0] = abi.encodeWithSelector( + callReceiverMock.mockFunctionWritesStorage.selector, + slot, + value + ); + bytes32 batchedOperationId = timelockController.hash_operation_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT + ); + + vm.startPrank(PROPOSER_ONE); + for (uint256 i = 0; i < targets.length; ++i) { + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallScheduled( + batchedOperationId, + i, + targets[i], + amounts[i], + payloads[i], + NO_PREDECESSOR, + MIN_DELAY + ); + } + timelockController.schedule_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT, + MIN_DELAY + ); + + payloads[0] = abi.encodeWithSelector( + callReceiverMock.mockFunction.selector + ); + vm.startPrank(PROPOSER_ONE); + timelockController.schedule_batch( + targets, + amounts, + payloads, + batchedOperationId, + EMPTY_SALT, + MIN_DELAY + ); + vm.stopPrank(); + + vm.warp(block.timestamp + MIN_DELAY + 2 days); + vm.expectRevert("TimelockController: missing dependency"); + vm.prank(EXECUTOR_ONE); + timelockController.execute_batch( + targets, + amounts, + payloads, + batchedOperationId, + EMPTY_SALT + ); + } + + function testBatchCancelFinished() public { + address[] memory targets = new address[](1); + targets[0] = target; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 0; + bytes32 slot = bytes32(uint256(1337)); + bytes32 value = bytes32(uint256(6699)); + bytes[] memory payloads = new bytes[](1); + payloads[0] = abi.encodeWithSelector( + callReceiverMock.mockFunctionWritesStorage.selector, + slot, + value + ); + bytes32 batchedOperationId = timelockController.hash_operation_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT + ); + + vm.startPrank(PROPOSER_ONE); + for (uint256 i = 0; i < targets.length; ++i) { + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallScheduled( + batchedOperationId, + i, + targets[i], + amounts[i], + payloads[i], + NO_PREDECESSOR, + MIN_DELAY + ); + } + timelockController.schedule_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT, + MIN_DELAY + ); + vm.stopPrank(); + + vm.warp(block.timestamp + MIN_DELAY + 1); + vm.startPrank(EXECUTOR_ONE); + for (uint256 i = 0; i < targets.length; ++i) { + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallExecuted( + batchedOperationId, + i, + targets[i], + amounts[i], + payloads[i] + ); + } + timelockController.execute_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT + ); + vm.stopPrank(); + + vm.prank(PROPOSER_ONE); + vm.expectRevert("TimelockController: operation cannot be cancelled"); + timelockController.cancel(batchedOperationId); + } + + function testBatchPendingIfNotYetExecuted() public { + address[] memory targets = new address[](1); + targets[0] = target; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 0; + bytes32 slot = bytes32(uint256(1337)); + bytes32 value = bytes32(uint256(6699)); + bytes[] memory payloads = new bytes[](1); + payloads[0] = abi.encodeWithSelector( + callReceiverMock.mockFunctionWritesStorage.selector, + slot, + value + ); + bytes32 batchedOperationId = timelockController.hash_operation_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT + ); + + vm.startPrank(PROPOSER_ONE); + for (uint256 i = 0; i < targets.length; ++i) { + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallScheduled( + batchedOperationId, + i, + targets[i], + amounts[i], + payloads[i], + NO_PREDECESSOR, + MIN_DELAY + ); + } + timelockController.schedule_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT, + MIN_DELAY + ); + assertTrue(timelockController.is_operation_pending(batchedOperationId)); + vm.stopPrank(); + } + + function testBatchPendingIfExecuted() public { + address[] memory targets = new address[](1); + targets[0] = target; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 0; + bytes32 slot = bytes32(uint256(1337)); + bytes32 value = bytes32(uint256(6699)); + bytes[] memory payloads = new bytes[](1); + payloads[0] = abi.encodeWithSelector( + callReceiverMock.mockFunctionWritesStorage.selector, + slot, + value + ); + bytes32 batchedOperationId = timelockController.hash_operation_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT + ); + + vm.startPrank(PROPOSER_ONE); + for (uint256 i = 0; i < targets.length; ++i) { + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallScheduled( + batchedOperationId, + i, + targets[i], + amounts[i], + payloads[i], + NO_PREDECESSOR, + MIN_DELAY + ); + } + timelockController.schedule_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT, + MIN_DELAY + ); + vm.stopPrank(); + + vm.warp(block.timestamp + MIN_DELAY); + vm.startPrank(EXECUTOR_ONE); + for (uint256 i = 0; i < targets.length; ++i) { + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallExecuted( + batchedOperationId, + i, + targets[i], + amounts[i], + payloads[i] + ); + } + timelockController.execute_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT + ); + assertTrue( + !timelockController.is_operation_pending(batchedOperationId) + ); + vm.stopPrank(); + } + + function testBatchReadyOnTheExecutionTime() public { + address[] memory targets = new address[](1); + targets[0] = target; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 0; + bytes32 slot = bytes32(uint256(1337)); + bytes32 value = bytes32(uint256(6699)); + bytes[] memory payloads = new bytes[](1); + payloads[0] = abi.encodeWithSelector( + callReceiverMock.mockFunctionWritesStorage.selector, + slot, + value + ); + bytes32 batchedOperationId = timelockController.hash_operation_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT + ); + + vm.startPrank(PROPOSER_ONE); + for (uint256 i = 0; i < targets.length; ++i) { + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallScheduled( + batchedOperationId, + i, + targets[i], + amounts[i], + payloads[i], + NO_PREDECESSOR, + MIN_DELAY + ); + } + timelockController.schedule_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT, + MIN_DELAY + ); + vm.warp(block.timestamp + MIN_DELAY); + assertTrue(timelockController.is_operation_ready(batchedOperationId)); + vm.stopPrank(); + } + + function testBatchReadyAfterTheExecutionTime() public { + address[] memory targets = new address[](1); + targets[0] = target; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 0; + bytes32 slot = bytes32(uint256(1337)); + bytes32 value = bytes32(uint256(6699)); + bytes[] memory payloads = new bytes[](1); + payloads[0] = abi.encodeWithSelector( + callReceiverMock.mockFunctionWritesStorage.selector, + slot, + value + ); + bytes32 batchedOperationId = timelockController.hash_operation_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT + ); + + vm.startPrank(PROPOSER_ONE); + for (uint256 i = 0; i < targets.length; ++i) { + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallScheduled( + batchedOperationId, + i, + targets[i], + amounts[i], + payloads[i], + NO_PREDECESSOR, + MIN_DELAY + ); + } + timelockController.schedule_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT, + MIN_DELAY + ); + vm.warp(block.timestamp + MIN_DELAY + 1 days); + assertTrue(timelockController.is_operation_ready(batchedOperationId)); + vm.stopPrank(); + } + + function testBatchReadyBeforeTheExecutionTime() public { + address[] memory targets = new address[](1); + targets[0] = target; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 0; + bytes32 slot = bytes32(uint256(1337)); + bytes32 value = bytes32(uint256(6699)); + bytes[] memory payloads = new bytes[](1); + payloads[0] = abi.encodeWithSelector( + callReceiverMock.mockFunctionWritesStorage.selector, + slot, + value + ); + bytes32 batchedOperationId = timelockController.hash_operation_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT + ); + + vm.startPrank(PROPOSER_ONE); + for (uint256 i = 0; i < targets.length; ++i) { + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallScheduled( + batchedOperationId, + i, + targets[i], + amounts[i], + payloads[i], + NO_PREDECESSOR, + MIN_DELAY + ); + } + timelockController.schedule_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT, + MIN_DELAY + ); + vm.warp(block.timestamp + MIN_DELAY - 1 days); + assertTrue(!timelockController.is_operation_ready(batchedOperationId)); + vm.stopPrank(); + } + + function testBatchHasBeenExecuted() public { + address[] memory targets = new address[](1); + targets[0] = target; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 0; + bytes32 slot = bytes32(uint256(1337)); + bytes32 value = bytes32(uint256(6699)); + bytes[] memory payloads = new bytes[](1); + payloads[0] = abi.encodeWithSelector( + callReceiverMock.mockFunctionWritesStorage.selector, + slot, + value + ); + bytes32 batchedOperationId = timelockController.hash_operation_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT + ); + + vm.startPrank(PROPOSER_ONE); + for (uint256 i = 0; i < targets.length; ++i) { + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallScheduled( + batchedOperationId, + i, + targets[i], + amounts[i], + payloads[i], + NO_PREDECESSOR, + MIN_DELAY + ); + } + timelockController.schedule_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT, + MIN_DELAY + ); + vm.stopPrank(); + + vm.warp(block.timestamp + MIN_DELAY); + vm.startPrank(EXECUTOR_ONE); + for (uint256 i = 0; i < targets.length; ++i) { + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallExecuted( + batchedOperationId, + i, + targets[i], + amounts[i], + payloads[i] + ); + } + timelockController.execute_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT + ); + assertTrue(!timelockController.is_operation_ready(batchedOperationId)); + assertTrue(timelockController.is_operation_done(batchedOperationId)); + vm.stopPrank(); + } + + function testBatchHasNotBeenExecuted() public { + address[] memory targets = new address[](1); + targets[0] = target; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 0; + bytes32 slot = bytes32(uint256(1337)); + bytes32 value = bytes32(uint256(6699)); + bytes[] memory payloads = new bytes[](1); + payloads[0] = abi.encodeWithSelector( + callReceiverMock.mockFunctionWritesStorage.selector, + slot, + value + ); + bytes32 batchedOperationId = timelockController.hash_operation_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT + ); + + vm.startPrank(PROPOSER_ONE); + for (uint256 i = 0; i < targets.length; ++i) { + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallScheduled( + batchedOperationId, + i, + targets[i], + amounts[i], + payloads[i], + NO_PREDECESSOR, + MIN_DELAY + ); + } + timelockController.schedule_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT, + MIN_DELAY + ); + assertTrue(!timelockController.is_operation_done(batchedOperationId)); + vm.stopPrank(); + } + + function testBatchTimestampHasNotBeenExecuted() public { + address[] memory targets = new address[](1); + targets[0] = target; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 0; + bytes32 slot = bytes32(uint256(1337)); + bytes32 value = bytes32(uint256(6699)); + bytes[] memory payloads = new bytes[](1); + payloads[0] = abi.encodeWithSelector( + callReceiverMock.mockFunctionWritesStorage.selector, + slot, + value + ); + bytes32 batchedOperationId = timelockController.hash_operation_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT + ); + + vm.startPrank(PROPOSER_ONE); + for (uint256 i = 0; i < targets.length; ++i) { + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallScheduled( + batchedOperationId, + i, + targets[i], + amounts[i], + payloads[i], + NO_PREDECESSOR, + MIN_DELAY + ); + } + timelockController.schedule_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT, + MIN_DELAY + ); + uint256 operationTimestamp = timelockController.get_timestamp( + batchedOperationId + ); + assertEq(operationTimestamp, block.timestamp + MIN_DELAY); + vm.stopPrank(); + } + + function testBatchTimestampHasBeenExecuted() public { + address[] memory targets = new address[](1); + targets[0] = target; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 0; + bytes32 slot = bytes32(uint256(1337)); + bytes32 value = bytes32(uint256(6699)); + bytes[] memory payloads = new bytes[](1); + payloads[0] = abi.encodeWithSelector( + callReceiverMock.mockFunctionWritesStorage.selector, + slot, + value + ); + bytes32 batchedOperationId = timelockController.hash_operation_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT + ); + + vm.startPrank(PROPOSER_ONE); + for (uint256 i = 0; i < targets.length; ++i) { + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallScheduled( + batchedOperationId, + i, + targets[i], + amounts[i], + payloads[i], + NO_PREDECESSOR, + MIN_DELAY + ); + } + timelockController.schedule_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT, + MIN_DELAY + ); + vm.stopPrank(); + + vm.warp(block.timestamp + MIN_DELAY); + vm.startPrank(EXECUTOR_ONE); + for (uint256 i = 0; i < targets.length; ++i) { + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallExecuted( + batchedOperationId, + i, + targets[i], + amounts[i], + payloads[i] + ); + } + timelockController.execute_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT + ); + assertEq( + timelockController.get_timestamp(batchedOperationId), + DONE_TIMESTAMP + ); + vm.stopPrank(); + } + + function testReturnsLaterMinimumDelayForCalls() public { + uint256 newMinDelay = 31 days; + vm.startPrank(timelockControllerAddr); + vm.expectEmit(true, true, false, false); + emit ITimelockController.MinimumDelayChange( + timelockController.get_minimum_delay(), + newMinDelay + ); + timelockController.update_delay(newMinDelay); + vm.stopPrank(); + assertEq(timelockController.get_minimum_delay(), newMinDelay); + } + + function testInvalidOperation() public { + assertTrue(!timelockController.is_operation(keccak256("Invalid"))); + } + + function testRevertWhenNotTimelock() public { + vm.expectRevert("TimelockController: caller must be timelock"); + vm.prank(STRANGER); + timelockController.update_delay(3 days); + } + + function testAdminCannotBatchSchedule() public { + address[] memory targets = new address[](0); + uint256[] memory amounts = new uint256[](0); + bytes[] memory payloads = new bytes[](0); + vm.expectRevert("AccessControl: account is missing role"); + vm.prank(self); + timelockController.schedule_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT, + MIN_DELAY + ); + } + + function testAdminCannotSchedule() public { + vm.expectRevert("AccessControl: account is missing role"); + vm.prank(self); + timelockController.schedule( + zeroAddress, + 0, + new bytes(0), + NO_PREDECESSOR, + EMPTY_SALT, + MIN_DELAY + ); + } + + function testAdminCannotBatchExecute() public { + address[] memory targets = new address[](0); + uint256[] memory amounts = new uint256[](0); + bytes[] memory payloads = new bytes[](0); + + vm.expectRevert("AccessControl: account is missing role"); + vm.prank(self); + timelockController.execute_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT + ); + } + + function testAdminCannotExecute() public { + vm.expectRevert("AccessControl: account is missing role"); + vm.prank(self); + timelockController.execute( + zeroAddress, + 0, + new bytes(0), + NO_PREDECESSOR, + EMPTY_SALT + ); + } + + function testAdminCannotCancel() public { + vm.expectRevert("AccessControl: account is missing role"); + vm.prank(self); + timelockController.cancel(EMPTY_SALT); + } + + function testProposerCanBatchSchedule() public { + address[] memory targets = new address[](0); + uint256[] memory amounts = new uint256[](0); + bytes[] memory payloads = new bytes[](0); + vm.startPrank(PROPOSER_ONE); + timelockController.schedule_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT, + MIN_DELAY + ); + vm.stopPrank(); + + vm.prank(PROPOSER_TWO); + timelockController.schedule_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + SALT, + MIN_DELAY + ); + vm.stopPrank(); + } + + function testProposerCanSchedule() public { + vm.startPrank(PROPOSER_ONE); + timelockController.schedule( + zeroAddress, + 0, + new bytes(0), + NO_PREDECESSOR, + EMPTY_SALT, + MIN_DELAY + ); + vm.stopPrank(); + + vm.startPrank(PROPOSER_TWO); + timelockController.schedule( + zeroAddress, + 0, + new bytes(0), + NO_PREDECESSOR, + SALT, + MIN_DELAY + ); + vm.stopPrank(); + } + + function testProposerCannotBatchExecute() public { + address[] memory targets = new address[](0); + uint256[] memory amounts = new uint256[](0); + bytes[] memory payloads = new bytes[](0); + + vm.expectRevert("AccessControl: account is missing role"); + vm.prank(PROPOSER_ONE); + timelockController.execute_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT + ); + + vm.expectRevert("AccessControl: account is missing role"); + vm.prank(PROPOSER_TWO); + timelockController.execute_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + SALT + ); + } + + function testProposerCannotExecute() public { + vm.expectRevert("AccessControl: account is missing role"); + vm.prank(PROPOSER_ONE); + timelockController.execute( + zeroAddress, + 0, + new bytes(0), + NO_PREDECESSOR, + EMPTY_SALT + ); + + vm.expectRevert("AccessControl: account is missing role"); + vm.prank(PROPOSER_TWO); + timelockController.execute( + zeroAddress, + 0, + new bytes(0), + NO_PREDECESSOR, + SALT + ); + } + + function testProposerCanCancel() public { + vm.expectRevert("TimelockController: operation cannot be cancelled"); + vm.prank(PROPOSER_ONE); + timelockController.cancel(EMPTY_SALT); + + vm.expectRevert("TimelockController: operation cannot be cancelled"); + vm.prank(PROPOSER_TWO); + timelockController.cancel(EMPTY_SALT); + } + + function testExecutorCannotBatchSchedule() public { + address[] memory targets = new address[](0); + uint256[] memory amounts = new uint256[](0); + bytes[] memory payloads = new bytes[](0); + + vm.expectRevert("AccessControl: account is missing role"); + vm.prank(EXECUTOR_ONE); + timelockController.schedule_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT, + MIN_DELAY + ); + + vm.expectRevert("AccessControl: account is missing role"); + vm.prank(EXECUTOR_TWO); + timelockController.schedule_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + SALT, + MIN_DELAY + ); + } + + function testExecutorCannotSchedule() public { + vm.expectRevert("AccessControl: account is missing role"); + vm.prank(EXECUTOR_ONE); + timelockController.schedule( + zeroAddress, + 0, + new bytes(0), + NO_PREDECESSOR, + EMPTY_SALT, + MIN_DELAY + ); + + vm.expectRevert("AccessControl: account is missing role"); + vm.prank(EXECUTOR_TWO); + timelockController.schedule( + zeroAddress, + 0, + new bytes(0), + NO_PREDECESSOR, + SALT, + MIN_DELAY + ); + } + + function testExecutorCanBatchExecute() public { + address[] memory targets = new address[](0); + uint256[] memory amounts = new uint256[](0); + bytes[] memory payloads = new bytes[](0); + + vm.expectRevert("TimelockController: operation is not ready"); + vm.prank(EXECUTOR_ONE); + timelockController.execute_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT + ); + + vm.expectRevert("TimelockController: operation is not ready"); + vm.prank(EXECUTOR_TWO); + timelockController.execute_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + SALT + ); + } + + function testExecutorCanExecute() public { + vm.expectRevert("TimelockController: operation is not ready"); + vm.prank(EXECUTOR_ONE); + timelockController.execute( + zeroAddress, + 0, + new bytes(0), + NO_PREDECESSOR, + EMPTY_SALT + ); + + vm.expectRevert("TimelockController: operation is not ready"); + vm.prank(EXECUTOR_TWO); + timelockController.execute( + zeroAddress, + 0, + new bytes(0), + NO_PREDECESSOR, + SALT + ); + } + + function testExecutorCannotCancel() public { + vm.expectRevert("AccessControl: account is missing role"); + vm.prank(EXECUTOR_ONE); + timelockController.cancel(EMPTY_SALT); + + vm.expectRevert("AccessControl: account is missing role"); + vm.prank(EXECUTOR_TWO); + timelockController.cancel(EMPTY_SALT); + } + + function testStrangerCannotBatchSchedule() public { + address[] memory targets = new address[](0); + uint256[] memory amounts = new uint256[](0); + bytes[] memory payloads = new bytes[](0); + + vm.expectRevert("AccessControl: account is missing role"); + vm.prank(STRANGER); + timelockController.schedule_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT, + MIN_DELAY + ); + } + + function testStrangerCannotSchedule() public { + vm.expectRevert("AccessControl: account is missing role"); + vm.prank(STRANGER); + timelockController.schedule( + zeroAddress, + 0, + new bytes(0), + NO_PREDECESSOR, + EMPTY_SALT, + MIN_DELAY + ); + } + + function testStrangerCannotBatchExecute() public { + address[] memory targets = new address[](0); + uint256[] memory amounts = new uint256[](0); + bytes[] memory payloads = new bytes[](0); + + vm.expectRevert("AccessControl: account is missing role"); + vm.prank(STRANGER); + timelockController.execute_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT + ); + } + + function testStrangerCannotExecute() public { + vm.expectRevert("AccessControl: account is missing role"); + vm.prank(STRANGER); + timelockController.execute( + zeroAddress, + 0, + new bytes(0), + NO_PREDECESSOR, + EMPTY_SALT + ); + } + + function testStrangerCannotCancel() public { + vm.expectRevert("AccessControl: account is missing role"); + vm.prank(STRANGER); + timelockController.cancel(EMPTY_SALT); + } + + function testCancellerCanCancelOperation() public { + address[] memory targets = new address[](1); + targets[0] = target; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 0; + bytes32 slot = bytes32(uint256(1337)); + bytes32 value = bytes32(uint256(6699)); + bytes[] memory payloads = new bytes[](1); + payloads[0] = abi.encodeWithSelector( + callReceiverMock.mockFunctionWritesStorage.selector, + slot, + value + ); + bytes32 batchedOperationId = timelockController.hash_operation_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT + ); + + vm.startPrank(PROPOSER_ONE); + for (uint256 i = 0; i < targets.length; ++i) { + vm.expectEmit(); + emit ITimelockController.CallScheduled( + batchedOperationId, + i, + targets[i], + amounts[i], + payloads[i], + NO_PREDECESSOR, + MIN_DELAY + ); + } + timelockController.schedule_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT, + MIN_DELAY + ); + vm.expectEmit(true, false, false, false); + emit ITimelockController.Cancelled(batchedOperationId); + timelockController.cancel(batchedOperationId); + assertTrue(!timelockController.is_operation(batchedOperationId)); + vm.stopPrank(); + } + + function testCompletePipelineOperationSetRoleAdmin() public { + uint256 amount = 0; + bytes memory payload = abi.encodeWithSelector( + timelockController.set_role_admin.selector, + EXECUTOR_ROLE, + PROPOSER_ROLE + ); + bytes32 operationId = timelockController.hash_operation( + timelockControllerAddr, + amount, + payload, + NO_PREDECESSOR, + EMPTY_SALT + ); + + vm.startPrank(PROPOSER_ONE); + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallScheduled( + operationId, + 0, + timelockControllerAddr, + amount, + payload, + NO_PREDECESSOR, + MIN_DELAY + ); + timelockController.schedule( + timelockControllerAddr, + amount, + payload, + NO_PREDECESSOR, + EMPTY_SALT, + MIN_DELAY + ); + vm.stopPrank(); + + vm.warp(block.timestamp + MIN_DELAY); + vm.startPrank(EXECUTOR_ONE); + vm.expectEmit(true, true, true, false); + emit IAccessControl.RoleAdminChanged( + EXECUTOR_ROLE, + DEFAULT_ADMIN_ROLE, + PROPOSER_ROLE + ); + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallExecuted( + operationId, + 0, + timelockControllerAddr, + amount, + payload + ); + timelockController.execute( + timelockControllerAddr, + amount, + payload, + NO_PREDECESSOR, + EMPTY_SALT + ); + assertEq(timelockController.getRoleAdmin(EXECUTOR_ROLE), PROPOSER_ROLE); + assertTrue( + timelockController.getRoleAdmin(EXECUTOR_ROLE) != DEFAULT_ADMIN_ROLE + ); + vm.stopPrank(); + } + + function testCompleteOperationWithAssignExecutorRoleToZeroAddress() public { + vm.startPrank(timelockControllerAddr); + timelockController.grantRole(EXECUTOR_ROLE, zeroAddress); + vm.stopPrank(); + + uint256 amount = 0; + bytes memory payload = abi.encodeWithSelector( + timelockController.set_role_admin.selector, + EXECUTOR_ROLE, + PROPOSER_ROLE + ); + bytes32 operationId = timelockController.hash_operation( + timelockControllerAddr, + amount, + payload, + NO_PREDECESSOR, + EMPTY_SALT + ); + + vm.startPrank(PROPOSER_ONE); + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallScheduled( + operationId, + 0, + timelockControllerAddr, + amount, + payload, + NO_PREDECESSOR, + MIN_DELAY + ); + timelockController.schedule( + timelockControllerAddr, + amount, + payload, + NO_PREDECESSOR, + EMPTY_SALT, + MIN_DELAY + ); + vm.stopPrank(); + + vm.warp(block.timestamp + MIN_DELAY); + vm.startPrank(STRANGER); + vm.expectEmit(true, true, true, false); + emit IAccessControl.RoleAdminChanged( + EXECUTOR_ROLE, + DEFAULT_ADMIN_ROLE, + PROPOSER_ROLE + ); + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallExecuted( + operationId, + 0, + timelockControllerAddr, + amount, + payload + ); + timelockController.execute( + timelockControllerAddr, + amount, + payload, + NO_PREDECESSOR, + EMPTY_SALT + ); + assertEq(timelockController.getRoleAdmin(EXECUTOR_ROLE), PROPOSER_ROLE); + assertTrue( + timelockController.getRoleAdmin(EXECUTOR_ROLE) != DEFAULT_ADMIN_ROLE + ); + vm.stopPrank(); + } + + function testHandleERC721() public { + bytes memory args = abi.encode( + "MyNFT", + "WAGMI", + "https://www.wagmi.xyz/", + "MyNFT", + "1" + ); + erc721Mock = IERC721Extended( + vyperDeployer.deployContract("src/tokens/", "ERC721", args) + ); + vm.startPrank(deployer); + erc721Mock.safe_mint(timelockControllerAddr, "my_awesome_nft_uri_1"); + assertEq(erc721Mock.balanceOf(timelockControllerAddr), 1); + vm.stopPrank(); + + address[] memory targets = new address[](1); + targets[0] = address(erc721Mock); + uint256[] memory amounts = new uint256[](1); + amounts[0] = 0; + bytes[] memory payloads = new bytes[](1); + payloads[0] = abi.encodeWithSelector(erc721Mock.burn.selector, 0); + bytes32 batchedOperationId = timelockController.hash_operation_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT + ); + + vm.startPrank(PROPOSER_ONE); + for (uint256 i = 0; i < targets.length; ++i) { + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallScheduled( + batchedOperationId, + i, + targets[i], + amounts[i], + payloads[i], + NO_PREDECESSOR, + MIN_DELAY + ); + } + timelockController.schedule_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT, + MIN_DELAY + ); + vm.stopPrank(); + + vm.warp(block.timestamp + MIN_DELAY); + vm.startPrank(EXECUTOR_ONE); + for (uint256 i = 0; i < targets.length; ++i) { + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallExecuted( + batchedOperationId, + i, + targets[i], + amounts[i], + payloads[i] + ); + } + timelockController.execute_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT + ); + assertEq( + timelockController.get_timestamp(batchedOperationId), + DONE_TIMESTAMP + ); + assertEq(erc721Mock.balanceOf(timelockControllerAddr), 0); + vm.stopPrank(); + } + + function testHandleERC1155() public { + bytes memory args = abi.encode("https://www.wagmi.xyz/"); + erc1155Mock = IERC1155Extended( + vyperDeployer.deployContract("src/tokens/", "ERC1155", args) + ); + uint256[] memory ids = new uint256[](2); + uint256[] memory tokenAmounts = new uint256[](2); + ids[0] = 1; + ids[1] = 2; + tokenAmounts[0] = 1; + tokenAmounts[1] = 2; + + vm.startPrank(deployer); + erc1155Mock.safe_mint(timelockControllerAddr, 0, 1, new bytes(0)); + erc1155Mock.safe_mint_batch( + timelockControllerAddr, + ids, + tokenAmounts, + new bytes(0) + ); + assertEq(erc1155Mock.balanceOf(timelockControllerAddr, 0), 1); + assertEq(erc1155Mock.balanceOf(timelockControllerAddr, 1), 1); + assertEq(erc1155Mock.balanceOf(timelockControllerAddr, 2), 2); + vm.stopPrank(); + + address[] memory targets = new address[](1); + targets[0] = address(erc1155Mock); + uint256[] memory amounts = new uint256[](1); + amounts[0] = 0; + bytes[] memory payloads = new bytes[](1); + payloads[0] = abi.encodeWithSelector( + erc1155Mock.burn.selector, + timelockControllerAddr, + 0, + 1 + ); + bytes32 batchedOperationId = timelockController.hash_operation_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT + ); + + vm.startPrank(PROPOSER_ONE); + for (uint256 i = 0; i < targets.length; ++i) { + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallScheduled( + batchedOperationId, + i, + targets[i], + amounts[i], + payloads[i], + NO_PREDECESSOR, + MIN_DELAY + ); + } + timelockController.schedule_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT, + MIN_DELAY + ); + vm.stopPrank(); + + vm.warp(block.timestamp + MIN_DELAY); + vm.startPrank(EXECUTOR_ONE); + for (uint256 i = 0; i < targets.length; ++i) { + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallExecuted( + batchedOperationId, + i, + targets[i], + amounts[i], + payloads[i] + ); + } + timelockController.execute_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT + ); + assertEq( + timelockController.get_timestamp(batchedOperationId), + DONE_TIMESTAMP + ); + assertEq(erc1155Mock.balanceOf(timelockControllerAddr, 0), 0); + vm.stopPrank(); + } + + function testFuzzHashOperation( + address target_, + uint256 amount, + bytes memory payload, + bytes32 predecessor, + bytes32 salt + ) public { + bytes32 operationId = timelockController.hash_operation( + target_, + amount, + payload, + predecessor, + salt + ); + assertEq( + operationId, + keccak256(abi.encode(target_, amount, payload, predecessor, salt)) + ); + } + + function testFuzzOperationValue(uint256 amount) public { + amount = bound(amount, 0, type(uint64).max); + bytes32 slot = bytes32(uint256(1337)); + bytes32 value = bytes32(uint256(6699)); + bytes memory payload = abi.encodeWithSelector( + callReceiverMock.mockFunctionWritesStorage.selector, + slot, + value + ); + bytes32 operationId = timelockController.hash_operation( + target, + amount, + payload, + NO_PREDECESSOR, + EMPTY_SALT + ); + deal(timelockControllerAddr, amount); + + vm.startPrank(PROPOSER_ONE); + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallScheduled( + operationId, + 0, + target, + amount, + payload, + NO_PREDECESSOR, + MIN_DELAY + ); + timelockController.schedule( + target, + amount, + payload, + NO_PREDECESSOR, + EMPTY_SALT, + MIN_DELAY + ); + vm.stopPrank(); + + vm.warp(block.timestamp + MIN_DELAY); + vm.startPrank(EXECUTOR_ONE); + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallExecuted( + operationId, + 0, + target, + amount, + payload + ); + timelockController.execute( + target, + amount, + payload, + NO_PREDECESSOR, + EMPTY_SALT + ); + assertEq(timelockController.get_timestamp(operationId), DONE_TIMESTAMP); + assertEq(address(callReceiverMock).balance, amount); + vm.stopPrank(); + } + + function testFuzzHashOperationBatch( + address[] memory targets_, + uint256[] memory amounts, + bytes[] memory payloads, + bytes32 predecessor, + bytes32 salt + ) public { + bytes32 batchedOperationId = timelockController.hash_operation_batch( + targets_, + amounts, + payloads, + predecessor, + salt + ); + assertEq( + batchedOperationId, + keccak256( + abi.encode(targets_, amounts, payloads, predecessor, salt) + ) + ); + } + + function testFuzzBatchValue(uint256 amount) public { + amount = bound(amount, 0, type(uint64).max); + address[] memory targets = new address[](1); + targets[0] = target; + uint256[] memory amounts = new uint256[](1); + amounts[0] = amount; + bytes32 slot = bytes32(uint256(1337)); + bytes32 value = bytes32(uint256(6699)); + bytes[] memory payloads = new bytes[](1); + payloads[0] = abi.encodeWithSelector( + callReceiverMock.mockFunctionWritesStorage.selector, + slot, + value + ); + bytes32 batchedOperationId = timelockController.hash_operation_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT + ); + deal(timelockControllerAddr, amount); + + vm.startPrank(PROPOSER_ONE); + for (uint256 i = 0; i < targets.length; ++i) { + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallScheduled( + batchedOperationId, + i, + targets[i], + amounts[i], + payloads[i], + NO_PREDECESSOR, + MIN_DELAY + ); + } + timelockController.schedule_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT, + MIN_DELAY + ); + vm.stopPrank(); + + vm.warp(block.timestamp + MIN_DELAY); + vm.startPrank(EXECUTOR_ONE); + for (uint256 i = 0; i < targets.length; ++i) { + vm.expectEmit(true, true, false, true); + emit ITimelockController.CallExecuted( + batchedOperationId, + i, + targets[i], + amounts[i], + payloads[i] + ); + } + timelockController.execute_batch( + targets, + amounts, + payloads, + NO_PREDECESSOR, + EMPTY_SALT + ); + assertEq( + timelockController.get_timestamp(batchedOperationId), + DONE_TIMESTAMP + ); + assertEq(address(callReceiverMock).balance, amounts[0]); + vm.stopPrank(); + } +} + +contract TimelockControllerInvariants is Test { + VyperDeployer private vyperDeployer = new VyperDeployer(); + + ITimelockController private timelockController; + TimelockControllerHandler private timelockControllerHandler; + + address private self = address(this); + address private timelockControllerAddr; + + function setUp() public { + address[] memory proposers = new address[](1); + proposers[0] = self; + address[] memory executors = new address[](1); + executors[0] = self; + uint256 minDelay = 2 days; + bytes memory args = abi.encode(minDelay, proposers, executors, self); + timelockController = ITimelockController( + vyperDeployer.deployContract( + "src/governance/", + "TimelockController", + args + ) + ); + timelockControllerHandler = new TimelockControllerHandler( + timelockController, + minDelay, + proposers, + executors, + self + ); + timelockControllerAddr = address(timelockControllerHandler); + + bytes4[] memory selectors = new bytes4[](3); + selectors[0] = TimelockControllerHandler.schedule.selector; + selectors[1] = TimelockControllerHandler.execute.selector; + selectors[2] = TimelockControllerHandler.cancel.selector; + targetSelector( + FuzzSelector({addr: timelockControllerAddr, selectors: selectors}) + ); + targetContract(timelockControllerAddr); + } + + /** + * @dev The number of scheduled transactions cannot exceed the number of + * executed transactions. + */ + function invariantExecutedLessThanOrEqualToScheduled() public { + assertTrue( + timelockControllerHandler.executeCount() <= + timelockControllerHandler.scheduleCount() + ); + } + + /** + * @dev The number of proposals executed must match the count number. + */ + function invariantProposalsExecutedMatchCount() public { + assertEq( + timelockControllerHandler.executeCount(), + timelockControllerHandler.counter() + ); + } + + /** + * @dev Proposals can only be scheduled and executed once. + */ + function invariantOnceProposalExecution() public { + uint256[] memory executed = timelockControllerHandler.getExecuted(); + for (uint256 i = 0; i < executed.length; ++i) { + // Ensure that the executed proposal cannot be executed again. + vm.expectRevert("TimelockController: operation is not ready"); + timelockController.execute( + timelockControllerAddr, + 0, + abi.encodeWithSelector( + TimelockControllerHandler.increment.selector + ), + bytes32(""), + bytes32(executed[i]) + ); + } + } + + /** + * @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 { + assertTrue( + (timelockControllerHandler.cancelCount() + + timelockControllerHandler.executeCount()) <= + timelockControllerHandler.scheduleCount() + ); + } + + /** + * @dev The executed proposals cannot be cancelled. + */ + function invariantExecutedProposalCancellation() public { + uint256[] memory executed = timelockControllerHandler.getExecuted(); + for (uint256 i = 0; i < executed.length; ++i) { + // Ensure that the executed proposal cannot be cancelled. + vm.expectRevert( + "TimelockController: operation cannot be cancelled" + ); + timelockController.cancel(bytes32(executed[i])); + } + } + + /** + * @dev The execution of a proposal that has been cancelled is not possible. + */ + function invariantExecutingCancelledProposal() public { + uint256[] memory cancelled = timelockControllerHandler.getCancelled(); + for (uint256 i = 0; i < cancelled.length; ++i) { + // Ensure that the cancelled proposal cannot be executed. + vm.expectRevert("TimelockController: operation is not ready"); + timelockController.execute( + address(timelockControllerHandler), + 0, + abi.encodeWithSelector( + TimelockControllerHandler.increment.selector + ), + bytes32(""), + bytes32(cancelled[i]) + ); + } + } + + /** + * @dev The execution of a proposal that is not ready is not possible. + */ + function invariantExecutingNotReadyProposal() public { + uint256[] memory pending = timelockControllerHandler.getPending(); + for (uint256 i = 0; i < pending.length; ++i) { + // Ensure that the pending proposal cannot be executed. + vm.expectRevert("TimelockController: operation is not ready"); + timelockController.execute( + address(timelockControllerHandler), + 0, + abi.encodeWithSelector( + TimelockControllerHandler.increment.selector + ), + bytes32(""), + bytes32(pending[i]) + ); + } + } +} + +contract TimelockControllerHandler is Test { + uint256 public counter; + uint256 public scheduleCount; + uint256 public executeCount; + uint256 public cancelCount; + + ITimelockController private timelockController; + + address private self = address(this); + uint256 private minDelay; + address private admin; + address private proposer; + address private executor; + uint256[] private pending; + uint256[] private executed; + uint256[] private cancelled; + + constructor( + ITimelockController timelockController_, + uint256 minDelay_, + address[] memory proposer_, + address[] memory executor_, + address admin_ + ) { + timelockController = timelockController_; + minDelay = minDelay_; + proposer = proposer_[0]; + executor = executor_[0]; + admin = admin_; + } + + function schedule(uint256 random) external { + vm.startPrank(proposer); + timelockController.schedule( + self, + 0, + abi.encodeWithSelector(this.increment.selector), + bytes32(""), + bytes32(random), + minDelay + ); + pending.push(random); + scheduleCount++; + vm.stopPrank(); + } + + function execute(uint256 random) external { + if (pending.length == 0 || scheduleCount == 0) { + return; + } + + uint256 identifier = random % pending.length; + uint256 operation = pending[identifier]; + + // Advance the time to make the proposal ready. + vm.warp(block.timestamp + minDelay); + vm.startPrank(executor); + timelockController.execute( + self, + 0, + abi.encodeWithSelector(this.increment.selector), + bytes32(""), + bytes32(operation) + ); + delete pending[identifier]; + executed.push(operation); + executeCount++; + vm.stopPrank(); + } + + function cancel(uint256 random) external { + if (pending.length == 0 || scheduleCount == 0) { + return; + } + + uint256 identifier = random % pending.length; + uint256 operation = pending[identifier]; + + vm.startPrank(proposer); + timelockController.cancel(bytes32(operation)); + delete pending[identifier]; + cancelled.push(operation); + cancelCount++; + vm.stopPrank(); + } + + function getExecuted() external view returns (uint256[] memory) { + return executed; + } + + function getCancelled() external view returns (uint256[] memory) { + return cancelled; + } + + function getPending() external view returns (uint256[] memory) { + return pending; + } + + function increment() external { + counter++; + } +} diff --git a/test/governance/interfaces/ITimelockController.sol b/test/governance/interfaces/ITimelockController.sol new file mode 100644 index 00000000..da07566b --- /dev/null +++ b/test/governance/interfaces/ITimelockController.sol @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: WTFPL +pragma solidity ^0.8.23; + +import {IERC721Receiver} from "openzeppelin/token/ERC721/IERC721Receiver.sol"; +import {IERC1155Receiver} from "openzeppelin/token/ERC1155/IERC1155Receiver.sol"; +import {IAccessControl} from "openzeppelin/access/IAccessControl.sol"; + +interface ITimelockController is + IERC721Receiver, + IERC1155Receiver, + IAccessControl +{ + event CallScheduled( + bytes32 indexed id, + uint256 indexed index, + address target, + uint256 amount, + bytes payload, + bytes32 predecessor, + uint256 delay + ); + + event CallExecuted( + bytes32 indexed id, + uint256 indexed index, + address target, + uint256 amount, + bytes payload + ); + + event CallSalt(bytes32 indexed id, bytes32 salt); + + event Cancelled(bytes32 indexed id); + + event MinimumDelayChange(uint256 oldDuration, uint256 newDuration); + + function DEFAULT_ADMIN_ROLE() external pure returns (bytes32); + + function PROPOSER_ROLE() external pure returns (bytes32); + + function EXECUTOR_ROLE() external pure returns (bytes32); + + function CANCELLER_ROLE() external pure returns (bytes32); + + function IERC721_TOKENRECEIVER_SELECTOR() external pure returns (bytes4); + + function IERC1155_TOKENRECEIVER_SINGLE_SELECTOR() + external + pure + returns (bytes4); + + function IERC1155_TOKENRECEIVER_BATCH_SELECTOR() + external + pure + returns (bytes4); + + function get_timestamp(bytes32 id) external view returns (uint256); + + function get_minimum_delay() external view returns (uint256); + + function is_operation(bytes32 id) external view returns (bool); + + function is_operation_pending(bytes32 id) external view returns (bool); + + function is_operation_ready(bytes32 id) external view returns (bool); + + function is_operation_done(bytes32 id) external view returns (bool); + + /** + * @dev As Enums are handled differently in Vyper and Solidity (see https://twitter.com/pcaversaccio/status/1626514029094047747), + * we directly return the underlying Vyper type `uint256` (instead of `OperationState`) + * for Enums for ease of testing. + */ + function get_operation_state(bytes32 id) external view returns (uint256); + + function hash_operation( + address target, + uint256 amount, + bytes calldata payload, + bytes32 predecessor, + bytes32 salt + ) external pure returns (bytes32 hash); + + function hash_operation_batch( + address[] calldata targets, + uint256[] calldata amounts, + bytes[] calldata payloads, + bytes32 predecessor, + bytes32 salt + ) external pure returns (bytes32 hash); + + function schedule( + address target, + uint256 amount, + bytes calldata payload, + bytes32 predecessor, + bytes32 salt, + uint256 delay + ) external; + + function schedule_batch( + address[] calldata targets, + uint256[] calldata amounts, + bytes[] calldata payloads, + bytes32 predecessor, + bytes32 salt, + uint256 delay + ) external; + + function cancel(bytes32 id) external; + + function execute( + address target, + uint256 amount, + bytes calldata payload, + bytes32 predecessor, + bytes32 salt + ) external payable; + + function execute_batch( + address[] calldata targets, + uint256[] calldata amounts, + bytes[] calldata payloads, + bytes32 predecessor, + bytes32 salt + ) external payable; + + function update_delay(uint256 newDelay) external; + + function set_role_admin(bytes32 role, bytes32 adminRole) external; +} diff --git a/test/governance/mocks/CallReceiverMock.sol b/test/governance/mocks/CallReceiverMock.sol new file mode 100644 index 00000000..c6be49e3 --- /dev/null +++ b/test/governance/mocks/CallReceiverMock.sol @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +/** + * @title CallReceiverMock + * @author pcaversaccio + * @notice Forked and adjusted accordingly from here: + * https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/mocks/CallReceiverMock.sol. + * @dev Allows to test receiving state-changing and/or static external calls. + */ +contract CallReceiverMock { + event MockFunctionCalled(); + event MockFunctionCalledWithArgs(uint256 a, uint256 b); + + string private _retValue = "0xba5ed"; + + /** + * @dev Emits `MockFunctionCalled` and returns the string `0xba5ed` + * after function invocation. + * @return string The return string `0xba5ed`. + */ + function mockFunction() public payable returns (string memory) { + emit MockFunctionCalled(); + return _retValue; + } + + /** + * @dev Emits `MockFunctionCalled` after function invocation. + */ + function mockFunctionEmptyReturn() public payable { + emit MockFunctionCalled(); + } + + /** + * @dev Emits `MockFunctionCalledWithArgs` and returns the string `0xba5ed` + * after function invocation. + * @param a The first 32-byte function argument. + * @param b The second 32-byte function argument. + * @return string The return string `0xba5ed`. + */ + function mockFunctionWithArgs( + uint256 a, + uint256 b + ) public payable returns (string memory) { + emit MockFunctionCalledWithArgs(a, b); + return _retValue; + } + + /** + * @dev Emits `MockFunctionCalled` and returns the string `0xba5ed` + * after function invocation. + * @notice `payable` function calls will revert. + * @return string The return string `0xba5ed`. + */ + function mockFunctionNonPayable() public returns (string memory) { + emit MockFunctionCalled(); + return _retValue; + } + + /** + * @dev Returns the string `0xba5ed` after function invocation. + * @notice Special function to mock `STATICCALL` calls. + * @return string The return string `0xba5ed`. + */ + function mockStaticFunction() public view returns (string memory) { + return _retValue; + } + + /** + * @dev Reverts with an empty reason. + */ + function mockFunctionRevertsWithEmptyReason() public payable { + // solhint-disable-next-line reason-string, custom-errors + revert(); + } + + /** + * @dev Reverts with a non-empty reason. + */ + function mockFunctionRevertsWithReason() public payable { + // solhint-disable-next-line custom-errors + revert("CallReceiverMock: reverting"); + } + + /** + * @dev Returns the string `0xba5ed` after function invocation. + * @param slot The 32-byte key in storage. + * @param value The 32-byte value to store at `slot`. + * @notice Writes to storage slot `slot` with value `value`. + * @return string The return string `0xba5ed`. + */ + function mockFunctionWritesStorage( + bytes32 slot, + bytes32 value + ) public payable returns (string memory) { + // solhint-disable-next-line no-inline-assembly + assembly { + sstore(slot, value) + } + return _retValue; + } +} diff --git a/test/tokens/ERC20.t.sol b/test/tokens/ERC20.t.sol index 56e02b2d..0c3f3a21 100644 --- a/test/tokens/ERC20.t.sol +++ b/test/tokens/ERC20.t.sol @@ -575,7 +575,6 @@ contract ERC20Test is Test { address spender = makeAddr("spender"); uint256 amount = 100; uint256 nonce = ERC20Extended.nonces(owner); - // solhint-disable-next-line not-rely-on-time uint256 deadline = block.timestamp + 100_000; bytes32 domainSeparator = ERC20Extended.DOMAIN_SEPARATOR(); (uint8 v, bytes32 r, bytes32 s) = vm.sign( @@ -609,7 +608,6 @@ contract ERC20Test is Test { address spender = makeAddr("spender"); uint256 amount = 100; uint256 nonce = ERC20Extended.nonces(owner); - // solhint-disable-next-line not-rely-on-time uint256 deadline = block.timestamp + 100_000; bytes32 domainSeparator = ERC20Extended.DOMAIN_SEPARATOR(); (uint8 v, bytes32 r, bytes32 s) = vm.sign( @@ -643,7 +641,6 @@ contract ERC20Test is Test { address spender = makeAddr("spender"); uint256 amount = 100; uint256 nonce = ERC20Extended.nonces(owner); - // solhint-disable-next-line not-rely-on-time uint256 deadline = block.timestamp + 100_000; bytes32 domainSeparator = ERC20Extended.DOMAIN_SEPARATOR(); (uint8 v, bytes32 r, bytes32 s) = vm.sign( @@ -674,7 +671,6 @@ contract ERC20Test is Test { address spender = makeAddr("spender"); uint256 amount = 100; uint256 nonce = ERC20Extended.nonces(owner); - // solhint-disable-next-line not-rely-on-time uint256 deadline = block.timestamp + 100_000; bytes32 domainSeparator = keccak256( abi.encode( @@ -713,7 +709,6 @@ contract ERC20Test is Test { address spender = makeAddr("spender"); uint256 amount = 100; uint256 nonce = 1; - // solhint-disable-next-line not-rely-on-time uint256 deadline = block.timestamp + 100_000; bytes32 domainSeparator = ERC20Extended.DOMAIN_SEPARATOR(); (uint8 v, bytes32 r, bytes32 s) = vm.sign( @@ -744,7 +739,6 @@ contract ERC20Test is Test { address spender = makeAddr("spender"); uint256 amount = 100; uint256 nonce = ERC20Extended.nonces(owner); - // solhint-disable-next-line not-rely-on-time uint256 deadline = block.timestamp - 1; bytes32 domainSeparator = ERC20Extended.DOMAIN_SEPARATOR(); (uint8 v, bytes32 r, bytes32 s) = vm.sign( @@ -1076,7 +1070,6 @@ contract ERC20Test is Test { address spenderAddr = makeAddr(spender); uint256 amount = block.number; uint256 nonce = ERC20Extended.nonces(ownerAddr); - // solhint-disable-next-line not-rely-on-time uint256 deadline = block.timestamp + increment; bytes32 domainSeparator = ERC20Extended.DOMAIN_SEPARATOR(); (uint8 v, bytes32 r, bytes32 s) = vm.sign( @@ -1118,7 +1111,6 @@ contract ERC20Test is Test { address spenderAddr = makeAddr(spender); uint256 amount = block.number; uint256 nonce = ERC20Extended.nonces(ownerAddr); - // solhint-disable-next-line not-rely-on-time uint256 deadline = block.timestamp + increment; bytes32 domainSeparator = ERC20Extended.DOMAIN_SEPARATOR(); (uint8 v, bytes32 r, bytes32 s) = vm.sign( diff --git a/test/tokens/ERC721.t.sol b/test/tokens/ERC721.t.sol index b1e69816..e47f9fa2 100644 --- a/test/tokens/ERC721.t.sol +++ b/test/tokens/ERC721.t.sol @@ -1629,7 +1629,6 @@ contract ERC721Test is Test { vm.stopPrank(); uint256 nonce = ERC721Extended.nonces(tokenId); - // solhint-disable-next-line not-rely-on-time uint256 deadline = block.timestamp + 100_000; bytes32 domainSeparator = ERC721Extended.DOMAIN_SEPARATOR(); (uint8 v, bytes32 r, bytes32 s) = vm.sign( @@ -1667,7 +1666,6 @@ contract ERC721Test is Test { vm.stopPrank(); uint256 nonce = ERC721Extended.nonces(tokenId); - // solhint-disable-next-line not-rely-on-time uint256 deadline = block.timestamp + 100_000; bytes32 domainSeparator = ERC721Extended.DOMAIN_SEPARATOR(); (uint8 v, bytes32 r, bytes32 s) = vm.sign( @@ -1705,7 +1703,6 @@ contract ERC721Test is Test { vm.stopPrank(); uint256 nonce = ERC721Extended.nonces(tokenId); - // solhint-disable-next-line not-rely-on-time uint256 deadline = block.timestamp + 100_000; bytes32 domainSeparator = ERC721Extended.DOMAIN_SEPARATOR(); (uint8 v, bytes32 r, bytes32 s) = vm.sign( @@ -1740,7 +1737,6 @@ contract ERC721Test is Test { vm.stopPrank(); uint256 nonce = ERC721Extended.nonces(tokenId); - // solhint-disable-next-line not-rely-on-time uint256 deadline = block.timestamp + 100_000; bytes32 domainSeparator = keccak256( abi.encode( @@ -1783,7 +1779,6 @@ contract ERC721Test is Test { vm.stopPrank(); uint256 nonce = 1; - // solhint-disable-next-line not-rely-on-time uint256 deadline = block.timestamp + 100_000; bytes32 domainSeparator = ERC721Extended.DOMAIN_SEPARATOR(); (uint8 v, bytes32 r, bytes32 s) = vm.sign( @@ -1818,7 +1813,6 @@ contract ERC721Test is Test { vm.stopPrank(); uint256 nonce = 1; - // solhint-disable-next-line not-rely-on-time uint256 deadline = block.timestamp - 1; bytes32 domainSeparator = ERC721Extended.DOMAIN_SEPARATOR(); (uint8 v, bytes32 r, bytes32 s) = vm.sign( @@ -2353,7 +2347,6 @@ contract ERC721Test is Test { vm.stopPrank(); uint256 nonce = ERC721Extended.nonces(tokenId); - // solhint-disable-next-line not-rely-on-time uint256 deadline = block.timestamp + increment; bytes32 domainSeparator = ERC721Extended.DOMAIN_SEPARATOR(); (uint8 v, bytes32 r, bytes32 s) = vm.sign( @@ -2399,7 +2392,6 @@ contract ERC721Test is Test { vm.stopPrank(); uint256 nonce = ERC721Extended.nonces(tokenId); - // solhint-disable-next-line not-rely-on-time uint256 deadline = block.timestamp + increment; bytes32 domainSeparator = ERC721Extended.DOMAIN_SEPARATOR(); (uint8 v, bytes32 r, bytes32 s) = vm.sign( diff --git a/test/tokens/mocks/ERC721ReceiverMock.sol b/test/tokens/mocks/ERC721ReceiverMock.sol index 03b46c89..6e327c20 100644 --- a/test/tokens/mocks/ERC721ReceiverMock.sol +++ b/test/tokens/mocks/ERC721ReceiverMock.sol @@ -11,16 +11,14 @@ import {IERC721Receiver} from "openzeppelin/token/ERC721/IERC721Receiver.sol"; * @dev Allows to test receiving ERC-721 tokens as a smart contract. */ contract ERC721ReceiverMock is IERC721Receiver { - // solhint-disable-next-line var-name-mixedcase - bytes4 private immutable _RETVAL; - enum Error { None, RevertWithMessage, RevertWithoutMessage, Panic } - // solhint-disable-next-line var-name-mixedcase + + bytes4 private immutable _RETVAL; Error private immutable _ERROR; event Received(address operator, address from, uint256 tokenId, bytes data); diff --git a/test/utils/EIP712DomainSeparator.t.sol b/test/utils/EIP712DomainSeparator.t.sol index 08c98f48..b09f66ad 100644 --- a/test/utils/EIP712DomainSeparator.t.sol +++ b/test/utils/EIP712DomainSeparator.t.sol @@ -83,7 +83,6 @@ contract EIP712DomainSeparatorTest is Test { address spender = makeAddr("spender"); uint256 value = 100; uint256 nonce = 1; - // solhint-disable-next-line not-rely-on-time uint256 deadline = block.timestamp + 100_000; bytes32 structHash = keccak256( abi.encode( @@ -161,7 +160,6 @@ contract EIP712DomainSeparatorTest is Test { uint256 nonce, uint64 increment ) public { - // solhint-disable-next-line not-rely-on-time uint256 deadline = block.timestamp + increment; bytes32 structHash = keccak256( abi.encode(