diff --git a/.gas-snapshot b/.gas-snapshot index 1aba2458d..15ea6c386 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,73 +1,74 @@ -AIP1Point2ActionTest:testAction() (gas: 629593) +AIP1Point2ActionTest:testAction() (gas: 629637) AIPNovaFeeRoutingActionTest:testAction() (gas: 3074) ActivateDvpQuorumActionTest:testAction() (gas: 3074) ArbitrumDAOConstitutionTest:testConstructor() (gas: 259383) ArbitrumDAOConstitutionTest:testMonOwnerCannotSetHash() (gas: 262836) ArbitrumDAOConstitutionTest:testOwnerCanSetHash() (gas: 261148) ArbitrumDAOConstitutionTest:testOwnerCanSetHashTwice() (gas: 263824) -ArbitrumFoundationVestingWalletTest:testBeneficiaryCanSetBeneficiary() (gas: 16921206) -ArbitrumFoundationVestingWalletTest:testMigrateEthToNewWalletWithSlowerVesting() (gas: 19719095) -ArbitrumFoundationVestingWalletTest:testMigrateTokensToNewWalletWithFasterVesting() (gas: 19723084) -ArbitrumFoundationVestingWalletTest:testMigrateTokensToNewWalletWithSlowerVesting() (gas: 19723029) -ArbitrumFoundationVestingWalletTest:testMigrationTargetMustBeContract() (gas: 16924455) -ArbitrumFoundationVestingWalletTest:testOnlyBeneficiaryCanRelease() (gas: 16916407) -ArbitrumFoundationVestingWalletTest:testOnlyOwnerCanMigrate() (gas: 16918774) -ArbitrumFoundationVestingWalletTest:testOwnerCanSetBeneficiary() (gas: 16921289) -ArbitrumFoundationVestingWalletTest:testProperlyInits() (gas: 16926820) -ArbitrumFoundationVestingWalletTest:testRandomAddressCantSetBeneficiary() (gas: 16918655) -ArbitrumFoundationVestingWalletTest:testRelease() (gas: 17044684) +ArbitrumFoundationVestingWalletTest:testBeneficiaryCanSetBeneficiary() (gas: 16921226) +ArbitrumFoundationVestingWalletTest:testMigrateEthToNewWalletWithSlowerVesting() (gas: 19719120) +ArbitrumFoundationVestingWalletTest:testMigrateTokensToNewWalletWithFasterVesting() (gas: 19723109) +ArbitrumFoundationVestingWalletTest:testMigrateTokensToNewWalletWithSlowerVesting() (gas: 19723054) +ArbitrumFoundationVestingWalletTest:testMigrationTargetMustBeContract() (gas: 16924475) +ArbitrumFoundationVestingWalletTest:testOnlyBeneficiaryCanRelease() (gas: 16916427) +ArbitrumFoundationVestingWalletTest:testOnlyOwnerCanMigrate() (gas: 16918794) +ArbitrumFoundationVestingWalletTest:testOwnerCanSetBeneficiary() (gas: 16921309) +ArbitrumFoundationVestingWalletTest:testProperlyInits() (gas: 16926840) +ArbitrumFoundationVestingWalletTest:testRandomAddressCantSetBeneficiary() (gas: 16918675) +ArbitrumFoundationVestingWalletTest:testRelease() (gas: 17044704) ArbitrumVestingWalletFactoryTest:testDeploy() (gas: 4589694) ArbitrumVestingWalletFactoryTest:testOnlyOwnerCanCreateWallets() (gas: 1504292) -ArbitrumVestingWalletTest:testCastVote() (gas: 16930552) -ArbitrumVestingWalletTest:testCastVoteFailsForNonBeneficiary() (gas: 16877248) -ArbitrumVestingWalletTest:testClaim() (gas: 16707924) -ArbitrumVestingWalletTest:testClaimFailsForNonBeneficiary() (gas: 16642711) -ArbitrumVestingWalletTest:testDelegate() (gas: 16784670) -ArbitrumVestingWalletTest:testDelegateFailsForNonBeneficiary() (gas: 16708567) -ArbitrumVestingWalletTest:testDoesDeploy() (gas: 16646098) -ArbitrumVestingWalletTest:testReleaseAffordance() (gas: 16708781) -ArbitrumVestingWalletTest:testVestedAmountStart() (gas: 16775433) +ArbitrumVestingWalletTest:testCastVote() (gas: 16930567) +ArbitrumVestingWalletTest:testCastVoteFailsForNonBeneficiary() (gas: 16877263) +ArbitrumVestingWalletTest:testClaim() (gas: 16707939) +ArbitrumVestingWalletTest:testClaimFailsForNonBeneficiary() (gas: 16642726) +ArbitrumVestingWalletTest:testDelegate() (gas: 16784685) +ArbitrumVestingWalletTest:testDelegateFailsForNonBeneficiary() (gas: 16708582) +ArbitrumVestingWalletTest:testDoesDeploy() (gas: 16646113) +ArbitrumVestingWalletTest:testReleaseAffordance() (gas: 16708796) +ArbitrumVestingWalletTest:testVestedAmountStart() (gas: 16775448) Cancel:testFuzz_CancelsPendingProposal(uint256) (runs: 256, μ: 343001, ~: 343001) Cancel:testFuzz_RevertIf_AlreadyCanceled(uint256) (runs: 256, μ: 350297, ~: 350297) Cancel:testFuzz_RevertIf_NotProposer(uint256,address) (runs: 256, μ: 337631, ~: 337631) Cancel:testFuzz_RevertIf_ProposalIsActive(uint256) (runs: 256, μ: 342482, ~: 342482) -E2E:testE2E() (gas: 83338879) -Execute:testFuzz_EmitsExecuteEvent(uint256,address) (runs: 257, μ: 611339, ~: 611339) -Execute:testFuzz_ExecutesASucceededProposal(uint256) (runs: 257, μ: 611129, ~: 611129) -Execute:testFuzz_RevertIf_OperationNotReady(uint256,address) (runs: 257, μ: 599719, ~: 599719) -FixedDelegateErc20WalletTest:testInit() (gas: 5962806) -FixedDelegateErc20WalletTest:testInitZeroToken() (gas: 5956180) -FixedDelegateErc20WalletTest:testTransfer() (gas: 6121973) -FixedDelegateErc20WalletTest:testTransferNotOwner() (gas: 6084194) +CancelTimelockAndRemoveMemberActionTest:testAction() (gas: 8159) +E2E:testE2E() (gas: 85158392) +Execute:testFuzz_EmitsExecuteEvent(uint256,address) (runs: 256, μ: 611319, ~: 611339) +Execute:testFuzz_ExecutesASucceededProposal(uint256) (runs: 256, μ: 611129, ~: 611129) +Execute:testFuzz_RevertIf_OperationNotReady(uint256,address) (runs: 256, μ: 599719, ~: 599719) +FixedDelegateErc20WalletTest:testInit() (gas: 5962816) +FixedDelegateErc20WalletTest:testInitZeroToken() (gas: 5956190) +FixedDelegateErc20WalletTest:testTransfer() (gas: 6121983) +FixedDelegateErc20WalletTest:testTransferNotOwner() (gas: 6084204) InboxActionsTest:testPauseAndUpauseInbox() (gas: 370760) L1AddressRegistryTest:testAddressRegistryAddress() (gas: 47105) -L1ArbitrumTimelockTest:testCancel() (gas: 5349713) -L1ArbitrumTimelockTest:testCancelFailsBadSender() (gas: 5394603) -L1ArbitrumTimelockTest:testDoesDeploy() (gas: 5298115) -L1ArbitrumTimelockTest:testDoesNotDeployZeroInbox() (gas: 5004008) -L1ArbitrumTimelockTest:testDoesNotDeployZeroL2Timelock() (gas: 5001978) -L1ArbitrumTimelockTest:testExecute() (gas: 5430420) -L1ArbitrumTimelockTest:testExecuteInbox() (gas: 5784952) -L1ArbitrumTimelockTest:testExecuteInboxBatch() (gas: 6087826) -L1ArbitrumTimelockTest:testExecuteInboxInvalidData() (gas: 5472154) -L1ArbitrumTimelockTest:testExecuteInboxNotEnoughVal() (gas: 5484755) -L1ArbitrumTimelockTest:testSchedule() (gas: 5382865) -L1ArbitrumTimelockTest:testScheduleFailsBadL2Timelock() (gas: 5311169) -L1ArbitrumTimelockTest:testScheduleFailsBadSender() (gas: 5306141) -L1ArbitrumTokenTest:testBridgeBurn() (gas: 3395679) -L1ArbitrumTokenTest:testBridgeBurnNotGateway() (gas: 3389683) -L1ArbitrumTokenTest:testBridgeMint() (gas: 3390882) -L1ArbitrumTokenTest:testBridgeMintNotGateway() (gas: 3341084) -L1ArbitrumTokenTest:testInit() (gas: 3356227) -L1ArbitrumTokenTest:testInitZeroGateway() (gas: 3177270) -L1ArbitrumTokenTest:testInitZeroNovaGateway() (gas: 3177337) -L1ArbitrumTokenTest:testInitZeroNovaRouter() (gas: 3177271) -L1ArbitrumTokenTest:testRegisterTokenOnL2() (gas: 4568996) -L1ArbitrumTokenTest:testRegisterTokenOnL2NotEnoughVal() (gas: 4425871) +L1ArbitrumTimelockTest:testCancel() (gas: 5349718) +L1ArbitrumTimelockTest:testCancelFailsBadSender() (gas: 5394608) +L1ArbitrumTimelockTest:testDoesDeploy() (gas: 5298120) +L1ArbitrumTimelockTest:testDoesNotDeployZeroInbox() (gas: 5004013) +L1ArbitrumTimelockTest:testDoesNotDeployZeroL2Timelock() (gas: 5001983) +L1ArbitrumTimelockTest:testExecute() (gas: 5430425) +L1ArbitrumTimelockTest:testExecuteInbox() (gas: 5784957) +L1ArbitrumTimelockTest:testExecuteInboxBatch() (gas: 6087831) +L1ArbitrumTimelockTest:testExecuteInboxInvalidData() (gas: 5472159) +L1ArbitrumTimelockTest:testExecuteInboxNotEnoughVal() (gas: 5484760) +L1ArbitrumTimelockTest:testSchedule() (gas: 5382870) +L1ArbitrumTimelockTest:testScheduleFailsBadL2Timelock() (gas: 5311174) +L1ArbitrumTimelockTest:testScheduleFailsBadSender() (gas: 5306146) +L1ArbitrumTokenTest:testBridgeBurn() (gas: 3395684) +L1ArbitrumTokenTest:testBridgeBurnNotGateway() (gas: 3389688) +L1ArbitrumTokenTest:testBridgeMint() (gas: 3390887) +L1ArbitrumTokenTest:testBridgeMintNotGateway() (gas: 3341089) +L1ArbitrumTokenTest:testInit() (gas: 3356232) +L1ArbitrumTokenTest:testInitZeroGateway() (gas: 3177275) +L1ArbitrumTokenTest:testInitZeroNovaGateway() (gas: 3177342) +L1ArbitrumTokenTest:testInitZeroNovaRouter() (gas: 3177276) +L1ArbitrumTokenTest:testRegisterTokenOnL2() (gas: 4569001) +L1ArbitrumTokenTest:testRegisterTokenOnL2NotEnoughVal() (gas: 4425876) L1GovernanceFactoryTest:testL1GovernanceFactory() (gas: 10796764) L1GovernanceFactoryTest:testSetMinDelay() (gas: 10771242) L1GovernanceFactoryTest:testSetMinDelayRevertsForCoreAddress() (gas: 10824209) -L2AddressRegistryTest:testAddressRegistryAddress() (gas: 54702) +L2AddressRegistryTest:testAddressRegistryAddress() (gas: 54814) L2ArbitrumTokenTest:testCanBurn() (gas: 4206836) L2ArbitrumTokenTest:testCanMint2Percent() (gas: 4241646) L2ArbitrumTokenTest:testCanMintLessThan2Percent() (gas: 4241648) @@ -85,7 +86,7 @@ L2ArbitrumTokenTest:testDecreaseDVPOnUndelegate() (gas: 4317040) L2ArbitrumTokenTest:testDoesNotInitialiseZeroInitialSup() (gas: 3939579) L2ArbitrumTokenTest:testDoesNotInitialiseZeroL1Token() (gas: 3939531) L2ArbitrumTokenTest:testDoesNotInitialiseZeroOwner() (gas: 3939634) -L2ArbitrumTokenTest:testDvpAdjustment(uint64,int64) (runs: 256, μ: 4254235, ~: 4254851) +L2ArbitrumTokenTest:testDvpAdjustment(uint64,int64) (runs: 256, μ: 4252589, ~: 4254851) L2ArbitrumTokenTest:testDvpAtBlockBeforeFirstCheckpoint() (gas: 4254247) L2ArbitrumTokenTest:testDvpDecreaseOnTransferFromDelegator() (gas: 4357412) L2ArbitrumTokenTest:testDvpIncreaseOnTransferToDelegator() (gas: 4348789) @@ -109,23 +110,23 @@ L2GovernanceFactoryTest:testSanityCheckValues() (gas: 29444230) L2GovernanceFactoryTest:testSetMinDelay() (gas: 29392769) L2GovernanceFactoryTest:testSetMinDelayRevertsForCoreAddress() (gas: 29445646) L2GovernanceFactoryTest:testUpgraderCanCancel() (gas: 29766026) -L2SecurityCouncilMgmtFactoryTest:testMemberElectionGovDeployment() (gas: 29718801) -L2SecurityCouncilMgmtFactoryTest:testNomineeElectionGovDeployment() (gas: 29723152) -L2SecurityCouncilMgmtFactoryTest:testOnlyOwnerCanDeploy() (gas: 24767154) -L2SecurityCouncilMgmtFactoryTest:testRemovalGovDeployment() (gas: 29721032) -L2SecurityCouncilMgmtFactoryTest:testSecurityCouncilManagerDeployment() (gas: 29740353) -MiscTests:testCantReinit() (gas: 14345705) -MiscTests:testDVPQuorumAndClamping() (gas: 14717884) -MiscTests:testExecutorPermissions() (gas: 14383151) -MiscTests:testExecutorPermissionsFail() (gas: 14355476) -MiscTests:testMinMaxQuorumGetters() (gas: 14413247) -MiscTests:testPastCirculatingSupply() (gas: 14349820) -MiscTests:testPastCirculatingSupplyExclude() (gas: 14539332) -MiscTests:testPastCirculatingSupplyMint() (gas: 14416262) -MiscTests:testProperlyInitialized() (gas: 14343456) +L2SecurityCouncilMgmtFactoryTest:testMemberElectionGovDeployment() (gas: 31350700) +L2SecurityCouncilMgmtFactoryTest:testNomineeElectionGovDeployment() (gas: 31355095) +L2SecurityCouncilMgmtFactoryTest:testOnlyOwnerCanDeploy() (gas: 26328868) +L2SecurityCouncilMgmtFactoryTest:testRemovalGovDeployment() (gas: 31352931) +L2SecurityCouncilMgmtFactoryTest:testSecurityCouncilManagerDeployment() (gas: 31374298) +MiscTests:testCantReinit() (gas: 14345720) +MiscTests:testDVPQuorumAndClamping() (gas: 14717899) +MiscTests:testExecutorPermissions() (gas: 14383166) +MiscTests:testExecutorPermissionsFail() (gas: 14355491) +MiscTests:testMinMaxQuorumGetters() (gas: 14413262) +MiscTests:testPastCirculatingSupply() (gas: 14349835) +MiscTests:testPastCirculatingSupplyExclude() (gas: 14539347) +MiscTests:testPastCirculatingSupplyMint() (gas: 14416277) +MiscTests:testProperlyInitialized() (gas: 14343471) NomineeGovernorV2UpgradeActionTest:testAction() (gas: 8153) OfficeHoursActionTest:testConstructor() (gas: 9053) -OfficeHoursActionTest:testFuzzOfficeHoursDeployment(uint256,uint256,int256,uint256,uint256,uint256) (runs: 256, μ: 317104, ~: 317184) +OfficeHoursActionTest:testFuzzOfficeHoursDeployment(uint256,uint256,int256,uint256,uint256,uint256) (runs: 256, μ: 317098, ~: 317184) OfficeHoursActionTest:testInvalidConstructorParameters() (gas: 235758) OfficeHoursActionTest:testPerformBeforeMinimumTimestamp() (gas: 8646) OfficeHoursActionTest:testPerformDuringOfficeHours() (gas: 9140) @@ -143,51 +144,57 @@ Propose:testFuzz_ProposerAboveThresholdCanPropose(uint256) (runs: 256, μ: 33421 Propose:testFuzz_ProposerBelowThresholdCannotPropose(address) (runs: 256, μ: 46371, ~: 46371) ProxyUpgradeAndCallActionTest:testUpgrade() (gas: 137146) ProxyUpgradeAndCallActionTest:testUpgradeAndCall() (gas: 143096) -Queue:testFuzz_EmitsQueueEvent(uint256) (runs: 257, μ: 538170, ~: 538170) -Queue:testFuzz_QueuesASucceededProposal(uint256) (runs: 257, μ: 562826, ~: 562826) -Queue:testFuzz_RevertIf_ProposalIsNotSucceeded(uint256) (runs: 257, μ: 433353, ~: 433353) -SecurityCouncilManagerTest:testAddMemberAffordances() (gas: 251726) -SecurityCouncilManagerTest:testAddMemberSpecialAddresses() (gas: 20837) -SecurityCouncilManagerTest:testAddMemberToFirstCohort() (gas: 344166) -SecurityCouncilManagerTest:testAddMemberToSecondCohort() (gas: 347534) -SecurityCouncilManagerTest:testAddSC() (gas: 118681) -SecurityCouncilManagerTest:testAddSCAffordances() (gas: 112323) -SecurityCouncilManagerTest:testCantUpdateCohortWithADup() (gas: 125183) -SecurityCouncilManagerTest:testCohortMethods() (gas: 137289) -SecurityCouncilManagerTest:testInitialization() (gas: 193842) -SecurityCouncilManagerTest:testRemoveMember() (gas: 214972) -SecurityCouncilManagerTest:testRemoveMemberAffordances() (gas: 99314) -SecurityCouncilManagerTest:testRemoveSCAffordances() (gas: 81379) -SecurityCouncilManagerTest:testRemoveSeC() (gas: 38381) -SecurityCouncilManagerTest:testReplaceMemberAffordances() (gas: 209484) -SecurityCouncilManagerTest:testReplaceMemberInFirstCohort() (gas: 261463) -SecurityCouncilManagerTest:testReplaceMemberInSecondCohort() (gas: 265052) -SecurityCouncilManagerTest:testRotateMember() (gas: 261467) -SecurityCouncilManagerTest:testUpdateCohortAffordances() (gas: 83176) -SecurityCouncilManagerTest:testUpdateFirstCohort() (gas: 299573) -SecurityCouncilManagerTest:testUpdateRouter() (gas: 76374) -SecurityCouncilManagerTest:testUpdateRouterAffordacnes() (gas: 109981) -SecurityCouncilManagerTest:testUpdateSecondCohort() (gas: 299578) -SecurityCouncilMemberElectionGovernorTest:testCannotUseMoreVotesThanAvailable() (gas: 247063) -SecurityCouncilMemberElectionGovernorTest:testCastBySig() (gas: 302942) -SecurityCouncilMemberElectionGovernorTest:testCastBySigTwice() (gas: 266328) +Queue:testFuzz_EmitsQueueEvent(uint256) (runs: 256, μ: 538170, ~: 538170) +Queue:testFuzz_QueuesASucceededProposal(uint256) (runs: 256, μ: 562826, ~: 562826) +Queue:testFuzz_RevertIf_ProposalIsNotSucceeded(uint256) (runs: 256, μ: 433353, ~: 433353) +SecurityCouncilManagerTest:testAddMemberAffordances() (gas: 254008) +SecurityCouncilManagerTest:testAddMemberSpecialAddresses() (gas: 20770) +SecurityCouncilManagerTest:testAddMemberToFirstCohort() (gas: 349341) +SecurityCouncilManagerTest:testAddMemberToSecondCohort() (gas: 352791) +SecurityCouncilManagerTest:testAddSC() (gas: 118742) +SecurityCouncilManagerTest:testAddSCAffordances() (gas: 112449) +SecurityCouncilManagerTest:testCantUpdateCohortWithADup() (gas: 136636) +SecurityCouncilManagerTest:testCohortMethods() (gas: 137950) +SecurityCouncilManagerTest:testInitialization() (gas: 206767) +SecurityCouncilManagerTest:testPostUpgradeInit() (gas: 4958794) +SecurityCouncilManagerTest:testRemoveMember() (gas: 217537) +SecurityCouncilManagerTest:testRemoveMemberAffordances() (gas: 101588) +SecurityCouncilManagerTest:testRemoveMemberRotated() (gas: 423744) +SecurityCouncilManagerTest:testRemoveSCAffordances() (gas: 81462) +SecurityCouncilManagerTest:testRemoveSeC() (gas: 38383) +SecurityCouncilManagerTest:testReplaceMemberAffordances() (gas: 216513) +SecurityCouncilManagerTest:testReplaceMemberInFirstCohort() (gas: 266719) +SecurityCouncilManagerTest:testReplaceMemberInFirstCohortAfterRotation() (gas: 472001) +SecurityCouncilManagerTest:testReplaceMemberInSecondCohort() (gas: 479223) +SecurityCouncilManagerTest:testReplaceMemberInSecondCohortAfterRotation() (gas: 270288) +SecurityCouncilManagerTest:testRotateMember() (gas: 1016270) +SecurityCouncilManagerTest:testRotateMemberNotContender() (gas: 4106808) +SecurityCouncilManagerTest:testSetMinRotationPeriod() (gas: 65820) +SecurityCouncilManagerTest:testUpdateCohortAffordances() (gas: 83279) +SecurityCouncilManagerTest:testUpdateFirstCohort() (gas: 313896) +SecurityCouncilManagerTest:testUpdateRouter() (gas: 76407) +SecurityCouncilManagerTest:testUpdateRouterAffordances() (gas: 109980) +SecurityCouncilManagerTest:testUpdateSecondCohort() (gas: 313990) +SecurityCouncilMemberElectionGovernorTest:testCannotUseMoreVotesThanAvailable() (gas: 247084) +SecurityCouncilMemberElectionGovernorTest:testCastBySig() (gas: 302963) +SecurityCouncilMemberElectionGovernorTest:testCastBySigTwice() (gas: 266349) SecurityCouncilMemberElectionGovernorTest:testCastVoteReverts() (gas: 35277) -SecurityCouncilMemberElectionGovernorTest:testExecute() (gas: 665669) -SecurityCouncilMemberElectionGovernorTest:testForceSupport() (gas: 165397) +SecurityCouncilMemberElectionGovernorTest:testExecute() (gas: 666230) +SecurityCouncilMemberElectionGovernorTest:testForceSupport() (gas: 165418) SecurityCouncilMemberElectionGovernorTest:testInitReverts() (gas: 4868350) -SecurityCouncilMemberElectionGovernorTest:testInvalidParams() (gas: 165369) -SecurityCouncilMemberElectionGovernorTest:testMiscVotesViews() (gas: 227999) -SecurityCouncilMemberElectionGovernorTest:testNoVoteForNonCompliantNominee() (gas: 123578) -SecurityCouncilMemberElectionGovernorTest:testNoZeroWeightVotes() (gas: 169643) +SecurityCouncilMemberElectionGovernorTest:testInvalidParams() (gas: 165390) +SecurityCouncilMemberElectionGovernorTest:testMiscVotesViews() (gas: 228020) +SecurityCouncilMemberElectionGovernorTest:testNoVoteForNonCompliantNominee() (gas: 123599) +SecurityCouncilMemberElectionGovernorTest:testNoZeroWeightVotes() (gas: 169664) SecurityCouncilMemberElectionGovernorTest:testOnlyNomineeElectionGovernorCanPropose() (gas: 111068) SecurityCouncilMemberElectionGovernorTest:testProperInitialization() (gas: 49388) SecurityCouncilMemberElectionGovernorTest:testProposeReverts() (gas: 32952) SecurityCouncilMemberElectionGovernorTest:testRelay() (gas: 42235) -SecurityCouncilMemberElectionGovernorTest:testSelectTopNominees(uint256) (runs: 256, μ: 339674, ~: 339572) -SecurityCouncilMemberElectionGovernorTest:testSelectTopNomineesFails() (gas: 273353) +SecurityCouncilMemberElectionGovernorTest:testSelectTopNominees(uint256) (runs: 256, μ: 339907, ~: 339738) +SecurityCouncilMemberElectionGovernorTest:testSelectTopNomineesFails() (gas: 273485) SecurityCouncilMemberElectionGovernorTest:testSetFullWeightDuration() (gas: 34963) SecurityCouncilMemberElectionGovernorTest:testVotesToWeight() (gas: 152946) -SecurityCouncilMemberRemovalGovernorTest:testInitFails() (gas: 10244398) +SecurityCouncilMemberRemovalGovernorTest:testInitFails() (gas: 10244408) SecurityCouncilMemberRemovalGovernorTest:testProposalCreationCallParamRestriction() (gas: 56250) SecurityCouncilMemberRemovalGovernorTest:testProposalCreationCallRestriction() (gas: 49754) SecurityCouncilMemberRemovalGovernorTest:testProposalCreationTargetLen() (gas: 35452) @@ -203,85 +210,96 @@ SecurityCouncilMemberRemovalGovernorTest:testSetVoteSuccessNumeratorAffordance() SecurityCouncilMemberRemovalGovernorTest:testSuccessNumeratorInsufficientVotes() (gas: 358561) SecurityCouncilMemberRemovalGovernorTest:testSuccessNumeratorSufficientVotes() (gas: 361479) SecurityCouncilMemberRemovalGovernorTest:testSuccessfulProposalAndCantAbstain() (gas: 142792) -SecurityCouncilMemberSyncActionTest:testAddOne() (gas: 7827528) -SecurityCouncilMemberSyncActionTest:testAddOne() (gas: 7828366) -SecurityCouncilMemberSyncActionTest:testCantDropBelowThreshhold() (gas: 7855341) -SecurityCouncilMemberSyncActionTest:testCantDropBelowThreshhold() (gas: 7855363) -SecurityCouncilMemberSyncActionTest:testGetPrevOwner() (gas: 7822494) -SecurityCouncilMemberSyncActionTest:testGetPrevOwner() (gas: 7822494) -SecurityCouncilMemberSyncActionTest:testNonces() (gas: 8124889) -SecurityCouncilMemberSyncActionTest:testNoopUpdate() (gas: 7817392) -SecurityCouncilMemberSyncActionTest:testNoopUpdate() (gas: 7818318) -SecurityCouncilMemberSyncActionTest:testRemoveOne() (gas: 7819214) -SecurityCouncilMemberSyncActionTest:testRemoveOne() (gas: 7820075) -SecurityCouncilMemberSyncActionTest:testUpdateCohort() (gas: 8065891) -SecurityCouncilMemberSyncActionTest:testUpdateCohort() (gas: 8066751) +SecurityCouncilMemberSyncActionTest:testAddOne() (gas: 7828184) +SecurityCouncilMemberSyncActionTest:testAddOne() (gas: 7829022) +SecurityCouncilMemberSyncActionTest:testCantDropBelowThreshhold() (gas: 7855346) +SecurityCouncilMemberSyncActionTest:testCantDropBelowThreshhold() (gas: 7855368) +SecurityCouncilMemberSyncActionTest:testGetPrevOwner() (gas: 7822499) +SecurityCouncilMemberSyncActionTest:testGetPrevOwner() (gas: 7822499) +SecurityCouncilMemberSyncActionTest:testNonces() (gas: 8128149) +SecurityCouncilMemberSyncActionTest:testNoopUpdate() (gas: 7818048) +SecurityCouncilMemberSyncActionTest:testNoopUpdate() (gas: 7818974) +SecurityCouncilMemberSyncActionTest:testRemoveOne() (gas: 7819812) +SecurityCouncilMemberSyncActionTest:testRemoveOne() (gas: 7820673) +SecurityCouncilMemberSyncActionTest:testUpdateCohort() (gas: 8066547) +SecurityCouncilMemberSyncActionTest:testUpdateCohort() (gas: 8067407) SecurityCouncilMgmtUtilsTests:testIsInArray() (gas: 2102) -SecurityCouncilNomineeElectionGovernorTest:testAddContender() (gas: 271338) -SecurityCouncilNomineeElectionGovernorTest:testCastBySig() (gas: 334285) -SecurityCouncilNomineeElectionGovernorTest:testCastBySigTwice() (gas: 297042) -SecurityCouncilNomineeElectionGovernorTest:testCastVoteReverts() (gas: 35278) -SecurityCouncilNomineeElectionGovernorTest:testCountVote() (gas: 584254) -SecurityCouncilNomineeElectionGovernorTest:testCreateElection() (gas: 253465) -SecurityCouncilNomineeElectionGovernorTest:testExcludeNominee() (gas: 457489) -SecurityCouncilNomineeElectionGovernorTest:testExecute() (gas: 678680) -SecurityCouncilNomineeElectionGovernorTest:testForceSupport() (gas: 194997) -SecurityCouncilNomineeElectionGovernorTest:testIncludeNominee() (gas: 675910) -SecurityCouncilNomineeElectionGovernorTest:testInvalidInit() (gas: 6977267) -SecurityCouncilNomineeElectionGovernorTest:testProperInitialization() (gas: 78233) -SecurityCouncilNomineeElectionGovernorTest:testProposeFails() (gas: 19791) -SecurityCouncilNomineeElectionGovernorTest:testRelay() (gas: 42523) -SecurityCouncilNomineeElectionGovernorTest:testSetNomineeVetter() (gas: 40037) +SecurityCouncilNomineeElectionGovernorTest:testAddContender() (gas: 282987) +SecurityCouncilNomineeElectionGovernorTest:testCadenceWithLargeValues() (gas: 52961) +SecurityCouncilNomineeElectionGovernorTest:testCastBySig() (gas: 338352) +SecurityCouncilNomineeElectionGovernorTest:testCastBySigTwice() (gas: 301062) +SecurityCouncilNomineeElectionGovernorTest:testCastVoteReverts() (gas: 35303) +SecurityCouncilNomineeElectionGovernorTest:testCountVote() (gas: 592371) +SecurityCouncilNomineeElectionGovernorTest:testCreateElection() (gas: 258275) +SecurityCouncilNomineeElectionGovernorTest:testDefaultCadence() (gas: 14950) +SecurityCouncilNomineeElectionGovernorTest:testElectionTimestampsWithDefaultCadence() (gas: 37631) +SecurityCouncilNomineeElectionGovernorTest:testExcludeNominee() (gas: 461718) +SecurityCouncilNomineeElectionGovernorTest:testExecute() (gas: 680974) +SecurityCouncilNomineeElectionGovernorTest:testForceSupport() (gas: 199102) +SecurityCouncilNomineeElectionGovernorTest:testIncludeNominee() (gas: 679693) +SecurityCouncilNomineeElectionGovernorTest:testInvalidInit() (gas: 7452727) +SecurityCouncilNomineeElectionGovernorTest:testMultipleCadenceChanges() (gas: 239269) +SecurityCouncilNomineeElectionGovernorTest:testProperInitialization() (gas: 78279) +SecurityCouncilNomineeElectionGovernorTest:testProposeFails() (gas: 19837) +SecurityCouncilNomineeElectionGovernorTest:testRelay() (gas: 42501) +SecurityCouncilNomineeElectionGovernorTest:testRotateNominee() (gas: 688690) +SecurityCouncilNomineeElectionGovernorTest:testSetCadenceAfterElections() (gas: 227915) +SecurityCouncilNomineeElectionGovernorTest:testSetCadenceBeforeFirstElection() (gas: 42562) +SecurityCouncilNomineeElectionGovernorTest:testSetCadenceInvalidValue() (gas: 26149) +SecurityCouncilNomineeElectionGovernorTest:testSetCadenceOnlyOwner() (gas: 16096) +SecurityCouncilNomineeElectionGovernorTest:testSetCadenceTooSoonReverts() (gas: 148292) +SecurityCouncilNomineeElectionGovernorTest:testSetNomineeVetter() (gas: 40150) +SecurityCouncilUpgradeActionTest:testAction() (gas: 8159) SequencerActionsTest:testAddAndRemoveSequencer() (gas: 486700) SequencerActionsTest:testCantAddZeroAddress() (gas: 235659) SetInitialGovParamsActionTest:testL1() (gas: 260009) -SetInitialGovParamsActionTest:testL2() (gas: 689085) +SetInitialGovParamsActionTest:testL2() (gas: 689152) SetSequencerInboxMaxTimeVariationActionTest:testSetMaxTimeVariation() (gas: 310404) SwitchManagerRolesActionTest:testAction() (gas: 6319) -TokenDistributorTest:testClaim() (gas: 5876433) -TokenDistributorTest:testClaimAndDelegate() (gas: 5987949) -TokenDistributorTest:testClaimAndDelegateFailsForExpired() (gas: 5881922) -TokenDistributorTest:testClaimAndDelegateFailsForWrongSender() (gas: 5937905) -TokenDistributorTest:testClaimAndDelegateFailsWrongNonce() (gas: 5937906) -TokenDistributorTest:testClaimFailsAfterEnd() (gas: 5812336) -TokenDistributorTest:testClaimFailsBeforeStart() (gas: 5811825) -TokenDistributorTest:testClaimFailsForFalseTransfer() (gas: 5794547) -TokenDistributorTest:testClaimFailsForTwice() (gas: 5875187) -TokenDistributorTest:testClaimFailsForUnknown() (gas: 5814412) -TokenDistributorTest:testClaimStartAfterClaimEnd() (gas: 4274189) -TokenDistributorTest:testDoesDeploy() (gas: 5401698) -TokenDistributorTest:testDoesDeployAndDeposit() (gas: 5512878) -TokenDistributorTest:testOldClaimStart() (gas: 4274752) -TokenDistributorTest:testSetRecipients() (gas: 5810222) -TokenDistributorTest:testSetRecipientsFailsNotEnoughDeposit() (gas: 5777102) -TokenDistributorTest:testSetRecipientsFailsNotOwner() (gas: 5528624) -TokenDistributorTest:testSetRecipientsFailsWhenAddingTwice() (gas: 5821288) -TokenDistributorTest:testSetRecipientsFailsWrongAmountCount() (gas: 5530088) -TokenDistributorTest:testSetRecipientsFailsWrongRecipientCount() (gas: 5530317) -TokenDistributorTest:testSetRecipientsTwice() (gas: 6499843) -TokenDistributorTest:testSetSweepReceiver() (gas: 5814545) -TokenDistributorTest:testSetSweepReceiverFailsNullAddress() (gas: 5812176) -TokenDistributorTest:testSetSweepReceiverFailsOwner() (gas: 5813131) -TokenDistributorTest:testSweep() (gas: 5885702) -TokenDistributorTest:testSweepAfterClaim() (gas: 5949073) -TokenDistributorTest:testSweepFailsBeforeClaimPeriodEnd() (gas: 5811892) -TokenDistributorTest:testSweepFailsForFailedTransfer() (gas: 5815633) -TokenDistributorTest:testSweepFailsTwice() (gas: 5885510) -TokenDistributorTest:testWithdraw() (gas: 5852909) -TokenDistributorTest:testWithdrawFailsNotOwner() (gas: 5852931) -TokenDistributorTest:testWithdrawFailsTransfer() (gas: 5814112) -TokenDistributorTest:testZeroDelegateTo() (gas: 4272084) -TokenDistributorTest:testZeroOwner() (gas: 4271997) -TokenDistributorTest:testZeroReceiver() (gas: 4272026) +TokenDistributorTest:testClaim() (gas: 5876438) +TokenDistributorTest:testClaimAndDelegate() (gas: 5987954) +TokenDistributorTest:testClaimAndDelegateFailsForExpired() (gas: 5881927) +TokenDistributorTest:testClaimAndDelegateFailsForWrongSender() (gas: 5937910) +TokenDistributorTest:testClaimAndDelegateFailsWrongNonce() (gas: 5937911) +TokenDistributorTest:testClaimFailsAfterEnd() (gas: 5812341) +TokenDistributorTest:testClaimFailsBeforeStart() (gas: 5811830) +TokenDistributorTest:testClaimFailsForFalseTransfer() (gas: 5794552) +TokenDistributorTest:testClaimFailsForTwice() (gas: 5875192) +TokenDistributorTest:testClaimFailsForUnknown() (gas: 5814417) +TokenDistributorTest:testClaimStartAfterClaimEnd() (gas: 4274194) +TokenDistributorTest:testDoesDeploy() (gas: 5401703) +TokenDistributorTest:testDoesDeployAndDeposit() (gas: 5512883) +TokenDistributorTest:testOldClaimStart() (gas: 4274757) +TokenDistributorTest:testSetRecipients() (gas: 5810227) +TokenDistributorTest:testSetRecipientsFailsNotEnoughDeposit() (gas: 5777107) +TokenDistributorTest:testSetRecipientsFailsNotOwner() (gas: 5528629) +TokenDistributorTest:testSetRecipientsFailsWhenAddingTwice() (gas: 5821293) +TokenDistributorTest:testSetRecipientsFailsWrongAmountCount() (gas: 5530093) +TokenDistributorTest:testSetRecipientsFailsWrongRecipientCount() (gas: 5530322) +TokenDistributorTest:testSetRecipientsTwice() (gas: 6499848) +TokenDistributorTest:testSetSweepReceiver() (gas: 5814550) +TokenDistributorTest:testSetSweepReceiverFailsNullAddress() (gas: 5812181) +TokenDistributorTest:testSetSweepReceiverFailsOwner() (gas: 5813136) +TokenDistributorTest:testSweep() (gas: 5885707) +TokenDistributorTest:testSweepAfterClaim() (gas: 5949078) +TokenDistributorTest:testSweepFailsBeforeClaimPeriodEnd() (gas: 5811897) +TokenDistributorTest:testSweepFailsForFailedTransfer() (gas: 5815638) +TokenDistributorTest:testSweepFailsTwice() (gas: 5885515) +TokenDistributorTest:testWithdraw() (gas: 5852914) +TokenDistributorTest:testWithdrawFailsNotOwner() (gas: 5852936) +TokenDistributorTest:testWithdrawFailsTransfer() (gas: 5814117) +TokenDistributorTest:testZeroDelegateTo() (gas: 4272089) +TokenDistributorTest:testZeroOwner() (gas: 4272002) +TokenDistributorTest:testZeroReceiver() (gas: 4272031) TokenDistributorTest:testZeroToken() (gas: 71812) -TopNomineesGasTest:testTopNomineesGas() (gas: 4533086) +TopNomineesGasTest:testTopNomineesGas() (gas: 4537586) UpgradeExecRouteBuilderTest:testAIP1Point2() (gas: 1408809) UpgradeExecRouteBuilderTest:testActionType() (gas: 1718332) UpgradeExecRouteBuilderTest:testRouteBuilderErrors() (gas: 1238856) -UpgradeExecutorTest:testAdminCanChangeExecutor() (gas: 2677248) -UpgradeExecutorTest:testCantExecuteEOA() (gas: 2533144) -UpgradeExecutorTest:testExecute() (gas: 2771532) -UpgradeExecutorTest:testExecuteFailsForAdmin() (gas: 2757061) -UpgradeExecutorTest:testExecuteFailsForNobody() (gas: 2759302) -UpgradeExecutorTest:testInit() (gas: 2520920) -UpgradeExecutorTest:testInitFailsZeroAdmin() (gas: 2381696) \ No newline at end of file +UpgradeExecutorTest:testAdminCanChangeExecutor() (gas: 2677253) +UpgradeExecutorTest:testCantExecuteEOA() (gas: 2533149) +UpgradeExecutorTest:testExecute() (gas: 2771537) +UpgradeExecutorTest:testExecuteFailsForAdmin() (gas: 2757066) +UpgradeExecutorTest:testExecuteFailsForNobody() (gas: 2759307) +UpgradeExecutorTest:testInit() (gas: 2520925) +UpgradeExecutorTest:testInitFailsZeroAdmin() (gas: 2381701) \ No newline at end of file diff --git a/Makefile b/Makefile index 77bfacf19..ecd351007 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ coverage :; forge coverage gas :; forge test --gas-report gas-check :; forge snapshot --check --tolerance 1 snapshot :; forge snapshot -test-unit :; forge test -vvv +test-unit :; ARB_RPC_URL=https://arb1.arbitrum.io/rpc forge test -vvv clean :; forge clean fmt :; forge fmt gen-network :; yarn gen:network diff --git a/files/mainnet/deployedContracts.json b/files/mainnet/deployedContracts.json index 6129f6f21..da589a9dc 100644 --- a/files/mainnet/deployedContracts.json +++ b/files/mainnet/deployedContracts.json @@ -31,6 +31,6 @@ "l1ProxyAdmin": "0x5613AF0474EB9c528A34701A5b1662E3C8FA0678", "l1Timelock": "0xE6841D92B0C345144506576eC13ECf5103aC7f49", "l1AddressRegistry": "0xd514C2b3aaBDBfa10800B9C96dc1eB25427520A0", - "l2AddressRegistry":"0x56C4E9Eb6c63aCDD19AeC2b1a00e4f0d7aBda9d3", + "l2AddressRegistry":"0x1dFA102bc097446bb2B836082367991dE24A1c64", "novaL1AddressRegistry":"0x2F06643fc2CC18585Ae790b546388F0DE4Ec6635" } \ No newline at end of file diff --git a/foundry.toml b/foundry.toml index 0e3f4a506..4df33222d 100644 --- a/foundry.toml +++ b/foundry.toml @@ -9,7 +9,7 @@ via_ir = false solc_version = '0.8.16' [profile.sec_council_mgmt] -optimizer_runs = 750 +optimizer_runs = 200 [fmt] number_underscore = 'thousands' diff --git a/hardhat.config.ts b/hardhat.config.ts index b9d9ac522..4ade24301 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -19,7 +19,7 @@ const solidityProfiles = { settings: { optimizer: { enabled: true, - runs: 750 + runs: 200 }, } } diff --git a/scripts/proposals/sec-council-rotate/data.json b/scripts/proposals/sec-council-rotate/data.json new file mode 100644 index 000000000..3560a9fd7 --- /dev/null +++ b/scripts/proposals/sec-council-rotate/data.json @@ -0,0 +1,12 @@ +{ + "actionChainIds": [ + 42161 + ], + "actionAddresses": [ + "0x86E93E21AD108CaE7ADe482C34C230Bfd94D4A8B" + ], + "arbSysSendTxToL1Args": { + "l1Timelock": "0xE6841D92B0C345144506576eC13ECf5103aC7f49", + "calldata": "0x8f2a0bb000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000008e40c6e3e3ea77546fc470efab6a7eb5b3896023b6a7c80fd8a11ea1920a2710000000000000000000000000000000000000000000000000000000000003f4800000000000000000000000000000000000000000000000000000000000000001000000000000000000000000a723c008e76e379c55599d2e4d93879beafda79c000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001800000000000000000000000004dbd4fc535ac27206064b68ffcf827b0a60bab3f000000000000000000000000cf57572261c7c2bcf21ffd220ea7d1a27d40a82700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000841cff79cd00000000000000000000000086e93e21ad108cae7ade482c34c230bfd94d4a8b00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000004b147f40c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + } +} \ No newline at end of file diff --git a/scripts/proposals/sec-council-rotate/generate.bash b/scripts/proposals/sec-council-rotate/generate.bash new file mode 100755 index 000000000..cffe65b64 --- /dev/null +++ b/scripts/proposals/sec-council-rotate/generate.bash @@ -0,0 +1,8 @@ +#!/bin/bash + +yarn gen:proposalData \ + --govChainProviderRPC https://arb1.arbitrum.io/rpc \ + --actionChainIds 42161 \ + --actionAddresses \ + 0x86E93E21AD108CaE7ADe482C34C230Bfd94D4A8B \ + --writeToJsonPath ./scripts/proposals/sec-council-rotate/data.json \ No newline at end of file diff --git a/scripts/proposals/sec-council-upgrade-rotation/data.json b/scripts/proposals/sec-council-upgrade-rotation/data.json new file mode 100644 index 000000000..638211af6 --- /dev/null +++ b/scripts/proposals/sec-council-upgrade-rotation/data.json @@ -0,0 +1,12 @@ +{ + "actionChainIds": [ + 42161 + ], + "actionAddresses": [ + "0xeF98Fc7A7F08De47Ed01f3F11f07319c22106445" + ], + "arbSysSendTxToL1Args": { + "l1Timelock": "0xE6841D92B0C345144506576eC13ECf5103aC7f49", + "calldata": "0x8f2a0bb000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000000d078d01ffb5a3a5eac98137cbe898b2f21c1114069936d04a90b74ee9806e3f5000000000000000000000000000000000000000000000000000000000003f4800000000000000000000000000000000000000000000000000000000000000001000000000000000000000000a723c008e76e379c55599d2e4d93879beafda79c000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001800000000000000000000000004dbd4fc535ac27206064b68ffcf827b0a60bab3f000000000000000000000000cf57572261c7c2bcf21ffd220ea7d1a27d40a82700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000841cff79cd000000000000000000000000ef98fc7a7f08de47ed01f3f11f07319c2210644500000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000004b147f40c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + } +} \ No newline at end of file diff --git a/src/gov-action-contracts/AIPs/AIPNovaFeeRoutingAction.sol b/src/gov-action-contracts/AIPs/AIPNovaFeeRoutingAction.sol index 24e51852a..975096019 100644 --- a/src/gov-action-contracts/AIPs/AIPNovaFeeRoutingAction.sol +++ b/src/gov-action-contracts/AIPs/AIPNovaFeeRoutingAction.sol @@ -58,7 +58,9 @@ contract AIPNovaFeeRoutingAction { // upgrade executor should have at least 3 * fullWeight ETH to fund the distributors // we need each of the reward distributors to have at least fullWeight in balance // otherwise we may get NoFundsToDistribute() errors - require(address(this).balance >= 3 * fullWeight, "AIPNovaFeeRoutingAction: insufficient balance"); + require( + address(this).balance >= 3 * fullWeight, "AIPNovaFeeRoutingAction: insufficient balance" + ); _fundDistributor(novaL1SurplusFeeDistr); _fundDistributor(novaL2SurplusFeeDistr); _fundDistributor(novaL2BaseFeeDistr); @@ -134,7 +136,7 @@ contract AIPNovaFeeRoutingAction { } function _fundDistributor(address recipient) internal { - (bool b, ) = recipient.call{value: fullWeight}(""); + (bool b,) = recipient.call{value: fullWeight}(""); require(b, "AIPNovaFeeRoutingAction: funding failed"); } } diff --git a/src/gov-action-contracts/AIPs/SecurityCouncilMgmt/SecurityCouncilUpgradeAction.sol b/src/gov-action-contracts/AIPs/SecurityCouncilMgmt/SecurityCouncilUpgradeAction.sol new file mode 100644 index 000000000..e49da924d --- /dev/null +++ b/src/gov-action-contracts/AIPs/SecurityCouncilMgmt/SecurityCouncilUpgradeAction.sol @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.16; + +import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; +import "../../address-registries/L2AddressRegistryInterfaces.sol"; +import "@openzeppelin/contracts-upgradeable/access/IAccessControlUpgradeable.sol"; +import + "@openzeppelin/contracts-upgradeable/governance/extensions/GovernorVotesQuorumFractionUpgradeable.sol"; +import "../../../security-council-mgmt/governors/SecurityCouncilNomineeElectionGovernor.sol"; + +/// @notice Perform the following upgrade proposed by the Arbitrum Foundation: +/// - Upgrade the sec council manager to allow member rotation and sets min rotation vars +/// - Upgrade the sec council nominee election governor to allow modifying the cadence of election +/// - Adjusting the qualification threshold of the Member Election phase from 0.2% to 0.1% +/// - Updating the ArbitrumDAO Constitution to reflect these changes +contract SecurityCouncilUpgradeAction { + IL2AddressRegistry public immutable l2AddressRegistry; + address public immutable secCouncilManagerImpl; + address public immutable scNomineeElectionGovernorImpl; + uint256 public immutable minRotationPeriod; + address public immutable minRotationPeriodSetter; + uint256 public immutable cadenceInMonths; + bytes32 public immutable newConstitutionHash; + + constructor( + IL2AddressRegistry _l2AddressRegistry, + address _secCouncilManagerImpl, + address _scNomineeElectionGovernorImpl, + uint256 _minRotationPeriod, + address _minRotationPeriodSetter, + uint256 _cadenceInMonths, + bytes32 _newConstitutionHash + ) { + l2AddressRegistry = _l2AddressRegistry; + secCouncilManagerImpl = _secCouncilManagerImpl; + scNomineeElectionGovernorImpl = _scNomineeElectionGovernorImpl; + minRotationPeriod = _minRotationPeriod; + minRotationPeriodSetter = _minRotationPeriodSetter; + cadenceInMonths = _cadenceInMonths; + newConstitutionHash = _newConstitutionHash; + } + + function perform() external { + SecurityCouncilNomineeElectionGovernor scNomineeElectionGovernor = + SecurityCouncilNomineeElectionGovernor( + payable(address(l2AddressRegistry.scNomineeElectionGovernor())) + ); + require( + scNomineeElectionGovernor.electionCount() == 6, + "SecurityCouncilUpgradeAction: not expected timing" + ); + + // Upgrade the sec council manager to allow member rotation and sets min rotation vars + ISecurityCouncilManager secCouncilManager = l2AddressRegistry.securityCouncilManager(); + l2AddressRegistry.govProxyAdmin().upgradeAndCall( + TransparentUpgradeableProxy(payable(address(secCouncilManager))), + secCouncilManagerImpl, + abi.encodeCall( + ISecurityCouncilManager(secCouncilManagerImpl).postUpgradeInit, + (minRotationPeriod, minRotationPeriodSetter) + ) + ); + require( + minRotationPeriod == secCouncilManager.minRotationPeriod(), + "SecurityCouncilUpgradeAction: Min rotation period not set" + ); + require( + IAccessControlUpgradeable(address(secCouncilManager)).hasRole( + secCouncilManager.MIN_ROTATION_PERIOD_SETTER_ROLE(), minRotationPeriodSetter + ), + "SecurityCouncilUpgradeAction: Min rotation period setter not set" + ); + + // Upgrade the sec council nominee election governor to allow modifying the cadence of election + l2AddressRegistry.govProxyAdmin().upgradeAndCall( + TransparentUpgradeableProxy(payable(address(scNomineeElectionGovernor))), + scNomineeElectionGovernorImpl, + abi.encodeCall(scNomineeElectionGovernor.postUpgradeInit, ()) + ); + + scNomineeElectionGovernor.relay( + address(scNomineeElectionGovernor), + 0, + abi.encodeCall(scNomineeElectionGovernor.setCadence, (cadenceInMonths)) + ); + require( + scNomineeElectionGovernor.cadenceInMonths() == cadenceInMonths, + "SecurityCouncilUpgradeAction: Cadence not set" + ); + + // Adjusting the qualification threshold of the Member Election phase from 0.2% to 0.1% + scNomineeElectionGovernor.relay( + address(scNomineeElectionGovernor), + 0, + abi.encodeCall(scNomineeElectionGovernor.updateQuorumNumerator, (10)) + ); + require( + scNomineeElectionGovernor.quorumNumerator() == 10, + "SecurityCouncilUpgradeAction: Quorum numerator not set" + ); + + // Updating the ArbitrumDAO Constitution to reflect these changes + IArbitrumDAOConstitution arbitrumDaoConstitution = + l2AddressRegistry.arbitrumDAOConstitution(); + arbitrumDaoConstitution.setConstitutionHash(newConstitutionHash); + require( + arbitrumDaoConstitution.constitutionHash() == newConstitutionHash, + "SecurityCouncilUpgradeAction: new constitution hash not set" + ); + } +} diff --git a/src/gov-action-contracts/address-registries/L2AddressRegistry.sol b/src/gov-action-contracts/address-registries/L2AddressRegistry.sol index 87fbc2a86..a96baf424 100644 --- a/src/gov-action-contracts/address-registries/L2AddressRegistry.sol +++ b/src/gov-action-contracts/address-registries/L2AddressRegistry.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity 0.8.16; +import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; import "./L2AddressRegistryInterfaces.sol"; contract L2AddressRegistry is IL2AddressRegistry { @@ -8,12 +9,16 @@ contract L2AddressRegistry is IL2AddressRegistry { IL2ArbitrumGoverner public immutable treasuryGov; IFixedDelegateErc20Wallet public immutable treasuryWallet; IArbitrumDAOConstitution public immutable arbitrumDAOConstitution; + ProxyAdmin public immutable govProxyAdmin; + ISecurityCouncilNomineeElectionGovernor public immutable scNomineeElectionGovernor; constructor( IL2ArbitrumGoverner _coreGov, IL2ArbitrumGoverner _treasuryGov, IFixedDelegateErc20Wallet _treasuryWallet, - IArbitrumDAOConstitution _arbitrumDAOConstitution + IArbitrumDAOConstitution _arbitrumDAOConstitution, + ProxyAdmin _govProxyAdmin, + ISecurityCouncilNomineeElectionGovernor _scNomineeElectionGovernor ) { require( _treasuryWallet.owner() == _treasuryGov.timelock(), @@ -27,6 +32,13 @@ contract L2AddressRegistry is IL2AddressRegistry { treasuryGov = _treasuryGov; treasuryWallet = _treasuryWallet; arbitrumDAOConstitution = _arbitrumDAOConstitution; + require( + _govProxyAdmin.getProxyAdmin(TransparentUpgradeableProxy(payable(address(_coreGov)))) + == address(_govProxyAdmin), + "GovProxyAdmin must be proxy admin of the core governor" + ); + govProxyAdmin = _govProxyAdmin; + scNomineeElectionGovernor = _scNomineeElectionGovernor; } function coreGovTimelock() external view returns (IArbitrumTimelock) { @@ -40,4 +52,16 @@ contract L2AddressRegistry is IL2AddressRegistry { function l2ArbitrumToken() external view returns (IL2ArbitrumToken) { return IL2ArbitrumGoverner(address(coreGov)).token(); } + + function scMemberElectionGovernor() + external + view + returns (ISecurityCouncilMemberElectionGovernor) + { + return scNomineeElectionGovernor.securityCouncilMemberElectionGovernor(); + } + + function securityCouncilManager() external view returns (ISecurityCouncilManager) { + return scNomineeElectionGovernor.securityCouncilManager(); + } } diff --git a/src/gov-action-contracts/address-registries/L2AddressRegistryInterfaces.sol b/src/gov-action-contracts/address-registries/L2AddressRegistryInterfaces.sol index 54dab50f3..7205b3f2b 100644 --- a/src/gov-action-contracts/address-registries/L2AddressRegistryInterfaces.sol +++ b/src/gov-action-contracts/address-registries/L2AddressRegistryInterfaces.sol @@ -2,11 +2,15 @@ pragma solidity 0.8.16; import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; +import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; import "../../interfaces/IArbitrumTimelock.sol"; import "../../interfaces/IFixedDelegateErc20Wallet.sol"; import "../../interfaces/IL2ArbitrumToken.sol"; import "../../interfaces/IL2ArbitrumGovernor.sol"; import "../../interfaces/IArbitrumDAOConstitution.sol"; +import "../../security-council-mgmt/interfaces/ISecurityCouncilManager.sol"; +import "../../security-council-mgmt/interfaces/ISecurityCouncilNomineeElectionGovernor.sol"; +import "../../security-council-mgmt/interfaces/ISecurityCouncilMemberElectionGovernor.sol"; interface ICoreGovTimelockGetter { function coreGovTimelock() external view returns (IArbitrumTimelock); @@ -36,6 +40,22 @@ interface IArbitrumDAOConstitutionGetter { function arbitrumDAOConstitution() external view returns (IArbitrumDAOConstitution); } +interface IGovProxyAdminGetter { + function govProxyAdmin() external view returns (ProxyAdmin); +} + +interface ISecurityCouncilGetters { + function securityCouncilManager() external view returns (ISecurityCouncilManager); + function scNomineeElectionGovernor() + external + view + returns (ISecurityCouncilNomineeElectionGovernor); + function scMemberElectionGovernor() + external + view + returns (ISecurityCouncilMemberElectionGovernor); +} + interface IL2AddressRegistry is ICoreGovGetter, ICoreGovTimelockGetter, @@ -43,5 +63,7 @@ interface IL2AddressRegistry is IDaoTreasuryGetter, ITreasuryGovGetter, IL2ArbitrumTokenGetter, - IArbitrumDAOConstitutionGetter + IArbitrumDAOConstitutionGetter, + IGovProxyAdminGetter, + ISecurityCouncilGetters {} diff --git a/src/gov-action-contracts/governance/CancelTimelockAndRemoveMemberAction.sol b/src/gov-action-contracts/governance/CancelTimelockAndRemoveMemberAction.sol new file mode 100644 index 000000000..0d0c3e6da --- /dev/null +++ b/src/gov-action-contracts/governance/CancelTimelockAndRemoveMemberAction.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.16; + +import "../address-registries/L2AddressRegistry.sol"; +import "./CancelTimelockOperation.sol"; + +contract CancelTimelockAndRemoveMemberAction { + IL2AddressRegistry public immutable l2AddressRegistry; + + constructor(IL2AddressRegistry _l2AddressRegistry) { + l2AddressRegistry = _l2AddressRegistry; + } + + function perform(address memberToRemove, bytes32 operationId) public { + // first remove the council member + ISecurityCouncilManager scm = l2AddressRegistry.securityCouncilManager(); + IAccessControlUpgradeable(address(scm)).grantRole(scm.MEMBER_REMOVER_ROLE(), address(this)); + scm.removeMember(memberToRemove); + IAccessControlUpgradeable(address(scm)).revokeRole(scm.MEMBER_REMOVER_ROLE(), address(this)); + + // then cancel the rotation operation in the timelock + CancelTimelockOperation.cancel(l2AddressRegistry.coreGov(), operationId); + } +} diff --git a/src/interfaces/IArbitrumTimelock.sol b/src/interfaces/IArbitrumTimelock.sol index 89e4409ea..e17250c6d 100644 --- a/src/interfaces/IArbitrumTimelock.sol +++ b/src/interfaces/IArbitrumTimelock.sol @@ -13,4 +13,5 @@ interface IArbitrumTimelock { ) external; function getMinDelay() external view returns (uint256 duration); function updateDelay(uint256 newDelay) external; + function isOperation(bytes32 id) external view returns (bool registered); } diff --git a/src/security-council-mgmt/SecurityCouncilManager.sol b/src/security-council-mgmt/SecurityCouncilManager.sol index c657401a5..99388123a 100644 --- a/src/security-council-mgmt/SecurityCouncilManager.sol +++ b/src/security-council-mgmt/SecurityCouncilManager.sol @@ -11,7 +11,10 @@ import "../UpgradeExecRouteBuilder.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts/utils/Address.sol"; import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/governance/IGovernorUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/utils/cryptography/ECDSAUpgradeable.sol"; import "./Common.sol"; +import "./interfaces/ISecurityCouncilMemberElectionGovernor.sol"; /// @title The Security Council Manager /// @notice The source of truth for an array of Security Councils that are under management. @@ -46,6 +49,7 @@ contract SecurityCouncilManager is uint256 securityCouncilsLength ); event UpgradeExecRouteBuilderSet(address indexed UpgradeExecRouteBuilder); + event MinRotationPeriodSet(uint256 minRotationPeriod); // The Security Council members are separated into two cohorts, allowing a whole cohort to be replaced, as // specified by the Arbitrum Constitution. @@ -76,6 +80,24 @@ contract SecurityCouncilManager is /// @notice Size of cohort under ordinary circumstances uint256 public cohortSize; + /// @notice The timestamp at which the address was last rotated + mapping(address => uint256) public lastRotated; + + /// @notice If an address was rotated, this is the last address it rotated to + /// @dev This can be used to avoid race conditions between rotation and other actions + mapping(address => address) public rotatedTo; + + /// @inheritdoc ISecurityCouncilManager + uint256 public minRotationPeriod; + + /// @notice Nonce used when setting rotatedTo + mapping(address => uint256) public rotationNonce; + + /// @notice The 712 name hash + bytes32 public constant NAME_HASH = keccak256(bytes("SecurityCouncilManager")); + /// @notice The 712 version hash + bytes32 public constant VERSION_HASH = keccak256(bytes("1")); + /// @notice Magic value used by the L1 timelock to indicate that a retryable ticket should be created /// Value is defined in L1ArbitrumTimelock contract https://etherscan.io/address/0xE6841D92B0C345144506576eC13ECf5103aC7f49#readProxyContract#F5 address public constant RETRYABLE_TICKET_MAGIC = 0xa723C008e76E379c55599D2E4d93879BeaFDa79C; @@ -85,18 +107,27 @@ contract SecurityCouncilManager is bytes32 public constant MEMBER_REPLACER_ROLE = keccak256("MEMBER_REPLACER"); bytes32 public constant MEMBER_ROTATOR_ROLE = keccak256("MEMBER_ROTATOR"); bytes32 public constant MEMBER_REMOVER_ROLE = keccak256("MEMBER_REMOVER"); + bytes32 public constant MIN_ROTATION_PERIOD_SETTER_ROLE = + keccak256("MIN_ROTATION_PERIOD_SETTER"); + bytes32 public constant DOMAIN_TYPE_HASH = keccak256( + "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" + ); + bytes32 public constant ROTATE_MEMBER_TYPE_HASH = + keccak256(bytes("rotateMember(address from, uint256 nonce)")); constructor() { _disableInitializers(); } + /// @inheritdoc ISecurityCouncilManager function initialize( address[] memory _firstCohort, address[] memory _secondCohort, SecurityCouncilData[] memory _securityCouncils, SecurityCouncilManagerRoles memory _roles, address payable _l2CoreGovTimelock, - UpgradeExecRouteBuilder _router + UpgradeExecRouteBuilder _router, + uint256 _minRotationPeriod ) external initializer { if (_firstCohort.length != _secondCohort.length) { revert CohortLengthMismatch(_firstCohort, _secondCohort); @@ -112,6 +143,7 @@ contract SecurityCouncilManager is } _grantRole(MEMBER_ROTATOR_ROLE, _roles.memberRotator); _grantRole(MEMBER_REPLACER_ROLE, _roles.memberReplacer); + _grantRole(MIN_ROTATION_PERIOD_SETTER_ROLE, _roles.minRotationPeriodSetter); if (!Address.isContract(_l2CoreGovTimelock)) { revert NotAContract({account: _l2CoreGovTimelock}); @@ -122,6 +154,47 @@ contract SecurityCouncilManager is for (uint256 i = 0; i < _securityCouncils.length; i++) { _addSecurityCouncil(_securityCouncils[i]); } + + _setMinRotationPeriod(_minRotationPeriod); + } + + function getProxyAdmin() internal view returns (address admin) { + // https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.4.0/contracts/proxy/TransparentUpgradeableProxy.sol#L48 + // Storage slot with the admin of the proxy contract. + // This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1, and is + bytes32 slot = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; + assembly { + admin := sload(slot) + } + } + + function postUpgradeInit(uint256 _minRotationPeriod, address minRotationPeriodSetter) + external + { + require(msg.sender == getProxyAdmin(), "NOT_FROM_ADMIN"); + require(minRotationPeriod == 0, "MIN_ROTATION_ALREADY_SET"); + + _grantRole(MIN_ROTATION_PERIOD_SETTER_ROLE, minRotationPeriodSetter); + _setMinRotationPeriod(_minRotationPeriod); + } + + function _domainSeparatorV4() private view returns (bytes32) { + return keccak256( + abi.encode(DOMAIN_TYPE_HASH, NAME_HASH, VERSION_HASH, block.chainid, address(this)) + ); + } + + /// @inheritdoc ISecurityCouncilManager + function setMinRotationPeriod(uint256 _minRotationPeriod) + external + onlyRole(MIN_ROTATION_PERIOD_SETTER_ROLE) + { + _setMinRotationPeriod(_minRotationPeriod); + } + + function _setMinRotationPeriod(uint256 _minRotationPeriod) internal { + minRotationPeriod = _minRotationPeriod; + emit MinRotationPeriodSet(_minRotationPeriod); } /// @inheritdoc ISecurityCouncilManager @@ -160,6 +233,10 @@ contract SecurityCouncilManager is } cohort.push(_newMember); + // we use the rotatedTo mapping to ensure that a member is to be removed they cant rotate away from that + // however we assume that if a member is added after being rotated away, then the removal is actually targetting that member + // and not the one previously rotated away from, so we we wipe the rotation record + rotatedTo[_newMember] = address(0); } function _removeMemberFromCohortArray(address _member) internal returns (Cohort) { @@ -183,14 +260,24 @@ contract SecurityCouncilManager is emit MemberAdded(_newMember, _cohort); } + function memberRotatedTo(address _member) internal view returns (address) { + if (rotatedTo[_member] != address(0)) { + return rotatedTo[_member]; + } else { + return _member; + } + } + /// @inheritdoc ISecurityCouncilManager function removeMember(address _member) external onlyRole(MEMBER_REMOVER_ROLE) { if (_member == address(0)) { revert ZeroAddress(); } - Cohort cohort = _removeMemberFromCohortArray(_member); + address memberIfRotated = memberRotatedTo(_member); + + Cohort cohort = _removeMemberFromCohortArray(memberIfRotated); _scheduleUpdate(); - emit MemberRemoved({member: _member, cohort: cohort}); + emit MemberRemoved({member: memberIfRotated, cohort: cohort}); } /// @inheritdoc ISecurityCouncilManager @@ -198,25 +285,100 @@ contract SecurityCouncilManager is external onlyRole(MEMBER_REPLACER_ROLE) { - Cohort cohort = _swapMembers(_memberToReplace, _newMember); - emit MemberReplaced({ - replacedMember: _memberToReplace, - newMember: _newMember, - cohort: cohort - }); + address memberIfRotated = memberRotatedTo(_memberToReplace); + Cohort cohort = _swapMembers(memberIfRotated, _newMember); + emit MemberReplaced({replacedMember: memberIfRotated, newMember: _newMember, cohort: cohort}); } /// @inheritdoc ISecurityCouncilManager - function rotateMember(address _currentAddress, address _newAddress) - external - onlyRole(MEMBER_ROTATOR_ROLE) - { - Cohort cohort = _swapMembers(_currentAddress, _newAddress); - emit MemberRotated({ - replacedAddress: _currentAddress, - newAddress: _newAddress, - cohort: cohort - }); + function getRotateMemberHash(address from, uint256 nonce) public view returns (bytes32) { + return ECDSAUpgradeable.toTypedDataHash( + _domainSeparatorV4(), keccak256(abi.encode(ROTATE_MEMBER_TYPE_HASH, from, nonce)) + ); + } + + /// @inheritdoc ISecurityCouncilManager + function rotateMember( + address newMemberAddress, + address memberElectionGovernor, + bytes calldata signature + ) external { + uint256 lastRotatedTimestamp = lastRotated[msg.sender]; + if (lastRotatedTimestamp != 0 && block.timestamp < lastRotatedTimestamp + minRotationPeriod) + { + revert RotationTooSoon(msg.sender, lastRotatedTimestamp + minRotationPeriod); + } + // we enforce that a the new address is an eoa in the same way do + // in NomineeGovernor.addContender by requiring a signature + uint256 currentRotationNonce = rotationNonce[msg.sender]; + address newAddress = ECDSAUpgradeable.recover( + getRotateMemberHash(msg.sender, currentRotationNonce), signature + ); + // we safety check the new member address is the one that we expect to replace here + // this isn't strictly necessary but it guards agains the case where the wrong sig is accidentally used + if (newAddress != newMemberAddress) { + revert InvalidNewAddress(newAddress); + } + + // the cohort replacer should be the member election governor + // we don't explicitly store the member election governor in this manager + // so we pass it in here and verify it as having the correct role + // since cohort replacing can change any member it's already a trusted entity + if (!hasRole(COHORT_REPLACER_ROLE, memberElectionGovernor)) { + revert GovernorNotReplacer(); + } + // use the member election governor to get the nominee governor + // we we'll use that to check if there is a clash between the rotation and an ongoing election + ISecurityCouncilNomineeElectionGovernor nomineeGovernor = + ISecurityCouncilMemberElectionGovernor(memberElectionGovernor).nomineeElectionGovernor(); + // election count is incremented after proposal, so the current election is electionCount - 1 + // we use this to form the proposal id for that election, and then check isContender + uint256 electionCount = nomineeGovernor.electionCount(); + // if the election count is still zero then no elections have started or taken place + // in that case it is always valid to rotate a member as there can be non clash with contenders + if (electionCount != 0) { + uint256 currentElectionIndex = electionCount - 1; + ( + address[] memory targets, + uint256[] memory values, + bytes[] memory callDatas, + string memory description + ) = nomineeGovernor.getProposeArgs(currentElectionIndex); + uint256 proposalId = IGovernorUpgradeable(address(nomineeGovernor)).hashProposal( + targets, values, callDatas, keccak256(bytes(description)) + ); + + // there can only be a clash with an incoming member if there is + // a. an ongoing election + // b. the election is for the other cohort than the member being rotated + // c. the address is a contender in that ongoing election + IGovernorUpgradeable.ProposalState nomineePropState = + IGovernorUpgradeable(address(nomineeGovernor)).state(proposalId); + if ( + nomineePropState != IGovernorUpgradeable.ProposalState.Executed // the proposal is ongoing in nomination phase + || ( + nomineePropState == IGovernorUpgradeable.ProposalState.Executed // the proposal has passed nomination phase but is still in member selection phase + && IGovernorUpgradeable(memberElectionGovernor).state(proposalId) + != IGovernorUpgradeable.ProposalState.Executed + ) + ) { + Cohort otherCohort = nomineeGovernor.otherCohort(); + if (cohortIncludes(otherCohort, msg.sender)) { + if (nomineeGovernor.isContender(proposalId, newAddress)) { + revert NewMemberIsContender(proposalId, newAddress); + } + if (nomineeGovernor.isNominee(proposalId, newAddress)) { + revert NewMemberIsNominee(proposalId, newAddress); + } + } + } + } + + lastRotated[newAddress] = block.timestamp; + rotatedTo[msg.sender] = newAddress; + rotationNonce[msg.sender] = currentRotationNonce + 1; + Cohort cohort = _swapMembers(msg.sender, newAddress); + emit MemberRotated({replacedAddress: msg.sender, newAddress: newAddress, cohort: cohort}); } function _swapMembers(address _addressToRemove, address _addressToAdd) @@ -445,11 +607,11 @@ contract SecurityCouncilManager is delay: ArbitrumTimelock(l2CoreGovTimelock).getMinDelay() }); } + /** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ - - uint256[43] private __gap; + uint256[39] private __gap; } diff --git a/src/security-council-mgmt/factories/L2SecurityCouncilMgmtFactory.sol b/src/security-council-mgmt/factories/L2SecurityCouncilMgmtFactory.sol index 026158f0c..fd7eaadfb 100644 --- a/src/security-council-mgmt/factories/L2SecurityCouncilMgmtFactory.sol +++ b/src/security-council-mgmt/factories/L2SecurityCouncilMgmtFactory.sol @@ -41,6 +41,8 @@ struct DeployParams { uint256 nomineeVotingPeriod; uint256 memberVotingPeriod; uint256 fullWeightDuration; + uint256 minRotationPeriod; + address minRotationPeriodSetter; } struct ContractImplementations { @@ -152,7 +154,8 @@ contract L2SecurityCouncilMgmtFactory is Ownable { memberAdder: dp.govChainEmergencySecurityCouncil, memberRemovers: memberRemovers, memberRotator: dp.govChainEmergencySecurityCouncil, - memberReplacer: dp.govChainEmergencySecurityCouncil + memberReplacer: dp.govChainEmergencySecurityCouncil, + minRotationPeriodSetter: dp.minRotationPeriodSetter }); deployedContracts.upgradeExecRouteBuilder = new UpgradeExecRouteBuilder({ @@ -175,7 +178,8 @@ contract L2SecurityCouncilMgmtFactory is Ownable { _securityCouncils: dp.securityCouncils, _roles: roles, _l2CoreGovTimelock: payable(dp.l2CoreGovTimelock), - _router: deployedContracts.upgradeExecRouteBuilder + _router: deployedContracts.upgradeExecRouteBuilder, + _minRotationPeriod: dp.minRotationPeriod }); _initRemovalGov( diff --git a/src/security-council-mgmt/governors/SecurityCouncilMemberElectionGovernor.sol b/src/security-council-mgmt/governors/SecurityCouncilMemberElectionGovernor.sol index 13edde4e4..2ac217c8d 100644 --- a/src/security-council-mgmt/governors/SecurityCouncilMemberElectionGovernor.sol +++ b/src/security-council-mgmt/governors/SecurityCouncilMemberElectionGovernor.sol @@ -25,7 +25,7 @@ contract SecurityCouncilMemberElectionGovernor is ElectionGovernor, ISecurityCouncilMemberElectionGovernor { - /// @notice The SecurityCouncilNomineeElectionGovernor that creates proposals for this governor and contains the list of compliant nominees + /// @inheritdoc ISecurityCouncilMemberElectionGovernor ISecurityCouncilNomineeElectionGovernor public nomineeElectionGovernor; /// @notice The SecurityCouncilManager that will execute the election result diff --git a/src/security-council-mgmt/governors/SecurityCouncilNomineeElectionGovernor.sol b/src/security-council-mgmt/governors/SecurityCouncilNomineeElectionGovernor.sol index f660fa2d7..dfbc90106 100644 --- a/src/security-council-mgmt/governors/SecurityCouncilNomineeElectionGovernor.sol +++ b/src/security-council-mgmt/governors/SecurityCouncilNomineeElectionGovernor.sol @@ -35,7 +35,7 @@ contract SecurityCouncilNomineeElectionGovernor is /// @param owner Owner of the governor (the Arbitrum DAO) /// @param quorumNumeratorValue Numerator of the quorum fraction (0.2% = 20) /// @param votingPeriod Duration of the voting period (expressed in blocks) - /// Note that the voting period + nominee vetting duration must be << than 6 months to ensure elections dont overlap + /// Note that the voting period + nominee vetting duration must be << than the set cadence (`cadenceInMonths`) to ensure elections dont overlap struct InitParams { Date firstNominationStartDate; uint256 nomineeVettingDuration; @@ -58,17 +58,22 @@ contract SecurityCouncilNomineeElectionGovernor is uint256 excludedNomineeCount; } + /// @notice Nominees can rotate their position to a new address. They are allowed to do this during the vetting period, but no later than `ROTATION_CUT_OFF_BLOCKS` L1 blocks before the vetting deadline. + /// Currently this is set to 3 days, assuming 12 blocks per second. + /// @dev It is known that a malicious nominee can abuse rotation to avoid vetting, + /// but the nominee vetter would always have 3 extra days after any rotation to exclude the nominee if needed. + uint256 public constant ROTATION_CUT_OFF_BLOCKS = 21_600; + /// @notice Address responsible for blocking non compliant nominees address public nomineeVetter; - /// @notice Security council manager contract - /// @dev Used to execute the election result immediately if <= 6 compliant nominees are chosen + /// @inheritdoc ISecurityCouncilNomineeElectionGovernor ISecurityCouncilManager public securityCouncilManager; - /// @notice Security council member election governor contract + /// @inheritdoc ISecurityCouncilNomineeElectionGovernor ISecurityCouncilMemberElectionGovernor public securityCouncilMemberElectionGovernor; - /// @notice Number of elections created + /// @inheritdoc ISecurityCouncilNomineeElectionGovernor uint256 public electionCount; /// @notice Maps proposalId to ElectionInfo @@ -77,6 +82,7 @@ contract SecurityCouncilNomineeElectionGovernor is event NomineeVetterChanged(address indexed oldNomineeVetter, address indexed newNomineeVetter); event ContenderAdded(uint256 indexed proposalId, address indexed contender); event NomineeExcluded(uint256 indexed proposalId, address indexed nominee); + event NomineeRotated(uint256 indexed proposalId, address indexed from, address indexed to); error OnlyNomineeVetter(); error CreateTooEarly(uint256 blockTimestamp, uint256 startTime); @@ -85,23 +91,47 @@ contract SecurityCouncilNomineeElectionGovernor is error AccountInOtherCohort(Cohort cohort, address account); error ProposalNotSucceededState(ProposalState state); error ProposalNotInVettingPeriod(uint256 blockNumber, uint256 vettingDeadline); + error ProposalNotInRotationPeriod(uint256 blockNumber, uint256 rotationDeadline); error NomineeAlreadyExcluded(address nominee); + error OnlyContenderNomineeCanRotate(); + error NewNomineeIsContender(address newNominee); error CompliantNomineeTargetHit(uint256 nomineeCount, uint256 expectedCount); error ProposalInVettingPeriod(uint256 blockNumber, uint256 vettingDeadline); error InsufficientCompliantNomineeCount(uint256 compliantNomineeCount, uint256 expectedCount); error ProposeDisabled(); error NotNominee(address nominee); + error NotCompliantNominee(address nominee); error ProposalIdMismatch(uint256 nomineeProposalId, uint256 memberProposalId); error QuorumNumeratorTooLow(uint256 quorumNumeratorValue); error CastVoteDisabled(); error LastMemberElectionNotExecuted(uint256 prevProposalId); error InvalidSignature(); error Deprecated(string message); + error NotFromProxyAdmin(); constructor() { _disableInitializers(); } + function getProxyAdmin() internal view returns (address admin) { + // https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.4.0/contracts/proxy/TransparentUpgradeableProxy.sol#L48 + // Storage slot with the admin of the proxy contract. + // This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1, and is + bytes32 slot = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; + assembly { + admin := sload(slot) + } + } + + function postUpgradeInit() external { + if (msg.sender != getProxyAdmin()) { + revert NotFromProxyAdmin(); + } + if (cadenceInMonths == 0) { + cadenceInMonths = 6; + } + } + /// @notice Initializes the governor function initialize(InitParams memory params) public initializer { __Governor_init("SecurityCouncilNomineeElectionGovernor"); @@ -159,7 +189,7 @@ contract SecurityCouncilNomineeElectionGovernor is } /// @notice Creates a new nominee election proposal. - /// Can be called by anyone every 6 months. + /// Can be called by anyone every `cadenceInMonths` months. /// @return proposalId The id of the proposal function createElection() external returns (uint256 proposalId) { // require that the last member election has executed @@ -183,6 +213,18 @@ contract SecurityCouncilNomineeElectionGovernor is electionCount++; } + function _requireNotInOtherCohort(address account) internal view { + // check to make sure the contender is not part of the other cohort (the cohort not currently up for election) + // this only checks against the current cohort membership of the security council, + // so changes to those will mean this check will be inconsistent. + // this check then is only a relevant check when the elections are running as expected - one at a time, + // every `cadenceInMonths` months. Updates to the sec council manager using methods other than replaceCohort can effect this check + // and it's expected that the entity making those updates understands this. + if (securityCouncilManager.cohortIncludes(otherCohort(), account)) { + revert AccountInOtherCohort(otherCohort(), account); + } + } + /// @dev Revert if the previous member election has not executed. /// Ensures that there are no unexpected behaviors from multiple elections running at the same time. /// If, for some reason, the previous member election is blocked, @@ -233,16 +275,7 @@ contract SecurityCouncilNomineeElectionGovernor is revert ProposalNotPending(state_); } - // check to make sure the contender is not part of the other cohort (the cohort not currently up for election) - // this only checks against the current the current other cohort, and against the current cohort membership - // in the security council, so changes to those will mean this check will be inconsistent. - // this check then is only a relevant check when the elections are running as expected - one at a time, - // every 6 months. Updates to the sec council manager using methods other than replaceCohort can effect this check - // and it's expected that the entity making those updates understands this. - if (securityCouncilManager.cohortIncludes(otherCohort(), signer)) { - revert AccountInOtherCohort(otherCohort(), signer); - } - + _requireNotInOtherCohort(signer); election.isContender[signer] = true; emit ContenderAdded(proposalId, signer); @@ -266,6 +299,12 @@ contract SecurityCouncilNomineeElectionGovernor is AddressUpgradeable.functionCallWithValue(target, data, value); } + /// @notice Set the cadence for future elections + /// @param numberOfMonths The new cadence in months (must be >= 1) + function setCadence(uint256 numberOfMonths) external onlyGovernance { + _setCadence(numberOfMonths, electionCount); + } + /// @notice Allows the nomineeVetter to exclude a noncompliant nominee. /// @dev Can be called only after a nominee election proposal has "succeeded" (voting has ended) and before the nominee vetting period has ended. /// Will revert if the provided account is not a nominee (had less than the required votes). @@ -303,27 +342,58 @@ contract SecurityCouncilNomineeElectionGovernor is revert ProposalNotSucceededState(state_); } - if (isNominee(proposalId, account)) { - revert NomineeAlreadyAdded(account); - } - uint256 cnCount = compliantNomineeCount(proposalId); uint256 cohortSize = securityCouncilManager.cohortSize(); if (cnCount >= cohortSize) { revert CompliantNomineeTargetHit(cnCount, cohortSize); } - // can't include nominees from the other cohort (the cohort not currently up for election) - // this only checks against the current the current other cohort, and against the current cohort membership - // in the security council, so changes to those will mean this check will be inconsistent. - // this check then is only a relevant check when the elections are running as expected - one at a time, - // every 6 months. Updates to the sec council manager using methods other than replaceCohort can effect this check - // and it's expected that the entity making those updates understands this. - if (securityCouncilManager.cohortIncludes(otherCohort(), account)) { - revert AccountInOtherCohort(otherCohort(), account); + _requireNotInOtherCohort(account); + _addNominee(proposalId, account); + } + + /// @notice Allows a nominee to rotate their position to a new address + /// @param proposalId The id of the proposal + /// @param newNomineeAddress The new address to rotate to + /// @param signature A signature from the new member address over the 712 rotateNominee hash + function rotateNominee(uint256 proposalId, address newNomineeAddress, bytes calldata signature) + external + { + ElectionInfo storage election = _elections[proposalId]; + + if (!isCompliantNominee(proposalId, msg.sender)) { + revert NotCompliantNominee(msg.sender); } - _addNominee(proposalId, account); + uint256 rotationDeadline = proposalVettingDeadline(proposalId) - ROTATION_CUT_OFF_BLOCKS; + if (block.number > rotationDeadline) { + revert ProposalNotInRotationPeriod(block.number, rotationDeadline); + } + + if (election.isExcluded[newNomineeAddress]) { + revert NomineeAlreadyExcluded(newNomineeAddress); + } + + address signer = recoverRotateNomineeMessage(proposalId, signature, msg.sender); + if (signer != newNomineeAddress) { + revert InvalidSignature(); + } + + if (!isContender(proposalId, msg.sender)) { + revert OnlyContenderNomineeCanRotate(); + } + + if (isContender(proposalId, newNomineeAddress)) { + revert NewNomineeIsContender(newNomineeAddress); + } + + // rotation by first excluding the nominee and then adding the new nominee + election.isExcluded[msg.sender] = true; + election.excludedNomineeCount++; + _requireNotInOtherCohort(newNomineeAddress); + _addNominee(proposalId, newNomineeAddress); + emit NomineeExcluded(proposalId, msg.sender); + emit NomineeRotated(proposalId, msg.sender, newNomineeAddress); } /// @dev `GovernorUpgradeable` function to execute a proposal overridden to handle nominee elections. @@ -423,7 +493,10 @@ contract SecurityCouncilNomineeElectionGovernor is public view virtual - override + override( + ISecurityCouncilNomineeElectionGovernor, + SecurityCouncilNomineeElectionGovernorCountingUpgradeable + ) returns (bool) { return _elections[proposalId].isContender[possibleContender]; @@ -441,6 +514,23 @@ contract SecurityCouncilNomineeElectionGovernor is return ECDSAUpgradeable.recover(digest, signature); } + function recoverRotateNomineeMessage(uint256 proposalId, bytes calldata signature, address from) + public + view + returns (address) + { + bytes32 digest = _hashTypedDataV4( + keccak256( + abi.encode( + keccak256("RotateNomineeMessage(uint256 proposalId, address from)"), + proposalId, + from + ) + ) + ); + return ECDSAUpgradeable.recover(digest, signature); + } + /// @notice Always reverts. /// @dev `GovernorUpgradeable` function to create a proposal overridden to just revert. /// We only want proposals to be created via `createElection`. @@ -493,15 +583,6 @@ contract SecurityCouncilNomineeElectionGovernor is ); } - /// @notice Deprecated, use `addContender(uint256 proposalId, bytes calldata signature)` instead - /// @dev This function is deprecated because contenders should only be EOA's that can produce signatures. - /// If a security council member's address is not an EOA, then they may be unable to sign on all relevant chains. - function addContender(uint256) external pure { - revert Deprecated( - "addContender(uint256 proposalId) has been deprecated. Use addContender(uint256 proposalId, bytes calldata signature) instead" - ); - } - /** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. diff --git a/src/security-council-mgmt/governors/modules/ElectionGovernor.sol b/src/security-council-mgmt/governors/modules/ElectionGovernor.sol index 291ee2228..a174c7ba1 100644 --- a/src/security-council-mgmt/governors/modules/ElectionGovernor.sol +++ b/src/security-council-mgmt/governors/modules/ElectionGovernor.sol @@ -5,9 +5,10 @@ pragma solidity 0.8.16; import "@openzeppelin/contracts-upgradeable/utils/StringsUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/governance/GovernorUpgradeable.sol"; import "../../Common.sol"; +import "../../interfaces/IElectionGovernor.sol"; /// @notice Common functionality used by nominee and member election governors -abstract contract ElectionGovernor is GovernorUpgradeable { +abstract contract ElectionGovernor is GovernorUpgradeable, IElectionGovernor { /// @notice When a vote is cast using a signature we store a hash of the vote data /// so that the signature cannot be replayed mapping(bytes32 => bool) public usedNonces; @@ -54,12 +55,7 @@ abstract contract ElectionGovernor is GovernorUpgradeable { return _castVote(proposalId, voter, support, reason, params); } - /// @notice Generate arguments to be passed to the governor propose function - /// @param electionIndex The index of the election to create a proposal for - /// @return Targets - /// @return Values - /// @return Calldatas - /// @return Description + /// @inheritdoc IElectionGovernor function getProposeArgs(uint256 electionIndex) public pure diff --git a/src/security-council-mgmt/governors/modules/SecurityCouncilNomineeElectionGovernorCountingUpgradeable.sol b/src/security-council-mgmt/governors/modules/SecurityCouncilNomineeElectionGovernorCountingUpgradeable.sol index 25a568f7f..efbe43206 100644 --- a/src/security-council-mgmt/governors/modules/SecurityCouncilNomineeElectionGovernorCountingUpgradeable.sol +++ b/src/security-council-mgmt/governors/modules/SecurityCouncilNomineeElectionGovernorCountingUpgradeable.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity 0.8.16; +import "../../interfaces/ISecurityCouncilNomineeElectionGovernor.sol"; import "@openzeppelin/contracts-upgradeable/governance/GovernorUpgradeable.sol"; /// @title SecurityCouncilNomineeElectionGovernorCountingUpgradeable @@ -9,7 +10,8 @@ import "@openzeppelin/contracts-upgradeable/governance/GovernorUpgradeable.sol"; /// Voters can spread votes across multiple contenders abstract contract SecurityCouncilNomineeElectionGovernorCountingUpgradeable is Initializable, - GovernorUpgradeable + GovernorUpgradeable, + ISecurityCouncilNomineeElectionGovernorCountingUpgradeable { /// @param votesUsed The amount of votes a voter has used /// @param votesReceived The amount of votes a contender has received @@ -119,6 +121,9 @@ abstract contract SecurityCouncilNomineeElectionGovernorCountingUpgradeable is /// @dev Transitions an account to being a nominee function _addNominee(uint256 proposalId, address account) internal { + if (isNominee(proposalId, account)) { + revert NomineeAlreadyAdded(account); + } _elections[proposalId].nominees.push(account); _elections[proposalId].isNominee[account] = true; emit NewNominee(proposalId, account); @@ -134,7 +139,7 @@ abstract contract SecurityCouncilNomineeElectionGovernorCountingUpgradeable is return _elections[proposalId].votesUsed[account] > 0; } - /// @notice Whether the contender has enough votes to be a nominee + /// @inheritdoc ISecurityCouncilNomineeElectionGovernorCountingUpgradeable function isNominee(uint256 proposalId, address contender) public view returns (bool) { return _elections[proposalId].isNominee[contender]; } diff --git a/src/security-council-mgmt/governors/modules/SecurityCouncilNomineeElectionGovernorTiming.sol b/src/security-council-mgmt/governors/modules/SecurityCouncilNomineeElectionGovernorTiming.sol index c8fd05662..e17de3fbd 100644 --- a/src/security-council-mgmt/governors/modules/SecurityCouncilNomineeElectionGovernorTiming.sol +++ b/src/security-council-mgmt/governors/modules/SecurityCouncilNomineeElectionGovernorTiming.sol @@ -13,15 +13,28 @@ abstract contract SecurityCouncilNomineeElectionGovernorTiming is Initializable, GovernorUpgradeable { - /// @notice First election start date + /// @notice This is the first election start date only if the first election is yet to be created Date public firstNominationStartDate; /// @notice Duration of the nominee vetting period (expressed in blocks) /// @dev This is the amount of time after voting ends that the nomineeVetter can exclude noncompliant nominees uint256 public nomineeVettingDuration; + /// @notice The cadence of elections in months + uint256 public cadenceInMonths; + + event CadenceChanged( + uint256 newCadence, + uint256 nextElectionYear, + uint256 nextElectionMonth, + uint256 nextElectionDay, + uint256 nextElectionHour + ); + error InvalidStartDate(uint256 year, uint256 month, uint256 day, uint256 hour); error StartDateTooEarly(uint256 startTime, uint256 currentTime); + error InvalidCadence(uint256 cadence); + error NextElectionTooSoon(uint256 nextElectionTimestamp, uint256 currentTimestamp); /// @notice Initialize the timing module /// @dev Checks to make sure the start date is in the future and is valid @@ -63,6 +76,7 @@ abstract contract SecurityCouncilNomineeElectionGovernorTiming is firstNominationStartDate = _firstNominationStartDate; nomineeVettingDuration = _nomineeVettingDuration; + cadenceInMonths = 6; // Default to 6 months } /// @notice Deadline for the nominee vetting period for a given `proposalId` @@ -70,13 +84,71 @@ abstract contract SecurityCouncilNomineeElectionGovernorTiming is return proposalDeadline(proposalId) + nomineeVettingDuration; } + /// @notice Set the cadence for future elections + /// @param numberOfMonths The new cadence in months (must be >= 1) + /// @param currentElectionCount The current number of elections + /// @dev Internal function to be called by the main governor contract + function _setCadence(uint256 numberOfMonths, uint256 currentElectionCount) internal { + if (numberOfMonths == 0) { + revert InvalidCadence(numberOfMonths); + } + + // If no elections have been created yet, just update the cadence + if (currentElectionCount == 0) { + cadenceInMonths = numberOfMonths; + emit CadenceChanged( + numberOfMonths, + firstNominationStartDate.year, + firstNominationStartDate.month, + firstNominationStartDate.day, + firstNominationStartDate.hour + ); + return; + } + + // Calculate what the next election timestamp should be (last + new cadence) + uint256 nextElectionTimestamp; + { + // Calculate the timestamp of the last election + uint256 lastElectionTimestamp = electionToTimestamp(currentElectionCount - 1); + + nextElectionTimestamp = DateTimeLib.addMonths(lastElectionTimestamp, numberOfMonths); + (uint256 _year, uint256 _month, uint256 _day, uint256 _hour,,) = + DateTimeLib.timestampToDateTime(nextElectionTimestamp); + + // we emit the event here to save some stack space + emit CadenceChanged(numberOfMonths, _year, _month, _day, _hour); + } + + // Ensure the next election won't be moved to the past + if (nextElectionTimestamp < block.timestamp) { + revert NextElectionTooSoon(nextElectionTimestamp, block.timestamp); + } + + // Calculate the new firstNominationStartDate that would make election at currentElectionCount + // occur at nextElectionTimestamp with the new cadence + // nextElectionTimestamp = newFirstDate + (currentElectionCount * numberOfMonths) + // So: newFirstDate = nextElectionTimestamp - (currentElectionCount * numberOfMonths) + + // Work backwards from the next election timestamp + uint256 monthsToSubtract = numberOfMonths * currentElectionCount; + uint256 offsetTimestamp = DateTimeLib.subMonths(nextElectionTimestamp, monthsToSubtract); + (uint256 year, uint256 month, uint256 day, uint256 hour,,) = + DateTimeLib.timestampToDateTime(offsetTimestamp); + + // Update the firstNominationStartDate and cadence + firstNominationStartDate = Date({year: year, month: month, day: day, hour: hour}); + cadenceInMonths = numberOfMonths; + } + /// @notice Start timestamp of an election + /// Only returns accurate timestamps for the last and upcoming elections after cadence changes /// @param electionIndex The index of the election function electionToTimestamp(uint256 electionIndex) public view returns (uint256) { // subtract one to make month 0 indexed uint256 month = firstNominationStartDate.month - 1; - month += 6 * electionIndex; + month += cadenceInMonths * electionIndex; uint256 year = firstNominationStartDate.year + month / 12; month = month % 12; @@ -98,5 +170,5 @@ abstract contract SecurityCouncilNomineeElectionGovernorTiming is * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ - uint256[45] private __gap; + uint256[44] private __gap; } diff --git a/src/security-council-mgmt/interfaces/IElectionGovernor.sol b/src/security-council-mgmt/interfaces/IElectionGovernor.sol new file mode 100644 index 000000000..d898bcfe6 --- /dev/null +++ b/src/security-council-mgmt/interfaces/IElectionGovernor.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.16; + +interface IElectionGovernor { + /// @notice Generate arguments to be passed to the governor propose function + /// @param electionIndex The index of the election to create a proposal for + /// @return Targets + /// @return Values + /// @return Calldatas + /// @return Description + function getProposeArgs(uint256 electionIndex) + external + pure + returns (address[] memory, uint256[] memory, bytes[] memory, string memory); +} diff --git a/src/security-council-mgmt/interfaces/ISecurityCouncilManager.sol b/src/security-council-mgmt/interfaces/ISecurityCouncilManager.sol index 076eadfa8..c5ed75f96 100644 --- a/src/security-council-mgmt/interfaces/ISecurityCouncilManager.sol +++ b/src/security-council-mgmt/interfaces/ISecurityCouncilManager.sol @@ -12,6 +12,7 @@ struct SecurityCouncilManagerRoles { address[] memberRemovers; address memberRotator; address memberReplacer; + address minRotationPeriodSetter; } /// @notice Data for a Security Council to be managed @@ -40,6 +41,22 @@ interface ISecurityCouncilManager { error SecurityCouncilNotInManager(SecurityCouncilData securiyCouncilData); error SecurityCouncilAlreadyInRouter(SecurityCouncilData securiyCouncilData); + // rotation errors + error RotationTooSoon(address rotator, uint256 rotatableWhen); + error GovernorNotReplacer(); + error NewMemberIsContender(uint256 proposalId, address newMember); + error NewMemberIsNominee(uint256 proposalId, address newMember); + error InvalidNewAddress(address newAddress); + + function rotatedTo(address) external view returns (address); + function rotationNonce(address) external view returns (uint256); + + /// @notice There is a minimum period between when an address can be rotated + /// This is to ensure a single member cannot do many rotations in a row + function minRotationPeriod() external view returns (uint256); + function MIN_ROTATION_PERIOD_SETTER_ROLE() external view returns (bytes32); + function MEMBER_REMOVER_ROLE() external view returns (bytes32); + /// @notice initialize SecurityCouncilManager. /// @param _firstCohort addresses of first cohort /// @param _secondCohort addresses of second cohort @@ -47,48 +64,66 @@ interface ISecurityCouncilManager { /// @param _roles permissions for triggering modifications to security councils /// @param _l2CoreGovTimelock timelock for core governance / constitutional proposal /// @param _router UpgradeExecRouteBuilder address + /// @param _minRotationPeriod The minimum amount of time that must happen between address rotations by the same council member + /// Rotations are in race conditions with other actions, so care must be taken to set this parameter to be + /// greater than the time taken for other actions. An example of this is if the removal governor has the removal + /// role it may try to remove an address, but doing so requires passing a vote and in the meantime the address may + /// rotate. If the address is only allowed to rotate once during this period the manager can keep track of this and still + /// and still remove the address, however if the rotation period allows for two rotations the address will not get removed + /// A general rule for setting the min rotation period is: make sure it is longer that the amount of time taken to conduct + /// any other actions on the sec council manager. function initialize( address[] memory _firstCohort, address[] memory _secondCohort, SecurityCouncilData[] memory _securityCouncils, SecurityCouncilManagerRoles memory _roles, address payable _l2CoreGovTimelock, - UpgradeExecRouteBuilder _router + UpgradeExecRouteBuilder _router, + uint256 _minRotationPeriod ) external; + /// @notice Set the min rotation period. This is the minimum period that must occur + /// between two consecutive rotations by the same member + /// @param _minRotationPeriod The new minimum rotation period to be set + function setMinRotationPeriod(uint256 _minRotationPeriod) external; /// @notice Replaces a whole cohort. - /// @dev Initiaties cross chain messages to update the individual Security Councils. + /// @dev Initiates cross chain messages to update the individual Security Councils. /// @param _newCohort New cohort members to replace existing cohort. Must have 6 members. /// @param _cohort Cohort to replace. function replaceCohort(address[] memory _newCohort, Cohort _cohort) external; /// @notice Add a member to the specified cohort. /// Cohorts cannot have more than 6 members, so the cohort must have less than 6 in order to call this. /// New member cannot already be a member of either cohort. - /// @dev Initiaties cross chain messages to update the individual Security Councils. + /// @dev Initiates cross chain messages to update the individual Security Councils. /// When adding a member, make sure that the key does not conflict with any contenders/nominees of ongoing elections. /// @param _newMember New member to add /// @param _cohort Cohort to add member to function addMember(address _newMember, Cohort _cohort) external; /// @notice Remove a member. /// @dev Searches both cohorts for the member. - /// Initiaties cross chain messages to update the individual Security Councils + /// Initiates cross chain messages to update the individual Security Councils /// @param _member Member to remove function removeMember(address _member) external; /// @notice Replace a member in a council - equivalent to removing a member, then adding another in its place. - /// Idendities of members should be different. - /// Functionality is equivalent to replaceMember, - /// though emits a different event to distinguish the security council's intent (different identities). - /// @dev Initiaties cross chain messages to update the individual Security Councils. + /// @dev Initiates cross chain messages to update the individual Security Councils. /// When replacing a member, make sure that the key does not conflict with any contenders/nominees of ongoing electoins. /// @param _memberToReplace Security Council member to remove /// @param _newMember Security Council member to add in their place function replaceMember(address _memberToReplace, address _newMember) external; - /// @notice Security council member can rotate out their address for a new one; _currentAddress and _newAddress should be of the same identity. Functionality is equivalent to replaceMember, tho emits a different event to distinguish the security council's intent (same identity). - /// Rotation must be initiated by the security council. - /// @dev Initiaties cross chain messages to update the individual Security Councils. - /// When rotating a member, make sure that the key does not conflict with any contenders/nominees of ongoing elections. - /// @param _currentAddress Address to rotate out - /// @param _newAddress Address to rotate in - function rotateMember(address _currentAddress, address _newAddress) external; + /// @notice Get the hash to be signed for an existing member rotation + /// @param from The address that will be rotated out. Included in the hash so that other members cant use this message to rotate their address + /// @param nonce The message nonce. Must be equal to the rotationNonce for the member being rotated out + function getRotateMemberHash(address from, uint256 nonce) external view returns (bytes32); + /// @notice Security council member can rotate out their address for a new one + /// @dev Initiates cross chain messages to update the individual Security Councils. + /// Cannot rotate to a contender in an ongoing election, as this could cause a clash that would stop the election result executing + /// @param newMemberAddress The new member address to be rotated to + /// @param memberElectionGovernor The current member election governor - must have the COHORT_REPLACER_ROLE role + /// @param signature A signature from the new member address over the 712 rotateMember hash + function rotateMember( + address newMemberAddress, + address memberElectionGovernor, + bytes calldata signature + ) external; /// @notice Is the account a member of the first cohort function firstCohortIncludes(address account) external view returns (bool); /// @notice Is the account a member of the second cohort @@ -134,4 +169,7 @@ interface ISecurityCouncilManager { returns (bytes32); /// @notice Each update increments an internal nonce that keeps updates unique, current value stored here function updateNonce() external returns (uint256); + /// @notice Upgrade an existing contract and add rotation params + function postUpgradeInit(uint256 _minRotationPeriod, address minRotationPeriodSetter) + external; } diff --git a/src/security-council-mgmt/interfaces/ISecurityCouncilMemberElectionGovernor.sol b/src/security-council-mgmt/interfaces/ISecurityCouncilMemberElectionGovernor.sol index c52959502..c1eaf1ca9 100644 --- a/src/security-council-mgmt/interfaces/ISecurityCouncilMemberElectionGovernor.sol +++ b/src/security-council-mgmt/interfaces/ISecurityCouncilMemberElectionGovernor.sol @@ -1,7 +1,11 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity 0.8.16; +import "./ISecurityCouncilNomineeElectionGovernor.sol"; + interface ISecurityCouncilMemberElectionGovernor { /// @notice Creates a new member election proposal from the most recent nominee election. function proposeFromNomineeElectionGovernor(uint256 electionIndex) external returns (uint256); + /// @notice The SecurityCouncilNomineeElectionGovernor that creates proposals for this governor and contains the list of compliant nominees + function nomineeElectionGovernor() external returns (ISecurityCouncilNomineeElectionGovernor); } diff --git a/src/security-council-mgmt/interfaces/ISecurityCouncilNomineeElectionGovernor.sol b/src/security-council-mgmt/interfaces/ISecurityCouncilNomineeElectionGovernor.sol index aadaf74ad..c88452eff 100644 --- a/src/security-council-mgmt/interfaces/ISecurityCouncilNomineeElectionGovernor.sol +++ b/src/security-council-mgmt/interfaces/ISecurityCouncilNomineeElectionGovernor.sol @@ -1,8 +1,21 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity 0.8.16; +import "./IElectionGovernor.sol"; +import {Cohort} from "../Common.sol"; +import "./ISecurityCouncilMemberElectionGovernor.sol"; +import "./ISecurityCouncilManager.sol"; + +interface ISecurityCouncilNomineeElectionGovernorCountingUpgradeable { + /// @notice Whether the contender has enough votes to be a nominee + function isNominee(uint256 proposalId, address contender) external view returns (bool); +} + /// @notice Minimal interface of nominee election governor required by other contracts -interface ISecurityCouncilNomineeElectionGovernor { +interface ISecurityCouncilNomineeElectionGovernor is + IElectionGovernor, + ISecurityCouncilNomineeElectionGovernorCountingUpgradeable +{ /// @notice Whether the account a compliant nominee for a given proposal /// A compliant nominee is one who is a nominee, and has not been excluded /// @param proposalId The id of the proposal @@ -11,4 +24,20 @@ interface ISecurityCouncilNomineeElectionGovernor { /// @notice All compliant nominees of a given proposal /// A compliant nominee is one who is a nominee, and has not been excluded function compliantNominees(uint256 proposalId) external view returns (address[] memory); + /// @notice Number of elections created + function electionCount() external returns (uint256); + /// @notice Whether the account is a contender for the proposal + function isContender(uint256 proposalId, address possibleContender) + external + view + returns (bool); + function otherCohort() external view returns (Cohort); + /// @notice Security council manager contract + /// @dev Used to execute the election result immediately if <= 6 compliant nominees are chosen + function securityCouncilManager() external view returns (ISecurityCouncilManager); + /// @notice Security council member election governor contract + function securityCouncilMemberElectionGovernor() + external + view + returns (ISecurityCouncilMemberElectionGovernor); } diff --git a/test/gov-actions/CancelTimelockAndRemoveMemberActionTest.t.sol b/test/gov-actions/CancelTimelockAndRemoveMemberActionTest.t.sol new file mode 100644 index 000000000..eb513922f --- /dev/null +++ b/test/gov-actions/CancelTimelockAndRemoveMemberActionTest.t.sol @@ -0,0 +1,151 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.16; + +import "forge-std/Test.sol"; + +import "../../src/gov-action-contracts/AIPs/SecurityCouncilMgmt/SecurityCouncilUpgradeAction.sol"; +import "../../src/gov-action-contracts/governance/CancelTimelockAndRemoveMemberAction.sol"; +import "../../src/security-council-mgmt/SecurityCouncilManager.sol"; +import "../../src/gov-action-contracts/address-registries/L2AddressRegistry.sol"; + +contract CancelTimelockAndRemoveMemberActionTest is Test { + address oldImplementation = 0x468dA0eE5570Bdb1Dd81bFd925BAf028A93Dce64; + ProxyAdmin proxyAdmin = ProxyAdmin(0xdb216562328215E010F819B5aBe947bad4ca961e); + address council = 0x423552c0F05baCCac5Bfa91C6dCF1dc53a0A1641; + UpgradeExecutor arbOneUe = UpgradeExecutor(0xCF57572261c7c2BCF21ffD220ea7d1a27D40A827); + IArbitrumDAOConstitution constitution = + IArbitrumDAOConstitution(0x1D62fFeB72e4c360CcBbacf7c965153b00260417); + + function setUp() public { + string memory arbRpc = vm.envOr("ARB_RPC_URL", string("")); + if (bytes(arbRpc).length != 0) { + vm.createSelectFork(arbRpc); + } + } + + function testAction() external { + if (!_isForkTest()) { + console.log("not fork test, skipping SecurityCouncilUpgradeActionTest"); + return; + } + + // we need to deploy a new registry + L2AddressRegistry reg = new L2AddressRegistry( + IL2ArbitrumGoverner(0xf07DeD9dC292157749B6Fd268E37DF6EA38395B9), + IL2ArbitrumGoverner(0x789fC99093B09aD01C34DC7251D0C89ce743e5a4), + IFixedDelegateErc20Wallet(0xF3FC178157fb3c87548bAA86F9d24BA38E649B58), + constitution, + proxyAdmin, + ISecurityCouncilNomineeElectionGovernor(0x8a1cDA8dee421cD06023470608605934c16A05a0) + ); + // ensure that the scm has been updated + ensureLatestScm(reg); + + // rotate one of the members + ISecurityCouncilManager scm = reg.securityCouncilManager(); + address[] memory fc = scm.getFirstCohort(); + assertEq(fc.length, 6, "Not 6 addresses in first cohort"); + address memberOut = fc[2]; + uint256 memberInKey = 137; + address memberIn = vm.addr(memberInKey); + + // sign the rotation hash + bytes memory sig; + { + (uint8 v, bytes32 r, bytes32 s) = vm.sign( + memberInKey, scm.getRotateMemberHash(memberOut, scm.rotationNonce(memberOut)) + ); + sig = abi.encodePacked(r, s, v); + } + + vm.recordLogs(); + address memberElectionGov = address(reg.scMemberElectionGovernor()); + vm.prank(memberOut); + scm.rotateMember(memberIn, memberElectionGov, sig); + + // use the event to get the data we need for cancelling + // we do minimal checks here since we know what the transaction looked + // like in a live situation more verification would need to be done to ensure + // the correct proposal id and member to remove + address memberToRemove; + bytes32 proposalId; + Vm.Log[] memory logs = vm.getRecordedLogs(); + for (uint256 i = 0; i < logs.length; i++) { + if (logs[i].emitter == address(scm)) { + // first log is the member rotation + // event MemberRotated(address indexed replacedAddress, address indexed newAddress, Cohort cohort); + memberToRemove = address(uint160(uint256(logs[i].topics[2]))); + } else if (logs[i].emitter == address(reg.coreGovTimelock())) { + // second log is call scheduled + // event CallScheduled( + // bytes32 indexed id, + // uint256 indexed index, + // address target, + // uint256 value, + // bytes data, + // bytes32 predecessor, + // uint256 delay + // ); + proposalId = logs[i].topics[1]; + } else { + revert("Unrecognised log"); + } + } + assertTrue(reg.coreGovTimelock().isOperation(proposalId), "Prop does not exist"); + + CancelTimelockAndRemoveMemberAction action = new CancelTimelockAndRemoveMemberAction(reg); + vm.prank(council); + arbOneUe.execute( + address(action), abi.encodeCall(action.perform, (memberToRemove, proposalId)) + ); + + address[] memory fc1 = scm.getFirstCohort(); + assertEq(fc1.length, 5, "Not 5 addresses in first cohort"); + assertFalse(reg.coreGovTimelock().isOperation(proposalId), "Prop does not exist"); + for (uint256 i = 0; i < 6; i++) { + if (i == 0 || i == 1 || i == 3 || i == 4) { + assertEq(fc[i], fc1[i]); + } else if (i == 2) { + // do nothing this has been removed + } else if (i == 5) { + // last place has moved to 2 + assertEq(fc[i], fc1[2]); + } else { + revert("Unexpected case"); + } + } + } + + function ensureLatestScm(L2AddressRegistry reg) internal { + ISecurityCouncilManager scm = reg.securityCouncilManager(); + if ( + proxyAdmin.getProxyImplementation(TransparentUpgradeableProxy(payable(address(scm)))) + != oldImplementation + ) { + return; // already upgraded on-chain + } + + address newImplementation = address(new SecurityCouncilManager()); + address newNomineeElectionGovernorImplementation = + address(new SecurityCouncilNomineeElectionGovernor()); + address rotationSetter = address(1337); + uint256 minRotationPeriod = 1 weeks; + uint256 cadenceInMonths = 12; + + SecurityCouncilUpgradeAction action = new SecurityCouncilUpgradeAction( + reg, + newImplementation, + newNomineeElectionGovernorImplementation, + minRotationPeriod, + rotationSetter, + cadenceInMonths, + bytes32(0) + ); + vm.prank(council); + arbOneUe.execute(address(action), abi.encodeWithSelector(action.perform.selector)); + } + + function _isForkTest() internal view returns (bool) { + return address(arbOneUe).code.length > 0; + } +} diff --git a/test/gov-actions/SecurityCouncilUpgradeAction.t.sol b/test/gov-actions/SecurityCouncilUpgradeAction.t.sol new file mode 100644 index 000000000..48455b333 --- /dev/null +++ b/test/gov-actions/SecurityCouncilUpgradeAction.t.sol @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.16; + +import "forge-std/Test.sol"; + +import "../../src/gov-action-contracts/AIPs/SecurityCouncilMgmt/SecurityCouncilUpgradeAction.sol"; +import "../../src/security-council-mgmt/SecurityCouncilManager.sol"; +import "../../src/security-council-mgmt/governors/SecurityCouncilNomineeElectionGovernor.sol"; +import "../../src/gov-action-contracts/address-registries/L2AddressRegistry.sol"; + +contract SecurityCouncilUpgradeActionTest is Test { + SecurityCouncilManager scm = SecurityCouncilManager(0xD509E5f5aEe2A205F554f36E8a7d56094494eDFC); + address oldImplementation = 0x468dA0eE5570Bdb1Dd81bFd925BAf028A93Dce64; + ProxyAdmin proxyAdmin = ProxyAdmin(0xdb216562328215E010F819B5aBe947bad4ca961e); + address council = 0x423552c0F05baCCac5Bfa91C6dCF1dc53a0A1641; + UpgradeExecutor arbOneUe = UpgradeExecutor(0xCF57572261c7c2BCF21ffD220ea7d1a27D40A827); + IArbitrumDAOConstitution constitution = + IArbitrumDAOConstitution(0x1D62fFeB72e4c360CcBbacf7c965153b00260417); + + function setUp() public { + string memory arbRpc = vm.envOr("ARB_RPC_URL", string("")); + if (bytes(arbRpc).length != 0) { + vm.createSelectFork(arbRpc); + } + } + + function testAction() external { + if (!_isForkTest()) { + console.log("not fork test, skipping RotateMembersUpgradeActionTest"); + return; + } + + if (_getImplementation() != oldImplementation) { + console.log( + "implementation not set to old implementation, skipping RotateMembersUpgradeActionTest" + ); + return; + } + + // we need to deploy a new registry + L2AddressRegistry reg = new L2AddressRegistry( + IL2ArbitrumGoverner(0xf07DeD9dC292157749B6Fd268E37DF6EA38395B9), + IL2ArbitrumGoverner(0x789fC99093B09aD01C34DC7251D0C89ce743e5a4), + IFixedDelegateErc20Wallet(0xF3FC178157fb3c87548bAA86F9d24BA38E649B58), + constitution, + proxyAdmin, + ISecurityCouncilNomineeElectionGovernor(0x8a1cDA8dee421cD06023470608605934c16A05a0) + ); + + SecurityCouncilNomineeElectionGovernor scNomineeElectionGovernor = + SecurityCouncilNomineeElectionGovernor(payable(address(reg.scNomineeElectionGovernor()))); + + address newImplementation = address(new SecurityCouncilManager()); + address newNomineeElectionGovernorImplementation = + address(new SecurityCouncilNomineeElectionGovernor()); + address rotationSetter = address(137); + uint256 minRotationPeriod = 1 weeks; + uint256 cadenceInMonths = 12; + SecurityCouncilUpgradeAction action = new SecurityCouncilUpgradeAction( + reg, + newImplementation, + newNomineeElectionGovernorImplementation, + minRotationPeriod, + rotationSetter, + cadenceInMonths, + bytes32(0) + ); + vm.prank(council); + arbOneUe.execute(address(action), abi.encodeWithSelector(action.perform.selector)); + + assertEq(scm.minRotationPeriod(), minRotationPeriod, "min rotation period"); + assertTrue( + IAccessControlUpgradeable(address(scm)).hasRole( + scm.MIN_ROTATION_PERIOD_SETTER_ROLE(), rotationSetter + ), + "Min rotation period setter not set" + ); + assertEq(_getImplementation(), newImplementation, "implementation not set"); + + uint256 electionCount = scNomineeElectionGovernor.electionCount(); + assertEq( + scNomineeElectionGovernor.electionToTimestamp(electionCount), + 1_805_112_000, + "not March 15, 2027 12:00:00 PM" + ); + } + + function _getImplementation() internal view returns (address) { + return proxyAdmin.getProxyImplementation(TransparentUpgradeableProxy(payable(address(scm)))); + } + + function _isForkTest() internal view returns (bool) { + return address(scm).code.length > 0; + } +} diff --git a/test/security-council-mgmt/.gas-snapshot b/test/security-council-mgmt/.gas-snapshot new file mode 100644 index 000000000..de93dd32b --- /dev/null +++ b/test/security-council-mgmt/.gas-snapshot @@ -0,0 +1,250 @@ +AIP1Point2ActionTest:testAction() (gas: 629328) +AIPNovaFeeRoutingActionTest:testAction() (gas: 3074) +ArbitrumDAOConstitutionTest:testConstructor() (gas: 259383) +ArbitrumDAOConstitutionTest:testMonOwnerCannotSetHash() (gas: 262836) +ArbitrumDAOConstitutionTest:testOwnerCanSetHash() (gas: 261148) +ArbitrumDAOConstitutionTest:testOwnerCanSetHashTwice() (gas: 263824) +ArbitrumFoundationVestingWalletTest:testBeneficiaryCanSetBeneficiary() (gas: 16332093) +ArbitrumFoundationVestingWalletTest:testMigrateEthToNewWalletWithSlowerVesting() (gas: 19243747) +ArbitrumFoundationVestingWalletTest:testMigrateTokensToNewWalletWithFasterVesting() (gas: 19247090) +ArbitrumFoundationVestingWalletTest:testMigrateTokensToNewWalletWithSlowerVesting() (gas: 19247035) +ArbitrumFoundationVestingWalletTest:testMigrationTargetMustBeContract() (gas: 16335426) +ArbitrumFoundationVestingWalletTest:testOnlyBeneficiaryCanRelease() (gas: 16327408) +ArbitrumFoundationVestingWalletTest:testOnlyOwnerCanMigrate() (gas: 16329757) +ArbitrumFoundationVestingWalletTest:testOwnerCanSetBeneficiary() (gas: 16332176) +ArbitrumFoundationVestingWalletTest:testProperlyInits() (gas: 16337546) +ArbitrumFoundationVestingWalletTest:testRandomAddressCantSetBeneficiary() (gas: 16329656) +ArbitrumFoundationVestingWalletTest:testRelease() (gas: 16451131) +ArbitrumVestingWalletFactoryTest:testDeploy() (gas: 4589688) +ArbitrumVestingWalletFactoryTest:testOnlyOwnerCanCreateWallets() (gas: 1504286) +ArbitrumVestingWalletTest:testCastVote() (gas: 16201584) +ArbitrumVestingWalletTest:testCastVoteFailsForNonBeneficiary() (gas: 16151341) +ArbitrumVestingWalletTest:testClaim() (gas: 16007768) +ArbitrumVestingWalletTest:testClaimFailsForNonBeneficiary() (gas: 15967955) +ArbitrumVestingWalletTest:testDelegate() (gas: 16081106) +ArbitrumVestingWalletTest:testDelegateFailsForNonBeneficiary() (gas: 16008435) +ArbitrumVestingWalletTest:testDoesDeploy() (gas: 15971342) +ArbitrumVestingWalletTest:testReleaseAffordance() (gas: 16008649) +ArbitrumVestingWalletTest:testVestedAmountStart() (gas: 16074917) +E2E:testE2E() (gas: 85785140) +FixedDelegateErc20WalletTest:testInit() (gas: 5822575) +FixedDelegateErc20WalletTest:testInitZeroToken() (gas: 5816805) +FixedDelegateErc20WalletTest:testTransfer() (gas: 5932218) +FixedDelegateErc20WalletTest:testTransferNotOwner() (gas: 5897843) +InboxActionsTest:testPauseAndUpauseInbox() (gas: 370454) +L1AddressRegistryTest:testAddressRegistryAddress() (gas: 47009) +L1ArbitrumTimelockTest:testCancel() (gas: 5324642) +L1ArbitrumTimelockTest:testCancelFailsBadSender() (gas: 5369529) +L1ArbitrumTimelockTest:testDoesDeploy() (gas: 5273077) +L1ArbitrumTimelockTest:testDoesNotDeployZeroInbox() (gas: 4978961) +L1ArbitrumTimelockTest:testDoesNotDeployZeroL2Timelock() (gas: 4976931) +L1ArbitrumTimelockTest:testExecute() (gas: 5405352) +L1ArbitrumTimelockTest:testExecuteInbox() (gas: 5746378) +L1ArbitrumTimelockTest:testExecuteInboxBatch() (gas: 6056741) +L1ArbitrumTimelockTest:testExecuteInboxInvalidData() (gas: 5426399) +L1ArbitrumTimelockTest:testExecuteInboxNotEnoughVal() (gas: 5446210) +L1ArbitrumTimelockTest:testSchedule() (gas: 5357782) +L1ArbitrumTimelockTest:testScheduleFailsBadL2Timelock() (gas: 5286095) +L1ArbitrumTimelockTest:testScheduleFailsBadSender() (gas: 5281079) +L1ArbitrumTokenTest:testBridgeBurn() (gas: 3395571) +L1ArbitrumTokenTest:testBridgeBurnNotGateway() (gas: 3389611) +L1ArbitrumTokenTest:testBridgeMint() (gas: 3390798) +L1ArbitrumTokenTest:testBridgeMintNotGateway() (gas: 3341036) +L1ArbitrumTokenTest:testInit() (gas: 3355939) +L1ArbitrumTokenTest:testInitZeroGateway() (gas: 3177234) +L1ArbitrumTokenTest:testInitZeroNovaGateway() (gas: 3177301) +L1ArbitrumTokenTest:testInitZeroNovaRouter() (gas: 3177235) +L1ArbitrumTokenTest:testRegisterTokenOnL2() (gas: 4568612) +L1ArbitrumTokenTest:testRegisterTokenOnL2NotEnoughVal() (gas: 4425799) +L1GovernanceFactoryTest:testL1GovernanceFactory() (gas: 10771109) +L1GovernanceFactoryTest:testSetMinDelay() (gas: 10746003) +L1GovernanceFactoryTest:testSetMinDelayRevertsForCoreAddress() (gas: 10798958) +L2AddressRegistryTest:testAddressRegistryAddress() (gas: 54658) +L2ArbitrumGovernorTest:testCantReinit() (gas: 13669489) +L2ArbitrumGovernorTest:testExecutorPermissions() (gas: 13706483) +L2ArbitrumGovernorTest:testExecutorPermissionsFail() (gas: 13679135) +L2ArbitrumGovernorTest:testPastCirculatingSupply() (gas: 13673238) +L2ArbitrumGovernorTest:testPastCirculatingSupplyExclude() (gas: 13812715) +L2ArbitrumGovernorTest:testPastCirculatingSupplyMint() (gas: 13737218) +L2ArbitrumGovernorTest:testProperlyInitialized() (gas: 13664706) +L2ArbitrumTokenTest:testCanBurn() (gas: 4066835) +L2ArbitrumTokenTest:testCanMint2Percent() (gas: 4101512) +L2ArbitrumTokenTest:testCanMintLessThan2Percent() (gas: 4101514) +L2ArbitrumTokenTest:testCanMintTwiceWithWarp() (gas: 8190691) +L2ArbitrumTokenTest:testCanMintZero() (gas: 4081635) +L2ArbitrumTokenTest:testCanTransferAndCallContract() (gas: 4211883) +L2ArbitrumTokenTest:testCanTransferAndCallEmpty() (gas: 4096932) +L2ArbitrumTokenTest:testCannotMintMoreThan2Percent() (gas: 4071458) +L2ArbitrumTokenTest:testCannotMintNotOwner() (gas: 4069341) +L2ArbitrumTokenTest:testCannotMintTwice() (gas: 8158921) +L2ArbitrumTokenTest:testCannotMintWithoutFastForward() (gas: 4069700) +L2ArbitrumTokenTest:testCannotTransferAndCallNonReceiver() (gas: 4094203) +L2ArbitrumTokenTest:testCannotTransferAndCallReverter() (gas: 4154761) +L2ArbitrumTokenTest:testDoesNotInitialiseZeroInitialSup() (gas: 3800718) +L2ArbitrumTokenTest:testDoesNotInitialiseZeroL1Token() (gas: 3800726) +L2ArbitrumTokenTest:testDoesNotInitialiseZeroOwner() (gas: 3800739) +L2ArbitrumTokenTest:testIsInitialised() (gas: 4072777) +L2ArbitrumTokenTest:testNoLogicContractInit() (gas: 2693127) +L2GovernanceFactoryTest:testContractsDeployed() (gas: 28359365) +L2GovernanceFactoryTest:testContractsInitialized() (gas: 28396315) +L2GovernanceFactoryTest:testDeploySteps() (gas: 28370874) +L2GovernanceFactoryTest:testProxyAdminOwnership() (gas: 28368375) +L2GovernanceFactoryTest:testRoles() (gas: 28391450) +L2GovernanceFactoryTest:testSanityCheckValues() (gas: 28415658) +L2GovernanceFactoryTest:testSetMinDelay() (gas: 28364371) +L2GovernanceFactoryTest:testSetMinDelayRevertsForCoreAddress() (gas: 28417242) +L2GovernanceFactoryTest:testUpgraderCanCancel() (gas: 28657360) +L2SecurityCouncilMgmtFactoryTest:testMemberElectionGovDeployment() (gas: 31628990) +L2SecurityCouncilMgmtFactoryTest:testNomineeElectionGovDeployment() (gas: 31633221) +L2SecurityCouncilMgmtFactoryTest:testOnlyOwnerCanDeploy() (gas: 26672005) +L2SecurityCouncilMgmtFactoryTest:testRemovalGovDeployment() (gas: 31631221) +L2SecurityCouncilMgmtFactoryTest:testSecurityCouncilManagerDeployment() (gas: 31652101) +NomineeGovernorV2UpgradeActionTest:testAction() (gas: 8153) +OutboxActionsTest:testAddOutbxesAction() (gas: 651398) +OutboxActionsTest:testCantAddEOA() (gas: 968968) +OutboxActionsTest:testCantReAddOutbox() (gas: 974344) +OutboxActionsTest:testRemoveAllOutboxes() (gas: 693007) +OutboxActionsTest:testRemoveOutboxes() (gas: 853882) +ProxyUpgradeAndCallActionTest:testUpgrade() (gas: 137095) +ProxyUpgradeAndCallActionTest:testUpgradeAndCall() (gas: 143042) +SecurityCouncilManagerTest:testAddMemberAffordances() (gas: 249787) +SecurityCouncilManagerTest:testAddMemberSpecialAddresses() (gas: 20800) +SecurityCouncilManagerTest:testAddMemberToFirstCohort() (gas: 340022) +SecurityCouncilManagerTest:testAddMemberToSecondCohort() (gas: 343319) +SecurityCouncilManagerTest:testAddSC() (gas: 118677) +SecurityCouncilManagerTest:testAddSCAffordances() (gas: 112133) +SecurityCouncilManagerTest:testCantUpdateCohortWithADup() (gas: 123130) +SecurityCouncilManagerTest:testCohortMethods() (gas: 136182) +SecurityCouncilManagerTest:testInitialization() (gas: 201641) +SecurityCouncilManagerTest:testPostUpgradeInit() (gas: 5074706) +SecurityCouncilManagerTest:testRemoveMember() (gas: 213142) +SecurityCouncilManagerTest:testRemoveMemberAffordances() (gas: 99080) +SecurityCouncilManagerTest:testRemoveSCAffordances() (gas: 81331) +SecurityCouncilManagerTest:testRemoveSeC() (gas: 38350) +SecurityCouncilManagerTest:testReplaceMemberAffordances() (gas: 208648) +SecurityCouncilManagerTest:testReplaceMemberInFirstCohort() (gas: 258948) +SecurityCouncilManagerTest:testReplaceMemberInSecondCohort() (gas: 262487) +SecurityCouncilManagerTest:testRotateMember() (gas: 557872) +SecurityCouncilManagerTest:testRotateMemberNotContender() (gas: 3587319) +SecurityCouncilManagerTest:testSetMinRotationPeriod() (gas: 65822) +SecurityCouncilManagerTest:testUpdateCohortAffordances() (gas: 83057) +SecurityCouncilManagerTest:testUpdateFirstCohort() (gas: 295419) +SecurityCouncilManagerTest:testUpdateRouter() (gas: 76302) +SecurityCouncilManagerTest:testUpdateRouterAffordances() (gas: 112336) +SecurityCouncilManagerTest:testUpdateSecondCohort() (gas: 295468) +SecurityCouncilMemberElectionGovernorTest:testCannotUseMoreVotesThanAvailable() (gas: 246997) +SecurityCouncilMemberElectionGovernorTest:testCastBySig() (gas: 302852) +SecurityCouncilMemberElectionGovernorTest:testCastBySigTwice() (gas: 266244) +SecurityCouncilMemberElectionGovernorTest:testCastVoteReverts() (gas: 35277) +SecurityCouncilMemberElectionGovernorTest:testExecute() (gas: 665450) +SecurityCouncilMemberElectionGovernorTest:testForceSupport() (gas: 165349) +SecurityCouncilMemberElectionGovernorTest:testInitReverts() (gas: 4922497) +SecurityCouncilMemberElectionGovernorTest:testInvalidParams() (gas: 165321) +SecurityCouncilMemberElectionGovernorTest:testMiscVotesViews() (gas: 227939) +SecurityCouncilMemberElectionGovernorTest:testNoVoteForNonCompliantNominee() (gas: 123524) +SecurityCouncilMemberElectionGovernorTest:testNoZeroWeightVotes() (gas: 169595) +SecurityCouncilMemberElectionGovernorTest:testOnlyNomineeElectionGovernorCanPropose() (gas: 111038) +SecurityCouncilMemberElectionGovernorTest:testProperInitialization() (gas: 49388) +SecurityCouncilMemberElectionGovernorTest:testProposeReverts() (gas: 32916) +SecurityCouncilMemberElectionGovernorTest:testRelay() (gas: 42229) +SecurityCouncilMemberElectionGovernorTest:testSelectTopNominees(uint256) (runs: 256, μ: 340178, ~: 340008) +SecurityCouncilMemberElectionGovernorTest:testSelectTopNomineesFails() (gas: 273335) +SecurityCouncilMemberElectionGovernorTest:testSetFullWeightDuration() (gas: 34951) +SecurityCouncilMemberElectionGovernorTest:testVotesToWeight() (gas: 152898) +SecurityCouncilMemberRemovalGovernorTest:testInitFails() (gas: 10159193) +SecurityCouncilMemberRemovalGovernorTest:testProposalCreationCallParamRestriction() (gas: 56157) +SecurityCouncilMemberRemovalGovernorTest:testProposalCreationCallRestriction() (gas: 49685) +SecurityCouncilMemberRemovalGovernorTest:testProposalCreationTargetLen() (gas: 35392) +SecurityCouncilMemberRemovalGovernorTest:testProposalCreationTargetRestriction() (gas: 46987) +SecurityCouncilMemberRemovalGovernorTest:testProposalCreationUnexpectedCallDataLen() (gas: 41583) +SecurityCouncilMemberRemovalGovernorTest:testProposalCreationValuesRestriction() (gas: 61908) +SecurityCouncilMemberRemovalGovernorTest:testProposalDoesExpire() (gas: 272525) +SecurityCouncilMemberRemovalGovernorTest:testProposalExpirationDeadline() (gas: 134831) +SecurityCouncilMemberRemovalGovernorTest:testRelay() (gas: 42123) +SecurityCouncilMemberRemovalGovernorTest:testSeparateSelector() (gas: 23536) +SecurityCouncilMemberRemovalGovernorTest:testSetVoteSuccessNumerator() (gas: 30049) +SecurityCouncilMemberRemovalGovernorTest:testSetVoteSuccessNumeratorAffordance() (gas: 47631) +SecurityCouncilMemberRemovalGovernorTest:testSuccessNumeratorInsufficientVotes() (gas: 358327) +SecurityCouncilMemberRemovalGovernorTest:testSuccessNumeratorSufficientVotes() (gas: 361245) +SecurityCouncilMemberRemovalGovernorTest:testSuccessfulProposalAndCantAbstain() (gas: 142674) +SecurityCouncilMemberSyncActionTest:testAddOne() (gas: 7938503) +SecurityCouncilMemberSyncActionTest:testAddOne() (gas: 7939341) +SecurityCouncilMemberSyncActionTest:testCantDropBelowThreshhold() (gas: 7965404) +SecurityCouncilMemberSyncActionTest:testCantDropBelowThreshhold() (gas: 7965411) +SecurityCouncilMemberSyncActionTest:testGetPrevOwner() (gas: 7929385) +SecurityCouncilMemberSyncActionTest:testGetPrevOwner() (gas: 7929385) +SecurityCouncilMemberSyncActionTest:testNonces() (gas: 8229875) +SecurityCouncilMemberSyncActionTest:testNoopUpdate() (gas: 7928439) +SecurityCouncilMemberSyncActionTest:testNoopUpdate() (gas: 7929365) +SecurityCouncilMemberSyncActionTest:testRemoveOne() (gas: 7929685) +SecurityCouncilMemberSyncActionTest:testRemoveOne() (gas: 7930546) +SecurityCouncilMemberSyncActionTest:testUpdateCohort() (gas: 8171934) +SecurityCouncilMemberSyncActionTest:testUpdateCohort() (gas: 8172795) +SecurityCouncilMgmtUtilsTests:testIsInArray() (gas: 2102) +SecurityCouncilNomineeElectionGovernorTest:testAddContender() (gas: 270750) +SecurityCouncilNomineeElectionGovernorTest:testCastBySig() (gas: 333730) +SecurityCouncilNomineeElectionGovernorTest:testCastBySigTwice() (gas: 296589) +SecurityCouncilNomineeElectionGovernorTest:testCastVoteReverts() (gas: 35278) +SecurityCouncilNomineeElectionGovernorTest:testCountVote() (gas: 582574) +SecurityCouncilNomineeElectionGovernorTest:testCreateElection() (gas: 253153) +SecurityCouncilNomineeElectionGovernorTest:testExcludeNominee() (gas: 456505) +SecurityCouncilNomineeElectionGovernorTest:testExecute() (gas: 677159) +SecurityCouncilNomineeElectionGovernorTest:testForceSupport() (gas: 194733) +SecurityCouncilNomineeElectionGovernorTest:testIncludeNominee() (gas: 674020) +SecurityCouncilNomineeElectionGovernorTest:testInvalidInit() (gas: 7256741) +SecurityCouncilNomineeElectionGovernorTest:testProperInitialization() (gas: 78113) +SecurityCouncilNomineeElectionGovernorTest:testProposeFails() (gas: 19740) +SecurityCouncilNomineeElectionGovernorTest:testRelay() (gas: 42427) +SecurityCouncilNomineeElectionGovernorTest:testSetNomineeVetter() (gas: 39905) +SequencerActionsTest:testAddAndRemoveSequencer() (gas: 483532) +SequencerActionsTest:testCantAddZeroAddress() (gas: 235614) +SetInitialGovParamsActionTest:testL1() (gas: 259904) +SetInitialGovParamsActionTest:testL2() (gas: 688888) +SetSequencerInboxMaxTimeVariationAction:testSetMaxTimeVariation() (gas: 374262) +SwitchManagerRolesActionTest:testAction() (gas: 6313) +TokenDistributorTest:testClaim() (gas: 5742744) +TokenDistributorTest:testClaimAndDelegate() (gas: 5850827) +TokenDistributorTest:testClaimAndDelegateFailsForExpired() (gas: 5748244) +TokenDistributorTest:testClaimAndDelegateFailsForWrongSender() (gas: 5803385) +TokenDistributorTest:testClaimAndDelegateFailsWrongNonce() (gas: 5803386) +TokenDistributorTest:testClaimFailsAfterEnd() (gas: 5704035) +TokenDistributorTest:testClaimFailsBeforeStart() (gas: 5703530) +TokenDistributorTest:testClaimFailsForFalseTransfer() (gas: 5686246) +TokenDistributorTest:testClaimFailsForTwice() (gas: 5741504) +TokenDistributorTest:testClaimFailsForUnknown() (gas: 5706111) +TokenDistributorTest:testClaimStartAfterClaimEnd() (gas: 4134838) +TokenDistributorTest:testDoesDeploy() (gas: 5339553) +TokenDistributorTest:testDoesDeployAndDeposit() (gas: 5404583) +TokenDistributorTest:testOldClaimStart() (gas: 4135401) +TokenDistributorTest:testSetRecipients() (gas: 5701945) +TokenDistributorTest:testSetRecipientsFailsNotEnoughDeposit() (gas: 5668810) +TokenDistributorTest:testSetRecipientsFailsNotOwner() (gas: 5420359) +TokenDistributorTest:testSetRecipientsFailsWhenAddingTwice() (gas: 5712988) +TokenDistributorTest:testSetRecipientsFailsWrongAmountCount() (gas: 5421819) +TokenDistributorTest:testSetRecipientsFailsWrongRecipientCount() (gas: 5422048) +TokenDistributorTest:testSetRecipientsTwice() (gas: 6391525) +TokenDistributorTest:testSetSweepReceiver() (gas: 5706262) +TokenDistributorTest:testSetSweepReceiverFailsNullAddress() (gas: 5703881) +TokenDistributorTest:testSetSweepReceiverFailsOwner() (gas: 5704842) +TokenDistributorTest:testSweep() (gas: 5751971) +TokenDistributorTest:testSweepAfterClaim() (gas: 5789954) +TokenDistributorTest:testSweepFailsBeforeClaimPeriodEnd() (gas: 5703615) +TokenDistributorTest:testSweepFailsForFailedTransfer() (gas: 5707314) +TokenDistributorTest:testSweepFailsTwice() (gas: 5750930) +TokenDistributorTest:testWithdraw() (gas: 5741198) +TokenDistributorTest:testWithdrawFailsNotOwner() (gas: 5741220) +TokenDistributorTest:testWithdrawFailsTransfer() (gas: 5705817) +TokenDistributorTest:testZeroDelegateTo() (gas: 4132733) +TokenDistributorTest:testZeroOwner() (gas: 4132646) +TokenDistributorTest:testZeroReceiver() (gas: 4132675) +TokenDistributorTest:testZeroToken() (gas: 71889) +TopNomineesGasTest:testTopNomineesGas() (gas: 4502996) +UpgradeExecRouteBuilderTest:testAIP1Point2() (gas: 1322645) +UpgradeExecRouteBuilderTest:testRouteBuilderErrors() (gas: 1127374) +UpgradeExecutorTest:testAdminCanChangeExecutor() (gas: 2583801) +UpgradeExecutorTest:testCantExecuteEOA() (gas: 2439721) +UpgradeExecutorTest:testExecute() (gas: 2677995) +UpgradeExecutorTest:testExecuteFailsForAdmin() (gas: 2663614) +UpgradeExecutorTest:testExecuteFailsForNobody() (gas: 2665855) +UpgradeExecutorTest:testInit() (gas: 2427602) +UpgradeExecutorTest:testInitFailsZeroAdmin() (gas: 2288342) \ No newline at end of file diff --git a/test/security-council-mgmt/E2E.t.sol b/test/security-council-mgmt/E2E.t.sol index b3a649d55..0b3f0be8c 100644 --- a/test/security-council-mgmt/E2E.t.sol +++ b/test/security-council-mgmt/E2E.t.sol @@ -113,8 +113,6 @@ contract E2E is Test, DeployGnosisWithModule { address member17 = vm.addr(653); address member18 = vm.addr(654); - - address[] members = [ member1, member2, @@ -194,7 +192,9 @@ contract E2E is Test, DeployGnosisWithModule { uint256 nomineeVotingPeriod = 51; uint256 memberVotingPeriod = 53; uint256 fullWeightDuration = 39; + uint256 minRotationPeriod = 1 weeks; Date nominationStart = Date(1988, 1, 1, 1); + address minRotationPeriodSetter = address(7766); uint256 chain1Id = 937; uint256 chain2Id = 837; @@ -252,11 +252,7 @@ contract E2E is Test, DeployGnosisWithModule { UpgradeExecutor novaExecutorLogic = new UpgradeExecutor(); UpgradeExecutor novaExecutor = UpgradeExecutor( address( - new TransparentUpgradeableProxy( - address(novaExecutorLogic), - address(novaAdmin), - "" - ) + new TransparentUpgradeableProxy(address(novaExecutorLogic), address(novaAdmin), "") ) ); address[] memory executors = new address[](2); @@ -309,13 +305,6 @@ contract E2E is Test, DeployGnosisWithModule { vars.novaExecutor = deployNova(address(vars.l1Timelock)); // deploy sec council - vars.l2AddressRegistry = new L2AddressRegistry( - IL2ArbitrumGoverner(address(l2DeployedCoreContracts.coreGov)), - IL2ArbitrumGoverner(address(l2DeployedTreasuryContracts.treasuryGov)), - IFixedDelegateErc20Wallet(address(l2DeployedTreasuryContracts.arbTreasury)), - IArbitrumDAOConstitution(address(l2DeployedCoreContracts.arbitrumDAOConstitution)) - ); - vars.secFac = new L2SecurityCouncilMgmtFactory(); vars.moduleL2Safe = GnosisSafeL2( @@ -387,7 +376,9 @@ contract E2E is Test, DeployGnosisWithModule { nomineeQuorumNumerator: nomineeQuorumNumerator, nomineeVotingPeriod: nomineeVotingPeriod, memberVotingPeriod: memberVotingPeriod, - fullWeightDuration: fullWeightDuration + fullWeightDuration: fullWeightDuration, + minRotationPeriod: minRotationPeriod, + minRotationPeriodSetter: minRotationPeriodSetter }); ContractImplementations memory contractImpls = ContractImplementations({ @@ -400,6 +391,15 @@ contract E2E is Test, DeployGnosisWithModule { vars.secDeployedContracts = vars.secFac.deploy(secDeployParams, contractImpls); } + vars.l2AddressRegistry = new L2AddressRegistry( + IL2ArbitrumGoverner(address(l2DeployedCoreContracts.coreGov)), + IL2ArbitrumGoverner(address(l2DeployedTreasuryContracts.treasuryGov)), + IFixedDelegateErc20Wallet(address(l2DeployedTreasuryContracts.arbTreasury)), + IArbitrumDAOConstitution(address(l2DeployedCoreContracts.arbitrumDAOConstitution)), + l2DeployedCoreContracts.proxyAdmin, + vars.secDeployedContracts.nomineeElectionGovernor + ); + L1SCMgmtActivationAction installL1 = new L1SCMgmtActivationAction( IGnosisSafe(address(vars.moduleL1Safe)), IGnosisSafe(l1EmergencyCouncil), @@ -454,7 +454,9 @@ contract E2E is Test, DeployGnosisWithModule { SigUtils sigUtils = new SigUtils(address(vars.secDeployedContracts.nomineeElectionGovernor)); for (uint256 i = 0; i < newCohort1.length; i++) { uint256 pk = 649 + i; // member 13 - 18 priv keys - vars.secDeployedContracts.nomineeElectionGovernor.addContender(propId, sigUtils.signAddContenderMessage(propId, pk)); + vars.secDeployedContracts.nomineeElectionGovernor.addContender( + propId, sigUtils.signAddContenderMessage(propId, pk) + ); } // vote for them diff --git a/test/security-council-mgmt/L2SecurityCouncilMgmtFactory.t.sol b/test/security-council-mgmt/L2SecurityCouncilMgmtFactory.t.sol index 621c5c7a1..265550889 100644 --- a/test/security-council-mgmt/L2SecurityCouncilMgmtFactory.t.sol +++ b/test/security-council-mgmt/L2SecurityCouncilMgmtFactory.t.sol @@ -51,8 +51,11 @@ contract L2SecurityCouncilMgmtFactoryTest is Test, DeployGnosisWithModule { address firstCohortMember = address(3456); address secondCohortMember = address(7654); + uint256 minRotationPeriod = 1 weeks; + address minRotationPeriodSetter = address(7655); + function getDeployParams() public returns (DeployParams memory deployParams) { - ChainAndUpExecLocation[] memory upgradeExecutors; + ChainAndUpExecLocation[] memory _upgradeExecutors; address[] memory scOwners = new address[](2); scOwners[0] = firstCohortMember; @@ -70,7 +73,7 @@ contract L2SecurityCouncilMgmtFactoryTest is Test, DeployGnosisWithModule { vm.prank(owner); fac = new L2SecurityCouncilMgmtFactory(); return DeployParams({ - upgradeExecutors: upgradeExecutors, + upgradeExecutors: _upgradeExecutors, govChainEmergencySecurityCouncil: govChainEmergencySecurityCouncil, l1ArbitrumTimelock: l1ArbitrumTimelock, l2CoreGovTimelock: l2CoreGovTimelock, @@ -94,7 +97,9 @@ contract L2SecurityCouncilMgmtFactoryTest is Test, DeployGnosisWithModule { nomineeQuorumNumerator: nomineeQuorumNumerator, nomineeVotingPeriod: nomineeVotingPeriod, memberVotingPeriod: memberVotingPeriod, - fullWeightDuration: fullWeightDuration + fullWeightDuration: fullWeightDuration, + minRotationPeriod: minRotationPeriod, + minRotationPeriodSetter: minRotationPeriodSetter }); } @@ -157,6 +162,9 @@ contract L2SecurityCouncilMgmtFactoryTest is Test, DeployGnosisWithModule { ), "memberElectionGovernor has replacer role" ); + assertEq( + securityCouncilManager.minRotationPeriod(), minRotationPeriod, "Min rotation period" + ); assertTrue( TestUtil.areUniqueAddressArraysEqual( diff --git a/test/security-council-mgmt/SecurityCouncilManager.t.sol b/test/security-council-mgmt/SecurityCouncilManager.t.sol index 4e5da420b..7ff34235e 100644 --- a/test/security-council-mgmt/SecurityCouncilManager.t.sol +++ b/test/security-council-mgmt/SecurityCouncilManager.t.sol @@ -4,10 +4,14 @@ pragma solidity 0.8.16; import "forge-std/Test.sol"; import "../../src/security-council-mgmt/SecurityCouncilManager.sol"; import "../../src/UpgradeExecRouteBuilder.sol"; +import "../../src/security-council-mgmt/governors/SecurityCouncilNomineeElectionGovernor.sol"; +import "../../src/security-council-mgmt/governors/SecurityCouncilMemberElectionGovernor.sol"; +import "../../src/L2ArbitrumToken.sol"; import "../util/TestUtil.sol"; import "../util/MockArbSys.sol"; import "../../src/security-council-mgmt/Common.sol"; +import "./governors/SecurityCouncilNomineeElectionGovernor.t.sol"; contract MockArbitrumTimelock { event CallScheduled( @@ -20,7 +24,7 @@ contract MockArbitrumTimelock { uint256 delay ); - function getMinDelay() external view returns (uint256) { + function getMinDelay() external pure returns (uint256) { return uint256(123); } @@ -37,17 +41,22 @@ contract MockArbitrumTimelock { } contract SecurityCouncilManagerTest is Test { + uint256 pknc1 = 9772; + address pkncAddr1 = vm.addr(pknc1); + uint256 pknc2 = 9773; + address pkncAddr2 = vm.addr(pknc2); + address[] firstCohort = new address[](6); address[6] _firstCohort = [address(1111), address(1112), address(1113), address(1114), address(1115), address(1116)]; address[] secondCohort = new address[](6); address[6] _secondCohort = - [address(2221), address(2222), address(2223), address(2224), address(2225), address(2226)]; + [address(2221), address(2222), address(2223), pkncAddr2, address(2225), address(2226)]; address[] newCohort = new address[](6); address[6] _newCohort = - [address(3331), address(3332), address(3333), address(3334), address(3335), address(3336)]; + [address(3331), address(3332), address(3333), address(3334), pkncAddr1, address(3336)]; address[] newCohortWithADup = new address[](6); address dup = address(3335); @@ -59,6 +68,8 @@ contract SecurityCouncilManagerTest is Test { address[] memberRemovers = new address[](2); address memberRemover1 = address(4444); address memberRemover2 = address(4445); + uint256 minRotationPeriod = 1 weeks; + address minRotationPeriodSetter = address(4450); SecurityCouncilManagerRoles roles = SecurityCouncilManagerRoles({ admin: address(4441), @@ -66,14 +77,20 @@ contract SecurityCouncilManagerTest is Test { memberAdder: address(4443), memberRemovers: memberRemovers, memberRotator: address(4446), - memberReplacer: address(4447) + memberReplacer: address(4447), + minRotationPeriodSetter: minRotationPeriodSetter }); address rando = address(6661); address memberToAdd = address(7771); + uint256 pk1 = 7772; + address memberToRotate1 = vm.addr(pk1); + uint256 pk2 = 7773; + address memberToRotate2 = vm.addr(pk2); address l1ArbitrumTimelock = address(8881); + address nomineeVetter = address(8882); address payable l2CoreGovTimelock; @@ -105,6 +122,10 @@ contract SecurityCouncilManagerTest is Test { address[] bothCohorts; + address memberElectionGovernor; + address nomineeElectionGovernor; + L2ArbitrumToken token; + function setUp() public { chainAndUpExecLocation.push(firstChainAndUpExecLocation); chainAndUpExecLocation.push(secondChainAndUpExecLocation); @@ -125,13 +146,50 @@ contract SecurityCouncilManagerTest is Test { scm = SecurityCouncilManager(payable(prox)); l2CoreGovTimelock = payable(address(new MockArbitrumTimelock())); + token = L2ArbitrumToken(payable(TestUtil.deployProxy(address(new L2ArbitrumToken())))); + token.initialize(address(137), 10_000_000_000, address(this)); + + SecurityCouncilMemberElectionGovernor memGov = SecurityCouncilMemberElectionGovernor( + payable(TestUtil.deployProxy(address(new SecurityCouncilMemberElectionGovernor()))) + ); + SecurityCouncilNomineeElectionGovernor nomGov = SecurityCouncilNomineeElectionGovernor( + payable(TestUtil.deployProxy(address(new SecurityCouncilNomineeElectionGovernor()))) + ); + + SecurityCouncilNomineeElectionGovernor.InitParams memory initParams = + SecurityCouncilNomineeElectionGovernor.InitParams( + Date(2000, 1, 1, 1), 3, nomineeVetter, scm, memGov, token, address(0), 20, 20 + ); + nomGov.initialize(initParams); + memGov.initialize(nomGov, scm, token, address(10), 10, 5); + + roles.cohortUpdator = address(memGov); + memberElectionGovernor = address(memGov); + nomineeElectionGovernor = address(nomGov); + securityCouncils.push(firstSC); - scm.initialize(firstCohort, secondCohort, securityCouncils, roles, l2CoreGovTimelock, uerb); + scm.initialize( + firstCohort, + secondCohort, + securityCouncils, + roles, + l2CoreGovTimelock, + uerb, + minRotationPeriod + ); } function testInitialization() public { vm.expectRevert("Initializable: contract is already initialized"); - scm.initialize(firstCohort, secondCohort, securityCouncils, roles, l2CoreGovTimelock, uerb); + scm.initialize( + firstCohort, + secondCohort, + securityCouncils, + roles, + l2CoreGovTimelock, + uerb, + minRotationPeriod + ); assertTrue( TestUtil.areUniqueAddressArraysEqual(firstCohort, scm.getFirstCohort()), @@ -161,8 +219,11 @@ contract SecurityCouncilManagerTest is Test { "member memberRotator role set" ); assertEq(l2CoreGovTimelock, scm.l2CoreGovTimelock(), "l2CoreGovTimelock set"); + assertEq(minRotationPeriod, scm.minRotationPeriod(), "minRotationPeriod set"); assertEq(address(uerb), address(scm.router()), "exec router set"); + assertEq(scm.NAME_HASH(), keccak256(bytes("SecurityCouncilManager"))); + assertEq(scm.VERSION_HASH(), keccak256(bytes("1"))); } function testRemoveMemberAffordances() public { @@ -190,6 +251,28 @@ contract SecurityCouncilManagerTest is Test { ); } + function testRemoveMemberRotated() public { + address memberToRemove = firstCohort[0]; + bytes32 digest = scm.getRotateMemberHash(memberToRemove, scm.rotationNonce(memberToRemove)); + bytes memory signature = sign(pk1, digest); + vm.prank(memberToRemove); + scm.rotateMember(memberToRotate1, memberElectionGovernor, signature); + + vm.recordLogs(); + vm.prank(roles.memberRemovers[0]); + scm.removeMember(memberToRemove); + checkScheduleWasCalled(); + + address[] memory remainingMembers = new address[](5); + for (uint256 i = 1; i < firstCohort.length; i++) { + remainingMembers[i - 1] = firstCohort[i]; + } + assertTrue( + TestUtil.areUniqueAddressArraysEqual(remainingMembers, scm.getFirstCohort()), + "member removed from first chohort" + ); + } + function testAddMemberSpecialAddresses() public { vm.prank(roles.memberAdder); vm.expectRevert(ZeroAddress.selector); @@ -317,7 +400,40 @@ contract SecurityCouncilManagerTest is Test { vm.stopPrank(); } + function testReplaceMemberInFirstCohortAfterRotation() public { + bytes32 digest = scm.getRotateMemberHash(firstCohort[0], scm.rotationNonce(firstCohort[0])); + bytes memory signature = sign(pk1, digest); + vm.prank(firstCohort[0]); + scm.rotateMember(memberToRotate1, memberElectionGovernor, signature); + + vm.startPrank(roles.memberReplacer); + vm.recordLogs(); + scm.replaceMember(firstCohort[0], memberToAdd); + checkScheduleWasCalled(); + + address[] memory newFirstCohortArray = new address[](6); + newFirstCohortArray[0] = memberToAdd; + for (uint256 i = 1; i < firstCohort.length; i++) { + newFirstCohortArray[i] = firstCohort[i]; + } + assertTrue( + TestUtil.areUniqueAddressArraysEqual(newFirstCohortArray, scm.getFirstCohort()), + "first cohort updated" + ); + assertTrue( + TestUtil.areUniqueAddressArraysEqual(secondCohort, scm.getSecondCohort()), + "second cohort untouched" + ); + vm.stopPrank(); + } + function testReplaceMemberInSecondCohort() public { + bytes32 digest = + scm.getRotateMemberHash(secondCohort[0], scm.rotationNonce(secondCohort[0])); + bytes memory signature = sign(pk2, digest); + vm.prank(secondCohort[0]); + scm.rotateMember(memberToRotate2, memberElectionGovernor, signature); + vm.startPrank(roles.memberReplacer); vm.recordLogs(); scm.replaceMember(secondCohort[0], memberToAdd); @@ -338,26 +454,349 @@ contract SecurityCouncilManagerTest is Test { vm.stopPrank(); } - function testRotateMember() public { - vm.startPrank(roles.memberRotator); + function testReplaceMemberInSecondCohortAfterRotation() public { + vm.startPrank(roles.memberReplacer); vm.recordLogs(); - scm.rotateMember(firstCohort[0], memberToAdd); + scm.replaceMember(secondCohort[0], memberToAdd); checkScheduleWasCalled(); + address[] memory newSecondCohortArray = new address[](6); + newSecondCohortArray[0] = memberToAdd; + for (uint256 i = 1; i < secondCohort.length; i++) { + newSecondCohortArray[i] = secondCohort[i]; + } + assertTrue( + TestUtil.areUniqueAddressArraysEqual(newSecondCohortArray, scm.getSecondCohort()), + "second cohort updated" + ); + assertTrue( + TestUtil.areUniqueAddressArraysEqual(firstCohort, scm.getFirstCohort()), + "first cohort untouched" + ); + vm.stopPrank(); + } - address[] memory newFirstCohortArray = new address[](6); - newFirstCohortArray[0] = memberToAdd; - for (uint256 i = 1; i < firstCohort.length; i++) { - newFirstCohortArray[i] = firstCohort[i]; + function sign(uint256 privKey, bytes32 h) internal pure returns (bytes memory) { + (uint8 v, bytes32 r, bytes32 s) = vm.sign(privKey, h); + return abi.encodePacked(r, s, v); + } + + function checkCohortChange(address newMember, uint256 index, address[] memory cohort, Cohort c) + public + { + address[] memory newSecondCohortArray = new address[](6); + for (uint256 i = 0; i < cohort.length; i++) { + newSecondCohortArray[i] = cohort[i]; } + newSecondCohortArray[index] = newMember; assertTrue( - TestUtil.areUniqueAddressArraysEqual(newFirstCohortArray, scm.getFirstCohort()), - "first cohort rotated" + TestUtil.areUniqueAddressArraysEqual( + newSecondCohortArray, + c == Cohort.FIRST ? scm.getFirstCohort() : scm.getSecondCohort() + ), + "cohort updated" + ); + } + + function testRotateMember() public { + address originalMember = secondCohort[3]; + uint256 startTime = 5678; + vm.warp(startTime); + + bytes32 digest = scm.getRotateMemberHash(originalMember, scm.rotationNonce(originalMember)); + bytes memory signature = sign(pk1, digest); + uint256 startNonce = scm.rotationNonce(originalMember); + + vm.expectRevert( + abi.encodeWithSelector( + ISecurityCouncilManager.InvalidNewAddress.selector, + 0xf1ba0050017D0A16690e3Be7B4A2Ab75F22CFFA2 + ) + ); + vm.prank(secondCohort[2]); + scm.rotateMember(memberToRotate1, memberElectionGovernor, signature); + + vm.expectRevert( + abi.encodeWithSelector(ISecurityCouncilManager.GovernorNotReplacer.selector) + ); + vm.prank(originalMember); + scm.rotateMember(memberToRotate1, address(137), signature); + + vm.recordLogs(); + vm.prank(originalMember); + scm.rotateMember(memberToRotate1, memberElectionGovernor, signature); + assertEq(startNonce + 1, scm.rotationNonce(originalMember), "nonce 1"); + checkScheduleWasCalled(); + checkCohortChange(memberToRotate1, 3, secondCohort, Cohort.SECOND); + assertTrue( + TestUtil.areUniqueAddressArraysEqual(firstCohort, scm.getFirstCohort()), + "first cohort untouched" + ); + assertEq(scm.lastRotated(memberToRotate1), startTime, "Member 1 last rotated"); + + bytes32 digest1 = + scm.getRotateMemberHash(memberToRotate1, scm.rotationNonce(memberToRotate1)); + bytes memory signature1 = sign(pk2, digest1); + + vm.expectRevert( + abi.encodeWithSelector( + ISecurityCouncilManager.RotationTooSoon.selector, + memberToRotate1, + startTime + minRotationPeriod + ) + ); + vm.prank(memberToRotate1); + scm.rotateMember(memberToRotate1, memberElectionGovernor, signature1); + + vm.warp(startTime + minRotationPeriod - 1); + + vm.expectRevert( + abi.encodeWithSelector( + ISecurityCouncilManager.RotationTooSoon.selector, + memberToRotate1, + startTime + minRotationPeriod + ) + ); + vm.prank(memberToRotate1); + scm.rotateMember(memberToRotate1, memberElectionGovernor, signature1); + + vm.warp(startTime + minRotationPeriod); + + startNonce = scm.rotationNonce(memberToRotate1); + vm.recordLogs(); + vm.prank(memberToRotate1); + scm.rotateMember(memberToRotate2, memberElectionGovernor, signature1); + assertEq(startNonce + 1, scm.rotationNonce(memberToRotate1), "nonce 2"); + checkScheduleWasCalled(); + checkCohortChange(memberToRotate2, 3, secondCohort, Cohort.SECOND); + assertTrue( + TestUtil.areUniqueAddressArraysEqual(firstCohort, scm.getFirstCohort()), + "first cohort untouched 2" + ); + assertEq( + scm.lastRotated(memberToRotate2), startTime + minRotationPeriod, "Member 2 last rotated" + ); + + // now do another rotation from the original address - we rotate back to it then away from it - this tests the nonce updates + signature1 = sign( + pknc2, scm.getRotateMemberHash(memberToRotate2, scm.rotationNonce(memberToRotate2)) + ); + vm.warp(startTime + 2 * minRotationPeriod); + vm.recordLogs(); + vm.prank(memberToRotate2); + scm.rotateMember(pkncAddr2, memberElectionGovernor, signature1); + checkScheduleWasCalled(); + + assertTrue( + TestUtil.areUniqueAddressArraysEqual(firstCohort, scm.getFirstCohort()), + "first cohort untouched 1" ); assertTrue( TestUtil.areUniqueAddressArraysEqual(secondCohort, scm.getSecondCohort()), - "second cohort untouched" + "first cohort back to original" ); - vm.stopPrank(); + + signature1 = + sign(pk1, scm.getRotateMemberHash(originalMember, scm.rotationNonce(originalMember))); + vm.warp(startTime + 3 * minRotationPeriod); + startNonce = scm.rotationNonce(originalMember); + vm.recordLogs(); + vm.prank(originalMember); + scm.rotateMember(memberToRotate1, memberElectionGovernor, signature1); + checkScheduleWasCalled(); + assertEq(startNonce + 1, scm.rotationNonce(originalMember), "nonce 3"); + + assertTrue( + TestUtil.areUniqueAddressArraysEqual(firstCohort, scm.getFirstCohort()), + "first cohort untouched 1" + ); + checkCohortChange(memberToRotate1, 3, secondCohort, Cohort.SECOND); + } + + function addAllContendersAndVote(uint256 proposalId) public { + SecurityCouncilNomineeElectionGovernor nGov = + SecurityCouncilNomineeElectionGovernor(payable(nomineeElectionGovernor)); + SigUtils sigUtils = new SigUtils(nomineeElectionGovernor); + token.delegate(address(this)); + + for (uint256 i = 0; i < 6; i++) { + bytes memory sig = sigUtils.signAddContenderMessage(proposalId, i + 1000); + nGov.addContender(proposalId, sig); + } + vm.roll(nGov.proposalDeadline(proposalId)); + for (uint256 i = 0; i < 6; i++) { + nGov.castVoteWithReasonAndParams({ + proposalId: proposalId, + support: 1, + reason: "", + params: abi.encode(vm.addr(i + 1000), 20_000_000) + }); + } + } + + function execProp(uint256 proposalId) public { + SecurityCouncilNomineeElectionGovernor nGov = + SecurityCouncilNomineeElectionGovernor(payable(nomineeElectionGovernor)); + vm.roll( + SecurityCouncilNomineeElectionGovernorTiming(payable(address(nomineeElectionGovernor))) + .proposalVettingDeadline(proposalId) + 1 + ); + ( + address[] memory targets, + uint256[] memory values, + bytes[] memory callDatas, + string memory description + ) = nGov.getProposeArgs(nGov.electionCount() - 1); + nGov.execute(targets, values, callDatas, keccak256(bytes(description))); + } + + function testRotateMemberNotContender() public { + address originalMember = secondCohort[1]; + uint256 startTime = SecurityCouncilNomineeElectionGovernor(payable(nomineeElectionGovernor)) + .electionToTimestamp(0); + vm.warp(startTime); + + // start an election and add a contender + uint256 proposalId = SecurityCouncilNomineeElectionGovernor( + payable(nomineeElectionGovernor) + ).createElection(); + SecurityCouncilNomineeElectionGovernor(payable(nomineeElectionGovernor)).addContender( + proposalId, + new SigUtils(nomineeElectionGovernor).signAddContenderMessage(proposalId, pk1) + ); + + bytes memory signature = + sign(pk1, scm.getRotateMemberHash(originalMember, scm.rotationNonce(originalMember))); + bytes memory signature2 = + sign(pk2, scm.getRotateMemberHash(originalMember, scm.rotationNonce(originalMember))); + uint256 startNonce = scm.rotationNonce(originalMember); + + // replace in other cohort in ongoing election does not work + vm.expectRevert( + abi.encodeWithSelector( + ISecurityCouncilManager.NewMemberIsContender.selector, proposalId, memberToRotate1 + ) + ); + vm.prank(originalMember); + scm.rotateMember(memberToRotate1, memberElectionGovernor, signature); + + uint256 snap = vm.snapshot(); + + addAllContendersAndVote(proposalId); + + // check that we cant rotate to a nominee + vm.roll( + SecurityCouncilNomineeElectionGovernor(payable(nomineeElectionGovernor)) + .proposalDeadline(proposalId) + 1 + ); + vm.prank(nomineeVetter); + SecurityCouncilNomineeElectionGovernor(payable(nomineeElectionGovernor)).excludeNominee( + proposalId, vm.addr(1004) + ); + vm.prank(nomineeVetter); + SecurityCouncilNomineeElectionGovernor(payable(nomineeElectionGovernor)).includeNominee( + proposalId, memberToRotate2 + ); + vm.expectRevert( + abi.encodeWithSelector( + ISecurityCouncilManager.NewMemberIsNominee.selector, proposalId, memberToRotate2 + ) + ); + vm.prank(originalMember); + scm.rotateMember(memberToRotate2, memberElectionGovernor, signature2); + + execProp(proposalId); + assertEq( + uint8(IGovernorUpgradeable(nomineeElectionGovernor).state(proposalId)), + uint8(IGovernorUpgradeable.ProposalState.Executed), + "Not executed" + ); + vm.roll(block.number + 1); + assertEq( + uint8(IGovernorUpgradeable(memberElectionGovernor).state(proposalId)), + uint8(IGovernorUpgradeable.ProposalState.Active), + "Not active" + ); + vm.expectRevert( + abi.encodeWithSelector( + ISecurityCouncilManager.NewMemberIsContender.selector, proposalId, memberToRotate1 + ) + ); + vm.prank(originalMember); + scm.rotateMember(memberToRotate1, memberElectionGovernor, signature); + + vm.revertTo(snap); + + // replacing that member with one in the same cohort does work + bytes memory signatureA = + sign(pk1, scm.getRotateMemberHash(firstCohort[1], scm.rotationNonce(firstCohort[1]))); + startNonce = scm.rotationNonce(firstCohort[1]); + vm.prank(firstCohort[1]); + scm.rotateMember(memberToRotate1, memberElectionGovernor, signatureA); + assertEq(startNonce + 1, scm.rotationNonce(firstCohort[1]), "nonce 1"); + checkCohortChange(memberToRotate1, 1, firstCohort, Cohort.FIRST); + vm.revertTo(snap); + + bytes memory signature1 = + sign(pk2, scm.getRotateMemberHash(originalMember, scm.rotationNonce(originalMember))); + startNonce = scm.rotationNonce(originalMember); + + vm.recordLogs(); + vm.prank(originalMember); + scm.rotateMember(memberToRotate2, memberElectionGovernor, signature1); + assertEq(startNonce + 1, scm.rotationNonce(originalMember), "nonce 1"); + checkScheduleWasCalled(); + checkCohortChange(memberToRotate2, 1, secondCohort, Cohort.SECOND); + assertTrue( + TestUtil.areUniqueAddressArraysEqual(firstCohort, scm.getFirstCohort()), + "first cohort untouched" + ); + assertEq(scm.lastRotated(memberToRotate2), startTime, "Member 1 last rotated"); + } + + event MinRotationPeriodSet(uint256 minRotationPeriod); + + function testPostUpgradeInit() public { + ProxyAdmin pa = new ProxyAdmin(); + SecurityCouncilManager logic = new SecurityCouncilManager(); + SecurityCouncilManager s = SecurityCouncilManager( + address(new TransparentUpgradeableProxy(address(logic), address(pa), "")) + ); + uint256 mr = 25; + address mrs = address(88_766); + + vm.expectRevert(); + vm.prank(address(137)); + TransparentUpgradeableProxy(payable(address(s))).upgradeToAndCall( + address(logic), abi.encodeCall(s.postUpgradeInit, (mr, mrs)) + ); + + vm.expectEmit(true, true, true, true); + emit MinRotationPeriodSet(mr); + vm.prank(address(pa)); + TransparentUpgradeableProxy(payable(address(s))).upgradeToAndCall( + address(logic), abi.encodeCall(s.postUpgradeInit, (mr, mrs)) + ); + assertEq(s.minRotationPeriod(), mr, "Min rotation updated"); + assertTrue( + s.hasRole(s.MIN_ROTATION_PERIOD_SETTER_ROLE(), mrs), "Min rotation period setter role" + ); + assertEq(s.NAME_HASH(), keccak256(bytes("SecurityCouncilManager"))); + assertEq(s.VERSION_HASH(), keccak256(bytes("1"))); + + vm.expectRevert("MIN_ROTATION_ALREADY_SET"); + vm.prank(address(pa)); + TransparentUpgradeableProxy(payable(address(s))).upgradeToAndCall( + address(logic), abi.encodeCall(s.postUpgradeInit, (mr, mrs)) + ); + } + + function testSetMinRotationPeriod() public { + vm.expectRevert(); + scm.setMinRotationPeriod(27); + + vm.prank(minRotationPeriodSetter); + scm.setMinRotationPeriod(27); + assertEq(scm.minRotationPeriod(), 27, "Min rotation period set"); } function testAddSCAffordances() public { @@ -393,7 +832,7 @@ contract SecurityCouncilManagerTest is Test { scm.addSecurityCouncil(scToAdd); assertEq(len + 1, scm.securityCouncilsLength(), "confimred new SC added"); - (address scAddress, address action, uint256 chainid) = + (address scAddress,, uint256 chainid) = scm.securityCouncils(scm.securityCouncilsLength() - 1); assertEq(scAddress, scToAdd.securityCouncil, "confimred new SC added"); @@ -482,7 +921,7 @@ contract SecurityCouncilManagerTest is Test { scm.replaceCohort(newCohortWithADup, Cohort.SECOND); } - function testUpdateRouterAffordacnes() public { + function testUpdateRouterAffordances() public { UpgradeExecRouteBuilder newRouter = UpgradeExecRouteBuilder(TestUtil.deployStubContract()); vm.prank(rando); vm.expectRevert(); diff --git a/test/security-council-mgmt/governors/SecurityCouncilNomineeElectionGovernor.t.sol b/test/security-council-mgmt/governors/SecurityCouncilNomineeElectionGovernor.t.sol index 7cb019024..128c693da 100644 --- a/test/security-council-mgmt/governors/SecurityCouncilNomineeElectionGovernor.t.sol +++ b/test/security-council-mgmt/governors/SecurityCouncilNomineeElectionGovernor.t.sol @@ -36,6 +36,26 @@ contract SigUtils is Test { sig = abi.encodePacked(r, s, v); } + function signRotateNomineeMessage(uint256 proposalId, uint256 privKey, address from) + public + view + returns (bytes memory sig) + { + bytes32 digest = _hashTypedDataV4( + keccak256( + abi.encode( + keccak256("RotateNomineeMessage(uint256 proposalId, address from)"), + proposalId, + from + ) + ) + ); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(privKey, digest); + + sig = abi.encodePacked(r, s, v); + } + function _domainSeparatorV4() internal view returns (bytes32) { return _buildDomainSeparator(_TYPE_HASH, _EIP712NameHash(), _EIP712VersionHash()); } @@ -248,6 +268,7 @@ contract SecurityCouncilNomineeElectionGovernorTest is Test { sig = sigUtils.signAddContenderMessage(proposalId, _contenderPrivKey(0)); // test in other cohort + _mockCohortIncludes(Cohort.FIRST, _contender(0), false); _mockCohortIncludes(Cohort.SECOND, _contender(0), true); vm.expectRevert( abi.encodeWithSelector( @@ -259,6 +280,7 @@ contract SecurityCouncilNomineeElectionGovernorTest is Test { governor.addContender(proposalId, sig); // should fail if the proposal is not pending + _mockCohortIncludes(Cohort.FIRST, _contender(0), false); _mockCohortIncludes(Cohort.SECOND, _contender(0), false); vm.roll(governor.proposalSnapshot(proposalId) + 1); assertTrue(governor.state(proposalId) == IGovernorUpgradeable.ProposalState.Active); @@ -277,6 +299,7 @@ contract SecurityCouncilNomineeElectionGovernorTest is Test { // check that it correctly mutated the state assertTrue(governor.isContender(proposalId, _contender(0))); + assertFalse(governor.isNominee(proposalId, _contender(0))); // adding again should fail vm.expectRevert( @@ -838,8 +861,11 @@ contract SecurityCouncilNomineeElectionGovernorTest is Test { pure returns (uint256) { + uint256 year = months / 12; + months = months % 12; + return DateTimeLib.dateTimeToTimestamp({ - year: date.year, + year: date.year + year, month: date.month + months, day: date.day, hour: date.hour, @@ -917,6 +943,7 @@ contract SecurityCouncilNomineeElectionGovernorTest is Test { function _addContender(uint256 proposalId, uint8 contender) internal { uint256 privKey = _contenderPrivKey(contender); address addr = _contender(contender); + _mockCohortIncludes(Cohort.FIRST, addr, false); _mockCohortIncludes(Cohort.SECOND, addr, false); bytes memory sig = sigUtils.signAddContenderMessage(proposalId, privKey); governor.addContender(proposalId, sig); @@ -963,4 +990,252 @@ contract SecurityCouncilNomineeElectionGovernorTest is Test { ) ); } + + function testDefaultCadence() public { + assertEq(governor.cadenceInMonths(), 6, "Default cadence should be 6 months"); + } + + function testSetCadenceBeforeFirstElection() public { + vm.prank(initParams.owner); + governor.relay( + address(governor), 0, abi.encodeWithSelector(governor.setCadence.selector, 3) + ); + + assertEq(governor.cadenceInMonths(), 3, "Cadence should be updated to 3 months"); + } + + function testSetCadenceInvalidValue() public { + vm.prank(initParams.owner); + vm.expectRevert( + abi.encodeWithSelector( + SecurityCouncilNomineeElectionGovernorTiming.InvalidCadence.selector, 0 + ) + ); + governor.relay( + address(governor), 0, abi.encodeWithSelector(governor.setCadence.selector, 0) + ); + } + + function testSetCadenceOnlyOwner() public { + address nonOwner = address(0x1234); + vm.prank(nonOwner); + vm.expectRevert("Governor: onlyGovernance"); + governor.setCadence(3); + } + + function testElectionTimestampsWithDefaultCadence() public { + uint256 secondElectionTime = governor.electionToTimestamp(1); + uint256 thirdElectionTime = governor.electionToTimestamp(2); + + // Check that elections are properly spaced + // First election: Jan 1, 2030 + // Second election: Jul 1, 2030 (6 months later) + // Third election: Jan 1, 2031 (6 months later) + + // The actual timestamps depend on the exact calendar calculation + uint256 expectedSecondTime = + _datePlusMonthsToTimestamp(initParams.firstNominationStartDate, 6); + uint256 expectedThirdTime = + _datePlusMonthsToTimestamp(initParams.firstNominationStartDate, 12); + + assertEq( + secondElectionTime, expectedSecondTime, "Second election should be 6 months after first" + ); + assertEq( + thirdElectionTime, expectedThirdTime, "Third election should be 12 months after first" + ); + } + + function testSetCadenceAfterElections() public { + // Create first election + _propose(); + + // Fast forward and create second election + vm.warp(_datePlusMonthsToTimestamp(initParams.firstNominationStartDate, 6)); + vm.prank(proposer); + governor.createElection(); + + // Now change cadence to 3 months + vm.prank(initParams.owner); + governor.relay( + address(governor), 0, abi.encodeWithSelector(governor.setCadence.selector, 3) + ); + + assertEq(governor.cadenceInMonths(), 3, "Cadence should be updated to 3 months"); + + // The next election (index 2) should be 3 months after the last one (index 1) + uint256 nextElectionTime = governor.electionToTimestamp(2); + + // Should be approximately 3 months + uint256 expectedTime = _datePlusMonthsToTimestamp( + Date({ + year: 2030, + month: 7, // January + 6 months + day: 1, + hour: 0 + }), + 3 + ); + assertEq(nextElectionTime, expectedTime, "Next election should follow new cadence"); + } + + function testSetCadenceTooSoonReverts() public { + // Create first election + _propose(); + + // Fast forward to near the end of the 6-month period + vm.warp(_datePlusMonthsToTimestamp(initParams.firstNominationStartDate, 6) - 1 days); + + // Try to set cadence to 1 month - this would make next election in the past + vm.prank(initParams.owner); + vm.expectRevert(); + governor.relay( + address(governor), 0, abi.encodeWithSelector(governor.setCadence.selector, 1) + ); + } + + function testMultipleCadenceChanges() public { + // Create first election with default 6-month cadence + _propose(); + + // Change to 4 months + vm.prank(initParams.owner); + governor.relay( + address(governor), 0, abi.encodeWithSelector(governor.setCadence.selector, 4) + ); + + // Fast forward and create second election + vm.warp(_datePlusMonthsToTimestamp(initParams.firstNominationStartDate, 4)); + vm.prank(proposer); + governor.createElection(); + + // Change to 2 months + vm.prank(initParams.owner); + governor.relay( + address(governor), 0, abi.encodeWithSelector(governor.setCadence.selector, 2) + ); + + // Verify the third election timing + uint256 thirdElectionTime = governor.electionToTimestamp(2); + + // Should be 2 months after the second election + uint256 expectedTime = _datePlusMonthsToTimestamp( + Date({ + year: 2030, + month: 5, // January + 4 months + day: 1, + hour: 0 + }), + 2 + ); + assertEq(thirdElectionTime, expectedTime, "Third election should follow newest cadence"); + } + + function testCadenceWithLargeValues() public { + vm.prank(initParams.owner); + governor.relay( + address(governor), 0, abi.encodeWithSelector(governor.setCadence.selector, 36) + ); + + uint256 secondElection = governor.electionToTimestamp(1); + + // First election: Jan 1, 2030 + // Second election: Jan 1, 2033 (36 months later) + uint256 expectedSecondTime = + _datePlusMonthsToTimestamp(initParams.firstNominationStartDate, 36); + + assertEq(secondElection, expectedSecondTime, "Elections should be 36 months apart"); + } + + function testRotateNominee() public { + uint256 proposalId = _propose(); + + // create a nominee + vm.roll(governor.proposalSnapshot(proposalId)); + _addContender(proposalId, 0); + vm.roll(governor.proposalDeadline(proposalId)); + _mockGetPastVotes(_voter(0), governor.quorum(proposalId)); + _castVoteForContender(proposalId, _voter(0), _contender(0), governor.quorum(proposalId)); + + bytes memory sig = + sigUtils.signRotateNomineeMessage(proposalId, _contenderPrivKey(1), _contender(0)); + uint256 rotationDeadline = governor.proposalVettingDeadline(proposalId) - 21_600; + + // cannot rotate after the deadline + vm.roll(rotationDeadline + 1); + vm.prank(_contender(0)); + vm.expectRevert( + abi.encodeWithSelector( + SecurityCouncilNomineeElectionGovernor.ProposalNotInRotationPeriod.selector, + block.number, + rotationDeadline + ) + ); + governor.rotateNominee(proposalId, _contender(1), sig); + vm.roll(rotationDeadline); + + // cannot rotate if not a compliant nominee + vm.prank(_contender(1)); + vm.expectRevert( + abi.encodeWithSelector( + SecurityCouncilNomineeElectionGovernor.NotCompliantNominee.selector, _contender(1) + ) + ); + governor.rotateNominee(proposalId, _contender(1), sig); + + // cannot rotate with invalid signature + vm.prank(_contender(0)); + vm.expectRevert( + abi.encodeWithSelector(SecurityCouncilNomineeElectionGovernor.InvalidSignature.selector) + ); + governor.rotateNominee(proposalId, _contender(2), sig); + + // cannot rotate if in other cohort + _mockCohortIncludes(Cohort.SECOND, _contender(1), true); + vm.prank(_contender(0)); + vm.expectRevert( + abi.encodeWithSelector( + SecurityCouncilNomineeElectionGovernor.AccountInOtherCohort.selector, + Cohort.SECOND, + _contender(1) + ) + ); + governor.rotateNominee(proposalId, _contender(1), sig); + + // cannot rotate to existing nominee + bytes memory sig2 = + sigUtils.signRotateNomineeMessage(proposalId, _contenderPrivKey(2), _contender(0)); + vm.prank(initParams.nomineeVetter); + _mockCohortIncludes(Cohort.SECOND, _contender(2), false); + governor.includeNominee(proposalId, _contender(2)); + _mockCohortIncludes(Cohort.SECOND, _contender(2), false); + vm.prank(_contender(0)); + vm.expectRevert( + abi.encodeWithSelector( + SecurityCouncilNomineeElectionGovernorCountingUpgradeable + .NomineeAlreadyAdded + .selector, + _contender(2) + ) + ); + governor.rotateNominee(proposalId, _contender(2), sig2); + + // rotate the nominee + _mockCohortIncludes(Cohort.SECOND, _contender(1), false); + vm.prank(_contender(0)); + governor.rotateNominee(proposalId, _contender(1), sig); + + // cannot rotate again + vm.prank(_contender(0)); + vm.expectRevert( + abi.encodeWithSelector( + SecurityCouncilNomineeElectionGovernor.NotCompliantNominee.selector, _contender(0) + ) + ); + governor.rotateNominee(proposalId, _contender(1), sig); + + // make sure state is correct + assertTrue(governor.isCompliantNominee(proposalId, _contender(1))); + assertFalse(governor.isCompliantNominee(proposalId, _contender(0))); + } } diff --git a/test/security-council-mgmt/governors/TopNomineesGas.t.sol b/test/security-council-mgmt/governors/TopNomineesGas.t.sol index 207792f4d..9637283e9 100644 --- a/test/security-council-mgmt/governors/TopNomineesGas.t.sol +++ b/test/security-council-mgmt/governors/TopNomineesGas.t.sol @@ -22,7 +22,7 @@ contract TopNomineesGasTest is Test { securityCouncilManager: ISecurityCouncilManager(address(0x22)), securityCouncilMemberElectionGovernor: ISecurityCouncilMemberElectionGovernor( payable(address(0x33)) - ), + ), token: IVotesUpgradeable(address(0x44)), owner: address(0x55), quorumNumeratorValue: 10_000 / N, @@ -91,6 +91,7 @@ contract TopNomineesGasTest is Test { // vote for N nominees uint256 quorum = nomineeGov.quorum(proposalId); for (uint16 i = 0; i < N; i++) { + _mockCohortIncludes(Cohort.FIRST, _nominee(i), false); _mockCohortIncludes(Cohort.SECOND, _nominee(i), false); vm.roll(nomineeGov.proposalSnapshot(proposalId)); @@ -154,13 +155,7 @@ contract TopNomineesGasTest is Test { } function _deployProxy(address impl) internal returns (address) { - return address( - new TransparentUpgradeableProxy( - impl, - proxyAdmin, - bytes("") - ) - ); + return address(new TransparentUpgradeableProxy(impl, proxyAdmin, bytes(""))); } function _mockGetPastVotes(address account, uint256 votes) internal { diff --git a/test/signatures/CancelTimelockAndRemoveMemberAction b/test/signatures/CancelTimelockAndRemoveMemberAction new file mode 100644 index 000000000..ffb14b107 --- /dev/null +++ b/test/signatures/CancelTimelockAndRemoveMemberAction @@ -0,0 +1,9 @@ + +╭--------------------------+------------╮ +| Method | Identifier | ++=======================================+ +| l2AddressRegistry() | 9b491216 | +|--------------------------+------------| +| perform(address,bytes32) | afdb2e38 | +╰--------------------------+------------╯ + diff --git a/test/signatures/L2AddressRegistry b/test/signatures/L2AddressRegistry index a89b23bbc..65339017e 100644 --- a/test/signatures/L2AddressRegistry +++ b/test/signatures/L2AddressRegistry @@ -1,19 +1,27 @@ -╭---------------------------+------------╮ -| Method | Identifier | -+========================================+ -| arbitrumDAOConstitution() | 25efa844 | -|---------------------------+------------| -| coreGov() | c53d7d5c | -|---------------------------+------------| -| coreGovTimelock() | 662e4b50 | -|---------------------------+------------| -| l2ArbitrumToken() | dbbcb30b | -|---------------------------+------------| -| treasuryGov() | 2d475c9a | -|---------------------------+------------| -| treasuryGovTimelock() | 17eb758e | -|---------------------------+------------| -| treasuryWallet() | 4626402b | -╰---------------------------+------------╯ +╭-----------------------------+------------╮ +| Method | Identifier | ++==========================================+ +| arbitrumDAOConstitution() | 25efa844 | +|-----------------------------+------------| +| coreGov() | c53d7d5c | +|-----------------------------+------------| +| coreGovTimelock() | 662e4b50 | +|-----------------------------+------------| +| govProxyAdmin() | 8086e788 | +|-----------------------------+------------| +| l2ArbitrumToken() | dbbcb30b | +|-----------------------------+------------| +| scMemberElectionGovernor() | b249944f | +|-----------------------------+------------| +| scNomineeElectionGovernor() | 033084c9 | +|-----------------------------+------------| +| securityCouncilManager() | 03d1ce8a | +|-----------------------------+------------| +| treasuryGov() | 2d475c9a | +|-----------------------------+------------| +| treasuryGovTimelock() | 17eb758e | +|-----------------------------+------------| +| treasuryWallet() | 4626402b | +╰-----------------------------+------------╯ diff --git a/test/signatures/L2SecurityCouncilMgmtFactory b/test/signatures/L2SecurityCouncilMgmtFactory index 3ae8e88d4..cca23eee6 100644 --- a/test/signatures/L2SecurityCouncilMgmtFactory +++ b/test/signatures/L2SecurityCouncilMgmtFactory @@ -1,13 +1,13 @@ -╭----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------╮ -| Method | Identifier | -+=========================================================================================================================================================================================================================================================================================================================================+ -| deploy(((uint256,(address,address))[],address,address,address,address,address[],address[],address,address,uint256,uint256,uint256,uint256,uint256,uint256,uint64,uint256,(address,address,uint256)[],(uint256,uint256,uint256,uint256),uint256,address,uint256,uint256,uint256,uint256),(address,address,address,address)) | 22cc2946 | -|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------| -| owner() | 8da5cb5b | -|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------| -| renounceOwnership() | 715018a6 | -|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------| -| transferOwnership(address) | f2fde38b | -╰----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------╯ +╭--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------╮ +| Method | Identifier | ++=========================================================================================================================================================================================================================================================================================================================================================+ +| deploy(((uint256,(address,address))[],address,address,address,address,address[],address[],address,address,uint256,uint256,uint256,uint256,uint256,uint256,uint64,uint256,(address,address,uint256)[],(uint256,uint256,uint256,uint256),uint256,address,uint256,uint256,uint256,uint256,uint256,address),(address,address,address,address)) | a7985550 | +|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------| +| owner() | 8da5cb5b | +|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------| +| renounceOwnership() | 715018a6 | +|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------| +| transferOwnership(address) | f2fde38b | +╰--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------╯ diff --git a/test/signatures/SecurityCouncilManager b/test/signatures/SecurityCouncilManager index 7f7dc771c..9c8385eae 100644 --- a/test/signatures/SecurityCouncilManager +++ b/test/signatures/SecurityCouncilManager @@ -1,79 +1,103 @@ -╭---------------------------------------------------------------------------------------------------------------------------------+------------╮ -| Method | Identifier | -+==============================================================================================================================================+ -| COHORT_REPLACER_ROLE() | 279684e2 | -|---------------------------------------------------------------------------------------------------------------------------------+------------| -| DEFAULT_ADMIN_ROLE() | a217fddf | -|---------------------------------------------------------------------------------------------------------------------------------+------------| -| MAX_SECURITY_COUNCILS() | c7b3f5ca | -|---------------------------------------------------------------------------------------------------------------------------------+------------| -| MEMBER_ADDER_ROLE() | ab738506 | -|---------------------------------------------------------------------------------------------------------------------------------+------------| -| MEMBER_REMOVER_ROLE() | b8df7c7f | -|---------------------------------------------------------------------------------------------------------------------------------+------------| -| MEMBER_REPLACER_ROLE() | 8e8c3210 | -|---------------------------------------------------------------------------------------------------------------------------------+------------| -| MEMBER_ROTATOR_ROLE() | 903e7ad6 | -|---------------------------------------------------------------------------------------------------------------------------------+------------| -| RETRYABLE_TICKET_MAGIC() | 3994073d | -|---------------------------------------------------------------------------------------------------------------------------------+------------| -| addMember(address,uint8) | 62d0d1c3 | -|---------------------------------------------------------------------------------------------------------------------------------+------------| -| addSecurityCouncil((address,address,uint256)) | 6eaff79e | -|---------------------------------------------------------------------------------------------------------------------------------+------------| -| cohortIncludes(uint8,address) | f5f4fde0 | -|---------------------------------------------------------------------------------------------------------------------------------+------------| -| cohortSize() | c6db8129 | -|---------------------------------------------------------------------------------------------------------------------------------+------------| -| firstCohortIncludes(address) | 53901f8f | -|---------------------------------------------------------------------------------------------------------------------------------+------------| -| generateSalt(address[],uint256) | 927a0380 | -|---------------------------------------------------------------------------------------------------------------------------------+------------| -| getBothCohorts() | d0946961 | -|---------------------------------------------------------------------------------------------------------------------------------+------------| -| getFirstCohort() | a7b20c29 | -|---------------------------------------------------------------------------------------------------------------------------------+------------| -| getRoleAdmin(bytes32) | 248a9ca3 | -|---------------------------------------------------------------------------------------------------------------------------------+------------| -| getScheduleUpdateInnerData(uint256) | 8bbd5149 | -|---------------------------------------------------------------------------------------------------------------------------------+------------| -| getSecondCohort() | bdc9f17c | -|---------------------------------------------------------------------------------------------------------------------------------+------------| -| grantRole(bytes32,address) | 2f2ff15d | -|---------------------------------------------------------------------------------------------------------------------------------+------------| -| hasRole(bytes32,address) | 91d14854 | -|---------------------------------------------------------------------------------------------------------------------------------+------------| -| initialize(address[],address[],(address,address,uint256)[],(address,address,address,address[],address,address),address,address) | a386a802 | -|---------------------------------------------------------------------------------------------------------------------------------+------------| -| l2CoreGovTimelock() | eea3e4d4 | -|---------------------------------------------------------------------------------------------------------------------------------+------------| -| removeMember(address) | 0b1ca49a | -|---------------------------------------------------------------------------------------------------------------------------------+------------| -| removeSecurityCouncil((address,address,uint256)) | 60052890 | -|---------------------------------------------------------------------------------------------------------------------------------+------------| -| renounceRole(bytes32,address) | 36568abe | -|---------------------------------------------------------------------------------------------------------------------------------+------------| -| replaceCohort(address[],uint8) | b9862f27 | -|---------------------------------------------------------------------------------------------------------------------------------+------------| -| replaceMember(address,address) | e577e32e | -|---------------------------------------------------------------------------------------------------------------------------------+------------| -| revokeRole(bytes32,address) | d547741f | -|---------------------------------------------------------------------------------------------------------------------------------+------------| -| rotateMember(address,address) | 0931d37e | -|---------------------------------------------------------------------------------------------------------------------------------+------------| -| router() | f887ea40 | -|---------------------------------------------------------------------------------------------------------------------------------+------------| -| secondCohortIncludes(address) | e9d9f048 | -|---------------------------------------------------------------------------------------------------------------------------------+------------| -| securityCouncils(uint256) | bef3f745 | -|---------------------------------------------------------------------------------------------------------------------------------+------------| -| securityCouncilsLength() | 7889acb2 | -|---------------------------------------------------------------------------------------------------------------------------------+------------| -| setUpgradeExecRouteBuilder(address) | 0e5e43d7 | -|---------------------------------------------------------------------------------------------------------------------------------+------------| -| supportsInterface(bytes4) | 01ffc9a7 | -|---------------------------------------------------------------------------------------------------------------------------------+------------| -| updateNonce() | 0feca68a | -╰---------------------------------------------------------------------------------------------------------------------------------+------------╯ +╭-------------------------------------------------------------------------------------------------------------------------------------------------+------------╮ +| Method | Identifier | ++==============================================================================================================================================================+ +| COHORT_REPLACER_ROLE() | 279684e2 | +|-------------------------------------------------------------------------------------------------------------------------------------------------+------------| +| DEFAULT_ADMIN_ROLE() | a217fddf | +|-------------------------------------------------------------------------------------------------------------------------------------------------+------------| +| DOMAIN_TYPE_HASH() | c0993eea | +|-------------------------------------------------------------------------------------------------------------------------------------------------+------------| +| MAX_SECURITY_COUNCILS() | c7b3f5ca | +|-------------------------------------------------------------------------------------------------------------------------------------------------+------------| +| MEMBER_ADDER_ROLE() | ab738506 | +|-------------------------------------------------------------------------------------------------------------------------------------------------+------------| +| MEMBER_REMOVER_ROLE() | b8df7c7f | +|-------------------------------------------------------------------------------------------------------------------------------------------------+------------| +| MEMBER_REPLACER_ROLE() | 8e8c3210 | +|-------------------------------------------------------------------------------------------------------------------------------------------------+------------| +| MEMBER_ROTATOR_ROLE() | 903e7ad6 | +|-------------------------------------------------------------------------------------------------------------------------------------------------+------------| +| MIN_ROTATION_PERIOD_SETTER_ROLE() | 5db9bf4e | +|-------------------------------------------------------------------------------------------------------------------------------------------------+------------| +| NAME_HASH() | 04622c2e | +|-------------------------------------------------------------------------------------------------------------------------------------------------+------------| +| RETRYABLE_TICKET_MAGIC() | 3994073d | +|-------------------------------------------------------------------------------------------------------------------------------------------------+------------| +| ROTATE_MEMBER_TYPE_HASH() | aea6b1e7 | +|-------------------------------------------------------------------------------------------------------------------------------------------------+------------| +| VERSION_HASH() | 9e4e7318 | +|-------------------------------------------------------------------------------------------------------------------------------------------------+------------| +| addMember(address,uint8) | 62d0d1c3 | +|-------------------------------------------------------------------------------------------------------------------------------------------------+------------| +| addSecurityCouncil((address,address,uint256)) | 6eaff79e | +|-------------------------------------------------------------------------------------------------------------------------------------------------+------------| +| cohortIncludes(uint8,address) | f5f4fde0 | +|-------------------------------------------------------------------------------------------------------------------------------------------------+------------| +| cohortSize() | c6db8129 | +|-------------------------------------------------------------------------------------------------------------------------------------------------+------------| +| firstCohortIncludes(address) | 53901f8f | +|-------------------------------------------------------------------------------------------------------------------------------------------------+------------| +| generateSalt(address[],uint256) | 927a0380 | +|-------------------------------------------------------------------------------------------------------------------------------------------------+------------| +| getBothCohorts() | d0946961 | +|-------------------------------------------------------------------------------------------------------------------------------------------------+------------| +| getFirstCohort() | a7b20c29 | +|-------------------------------------------------------------------------------------------------------------------------------------------------+------------| +| getRoleAdmin(bytes32) | 248a9ca3 | +|-------------------------------------------------------------------------------------------------------------------------------------------------+------------| +| getRotateMemberHash(address,uint256) | 09af9e5f | +|-------------------------------------------------------------------------------------------------------------------------------------------------+------------| +| getScheduleUpdateInnerData(uint256) | 8bbd5149 | +|-------------------------------------------------------------------------------------------------------------------------------------------------+------------| +| getSecondCohort() | bdc9f17c | +|-------------------------------------------------------------------------------------------------------------------------------------------------+------------| +| grantRole(bytes32,address) | 2f2ff15d | +|-------------------------------------------------------------------------------------------------------------------------------------------------+------------| +| hasRole(bytes32,address) | 91d14854 | +|-------------------------------------------------------------------------------------------------------------------------------------------------+------------| +| initialize(address[],address[],(address,address,uint256)[],(address,address,address,address[],address,address,address),address,address,uint256) | caa33e24 | +|-------------------------------------------------------------------------------------------------------------------------------------------------+------------| +| l2CoreGovTimelock() | eea3e4d4 | +|-------------------------------------------------------------------------------------------------------------------------------------------------+------------| +| lastRotated(address) | 64f747b6 | +|-------------------------------------------------------------------------------------------------------------------------------------------------+------------| +| minRotationPeriod() | cfc02946 | +|-------------------------------------------------------------------------------------------------------------------------------------------------+------------| +| postUpgradeInit(uint256,address) | c50e68a0 | +|-------------------------------------------------------------------------------------------------------------------------------------------------+------------| +| removeMember(address) | 0b1ca49a | +|-------------------------------------------------------------------------------------------------------------------------------------------------+------------| +| removeSecurityCouncil((address,address,uint256)) | 60052890 | +|-------------------------------------------------------------------------------------------------------------------------------------------------+------------| +| renounceRole(bytes32,address) | 36568abe | +|-------------------------------------------------------------------------------------------------------------------------------------------------+------------| +| replaceCohort(address[],uint8) | b9862f27 | +|-------------------------------------------------------------------------------------------------------------------------------------------------+------------| +| replaceMember(address,address) | e577e32e | +|-------------------------------------------------------------------------------------------------------------------------------------------------+------------| +| revokeRole(bytes32,address) | d547741f | +|-------------------------------------------------------------------------------------------------------------------------------------------------+------------| +| rotateMember(address,address,bytes) | 02ea6df4 | +|-------------------------------------------------------------------------------------------------------------------------------------------------+------------| +| rotatedTo(address) | 86bc77a3 | +|-------------------------------------------------------------------------------------------------------------------------------------------------+------------| +| rotationNonce(address) | ac823694 | +|-------------------------------------------------------------------------------------------------------------------------------------------------+------------| +| router() | f887ea40 | +|-------------------------------------------------------------------------------------------------------------------------------------------------+------------| +| secondCohortIncludes(address) | e9d9f048 | +|-------------------------------------------------------------------------------------------------------------------------------------------------+------------| +| securityCouncils(uint256) | bef3f745 | +|-------------------------------------------------------------------------------------------------------------------------------------------------+------------| +| securityCouncilsLength() | 7889acb2 | +|-------------------------------------------------------------------------------------------------------------------------------------------------+------------| +| setMinRotationPeriod(uint256) | d4c271b2 | +|-------------------------------------------------------------------------------------------------------------------------------------------------+------------| +| setUpgradeExecRouteBuilder(address) | 0e5e43d7 | +|-------------------------------------------------------------------------------------------------------------------------------------------------+------------| +| supportsInterface(bytes4) | 01ffc9a7 | +|-------------------------------------------------------------------------------------------------------------------------------------------------+------------| +| updateNonce() | 0feca68a | +╰-------------------------------------------------------------------------------------------------------------------------------------------------+------------╯ diff --git a/test/signatures/SecurityCouncilNomineeElectionGovernor b/test/signatures/SecurityCouncilNomineeElectionGovernor index 4e192dc98..91fbde2ab 100644 --- a/test/signatures/SecurityCouncilNomineeElectionGovernor +++ b/test/signatures/SecurityCouncilNomineeElectionGovernor @@ -10,10 +10,12 @@ |-----------------------------------------------------------------------------------------------------------------+------------| | EXTENDED_BALLOT_TYPEHASH() | 2fe3e261 | |-----------------------------------------------------------------------------------------------------------------+------------| -| addContender(uint256) | 140af012 | +| ROTATION_CUT_OFF_BLOCKS() | 6bc9d9ab | |-----------------------------------------------------------------------------------------------------------------+------------| | addContender(uint256,bytes) | a8f38759 | |-----------------------------------------------------------------------------------------------------------------+------------| +| cadenceInMonths() | e182a4cd | +|-----------------------------------------------------------------------------------------------------------------+------------| | castVote(uint256,uint8) | 56781388 | |-----------------------------------------------------------------------------------------------------------------+------------| | castVoteBySig(uint256,uint8,uint8,bytes32,bytes32) | 3bccf4fd | @@ -92,6 +94,8 @@ |-----------------------------------------------------------------------------------------------------------------+------------| | owner() | 8da5cb5b | |-----------------------------------------------------------------------------------------------------------------+------------| +| postUpgradeInit() | 95fcea78 | +|-----------------------------------------------------------------------------------------------------------------+------------| | proposalDeadline(uint256) | c01f9e37 | |-----------------------------------------------------------------------------------------------------------------+------------| | proposalSnapshot(uint256) | 2d63f693 | @@ -112,14 +116,20 @@ |-----------------------------------------------------------------------------------------------------------------+------------| | recoverAddContenderMessage(uint256,bytes) | 5a756eaf | |-----------------------------------------------------------------------------------------------------------------+------------| +| recoverRotateNomineeMessage(uint256,bytes,address) | 307d1d14 | +|-----------------------------------------------------------------------------------------------------------------+------------| | relay(address,uint256,bytes) | c28bc2fa | |-----------------------------------------------------------------------------------------------------------------+------------| | renounceOwnership() | 715018a6 | |-----------------------------------------------------------------------------------------------------------------+------------| +| rotateNominee(uint256,address,bytes) | 5cd48043 | +|-----------------------------------------------------------------------------------------------------------------+------------| | securityCouncilManager() | 03d1ce8a | |-----------------------------------------------------------------------------------------------------------------+------------| | securityCouncilMemberElectionGovernor() | 1b6a7673 | |-----------------------------------------------------------------------------------------------------------------+------------| +| setCadence(uint256) | 5ad525b1 | +|-----------------------------------------------------------------------------------------------------------------+------------| | setNomineeVetter(address) | ae8acb5e | |-----------------------------------------------------------------------------------------------------------------+------------| | setProposalThreshold(uint256) | ece40cc1 | diff --git a/test/signatures/SecurityCouncilUpgradeAction b/test/signatures/SecurityCouncilUpgradeAction new file mode 100644 index 000000000..33891359d --- /dev/null +++ b/test/signatures/SecurityCouncilUpgradeAction @@ -0,0 +1,21 @@ + +╭---------------------------------+------------╮ +| Method | Identifier | ++==============================================+ +| cadenceInMonths() | e182a4cd | +|---------------------------------+------------| +| l2AddressRegistry() | 9b491216 | +|---------------------------------+------------| +| minRotationPeriod() | cfc02946 | +|---------------------------------+------------| +| minRotationPeriodSetter() | 7a5c8992 | +|---------------------------------+------------| +| newConstitutionHash() | 8035cce0 | +|---------------------------------+------------| +| perform() | b147f40c | +|---------------------------------+------------| +| scNomineeElectionGovernorImpl() | c91461be | +|---------------------------------+------------| +| secCouncilManagerImpl() | a03700cc | +╰---------------------------------+------------╯ + diff --git a/test/storage/CancelTimelockAndRemoveMemberAction b/test/storage/CancelTimelockAndRemoveMemberAction new file mode 100644 index 000000000..1ec5dc079 --- /dev/null +++ b/test/storage/CancelTimelockAndRemoveMemberAction @@ -0,0 +1,6 @@ + +╭------+------+------+--------+-------+----------╮ +| Name | Type | Slot | Offset | Bytes | Contract | ++================================================+ +╰------+------+------+--------+-------+----------╯ + diff --git a/test/storage/SecurityCouncilManager b/test/storage/SecurityCouncilManager index 4e547c0f1..972049f05 100644 --- a/test/storage/SecurityCouncilManager +++ b/test/storage/SecurityCouncilManager @@ -28,6 +28,14 @@ |-------------------+--------------------------------------------------------------+------+--------+-------+-----------------------------------------------------------------------------| | cohortSize | uint256 | 157 | 0 | 32 | src/security-council-mgmt/SecurityCouncilManager.sol:SecurityCouncilManager | |-------------------+--------------------------------------------------------------+------+--------+-------+-----------------------------------------------------------------------------| -| __gap | uint256[43] | 158 | 0 | 1376 | src/security-council-mgmt/SecurityCouncilManager.sol:SecurityCouncilManager | +| lastRotated | mapping(address => uint256) | 158 | 0 | 32 | src/security-council-mgmt/SecurityCouncilManager.sol:SecurityCouncilManager | +|-------------------+--------------------------------------------------------------+------+--------+-------+-----------------------------------------------------------------------------| +| rotatedTo | mapping(address => address) | 159 | 0 | 32 | src/security-council-mgmt/SecurityCouncilManager.sol:SecurityCouncilManager | +|-------------------+--------------------------------------------------------------+------+--------+-------+-----------------------------------------------------------------------------| +| minRotationPeriod | uint256 | 160 | 0 | 32 | src/security-council-mgmt/SecurityCouncilManager.sol:SecurityCouncilManager | +|-------------------+--------------------------------------------------------------+------+--------+-------+-----------------------------------------------------------------------------| +| rotationNonce | mapping(address => uint256) | 161 | 0 | 32 | src/security-council-mgmt/SecurityCouncilManager.sol:SecurityCouncilManager | +|-------------------+--------------------------------------------------------------+------+--------+-------+-----------------------------------------------------------------------------| +| __gap | uint256[39] | 162 | 0 | 1248 | src/security-council-mgmt/SecurityCouncilManager.sol:SecurityCouncilManager | ╰-------------------+--------------------------------------------------------------+------+--------+-------+-----------------------------------------------------------------------------╯ diff --git a/test/storage/SecurityCouncilNomineeElectionGovernor b/test/storage/SecurityCouncilNomineeElectionGovernor index 235fb7592..95df9dcec 100644 --- a/test/storage/SecurityCouncilNomineeElectionGovernor +++ b/test/storage/SecurityCouncilNomineeElectionGovernor @@ -58,7 +58,9 @@ |---------------------------------------+------------------------------------------------------------------------------------------------------------------+------+--------+-------+-----------------------------------------------------------------------------------------------------------------------| | nomineeVettingDuration | uint256 | 558 | 0 | 32 | src/security-council-mgmt/governors/SecurityCouncilNomineeElectionGovernor.sol:SecurityCouncilNomineeElectionGovernor | |---------------------------------------+------------------------------------------------------------------------------------------------------------------+------+--------+-------+-----------------------------------------------------------------------------------------------------------------------| -| __gap | uint256[45] | 559 | 0 | 1440 | src/security-council-mgmt/governors/SecurityCouncilNomineeElectionGovernor.sol:SecurityCouncilNomineeElectionGovernor | +| cadenceInMonths | uint256 | 559 | 0 | 32 | src/security-council-mgmt/governors/SecurityCouncilNomineeElectionGovernor.sol:SecurityCouncilNomineeElectionGovernor | +|---------------------------------------+------------------------------------------------------------------------------------------------------------------+------+--------+-------+-----------------------------------------------------------------------------------------------------------------------| +| __gap | uint256[44] | 560 | 0 | 1408 | src/security-council-mgmt/governors/SecurityCouncilNomineeElectionGovernor.sol:SecurityCouncilNomineeElectionGovernor | |---------------------------------------+------------------------------------------------------------------------------------------------------------------+------+--------+-------+-----------------------------------------------------------------------------------------------------------------------| | usedNonces | mapping(bytes32 => bool) | 604 | 0 | 32 | src/security-council-mgmt/governors/SecurityCouncilNomineeElectionGovernor.sol:SecurityCouncilNomineeElectionGovernor | |---------------------------------------+------------------------------------------------------------------------------------------------------------------+------+--------+-------+-----------------------------------------------------------------------------------------------------------------------| diff --git a/test/storage/SecurityCouncilUpgradeAction b/test/storage/SecurityCouncilUpgradeAction new file mode 100644 index 000000000..1ec5dc079 --- /dev/null +++ b/test/storage/SecurityCouncilUpgradeAction @@ -0,0 +1,6 @@ + +╭------+------+------+--------+-------+----------╮ +| Name | Type | Slot | Offset | Bytes | Contract | ++================================================+ +╰------+------+------+--------+-------+----------╯ + diff --git a/test/util/ActionTestBase.sol b/test/util/ActionTestBase.sol index fb6a50dab..b81be809f 100644 --- a/test/util/ActionTestBase.sol +++ b/test/util/ActionTestBase.sol @@ -20,6 +20,7 @@ import "../../src/ArbitrumDAOConstitution.sol"; import "../../src/gov-action-contracts/address-registries/L1AddressRegistry.sol" as _ar; import "../../src/gov-action-contracts/address-registries/L2AddressRegistry.sol" as _ar1; import "../../src/gov-action-contracts/address-registries/interfaces.sol" as _ifaces; +import "../../src/security-council-mgmt/interfaces/ISecurityCouncilNomineeElectionGovernor.sol"; contract OwnableStub is Ownable {} @@ -57,12 +58,13 @@ abstract contract ActionTestBase { FixedDelegateErc20Wallet treasuryWallet; function setUp() public { + ProxyAdmin pa = new ProxyAdmin(); outboxesToAdd = [address(new OutboxStub()), address(new OutboxStub()), address(new OutboxStub())]; outboxesToRemove.push(outboxesToAdd[0]); outboxesToRemove.push(outboxesToAdd[1]); - ue = UpgradeExecutor(TestUtil.deployProxy(address(new UpgradeExecutor()))); + ue = UpgradeExecutor(TestUtil.deployProxy(pa, address(new UpgradeExecutor()))); address[] memory executors = new address[](2); executors[0] = executor0; @@ -71,7 +73,7 @@ abstract contract ActionTestBase { rollup = new OwnableStub(); rollup.transferOwnership(address(ue)); - bridge = Bridge(TestUtil.deployProxy(address(new Bridge()))); + bridge = Bridge(TestUtil.deployProxy(pa, address(new Bridge()))); bridge.initialize(IOwnable(address(rollup))); // nitro-testnode's L1 is not an Arbitrum chain, so IReader4844 must be a non-zero address si = SequencerInbox(TestUtil.deployProxy(address(new SequencerInbox(117964, IReader4844(address(1)), false, false)))); @@ -80,7 +82,7 @@ abstract contract ActionTestBase { inbox.initialize(bridge, si); l1Timelock = - L1ArbitrumTimelock(payable(TestUtil.deployProxy(address(new L1ArbitrumTimelock())))); + L1ArbitrumTimelock(payable(TestUtil.deployProxy(pa, address(new L1ArbitrumTimelock())))); address[] memory l1Proposers = new address[](1); l1Proposers[0] = address(bridge); l1Timelock.initialize(5, l1Proposers, new address[](0)); @@ -88,13 +90,17 @@ abstract contract ActionTestBase { l1Timelock.revokeRole(l1Timelock.TIMELOCK_ADMIN_ROLE(), address(l1Timelock)); l1Timelock.revokeRole(l1Timelock.TIMELOCK_ADMIN_ROLE(), address(this)); - addressRegistry = - new _ar.L1AddressRegistry(IInbox(address(inbox)), _ifaces.IL1Timelock(address(l1Timelock)), _ifaces.IL1CustomGateway(address(0)), _ifaces.IL1GatewayRouter(address(0))); + addressRegistry = new _ar.L1AddressRegistry( + IInbox(address(inbox)), + _ifaces.IL1Timelock(address(l1Timelock)), + _ifaces.IL1CustomGateway(address(0)), + _ifaces.IL1GatewayRouter(address(0)) + ); bridgeGetter = _ifaces.IBridgeGetter(address(addressRegistry)); inboxGetter = _ifaces.IInboxGetter(address(addressRegistry)); sequencerInboxGetter = _ifaces.ISequencerInboxGetter(address(addressRegistry)); - arbOneUe = UpgradeExecutor(TestUtil.deployProxy(address(new UpgradeExecutor()))); + arbOneUe = UpgradeExecutor(TestUtil.deployProxy(pa, address(new UpgradeExecutor()))); address[] memory executors2 = new address[](1); executors2[0] = executor2; arbOneUe.initialize(address(arbOneUe), executors2); @@ -102,12 +108,12 @@ abstract contract ActionTestBase { arbitrumDAOConstitution = new ArbitrumDAOConstitution(constitutionHash); arbitrumDAOConstitution.transferOwnership(address(arbOneUe)); - arbOneToken = L2ArbitrumToken(TestUtil.deployProxy(address(new L2ArbitrumToken()))); + arbOneToken = L2ArbitrumToken(TestUtil.deployProxy(pa, address(new L2ArbitrumToken()))); arbOneToken.initialize(address(4567), 10_000_000_000, address(arbOneUe)); coreTimelock = - ArbitrumTimelock(payable(TestUtil.deployProxy(address(new ArbitrumTimelock())))); + ArbitrumTimelock(payable(TestUtil.deployProxy(pa, address(new ArbitrumTimelock())))); coreGov = - L2ArbitrumGovernor(payable(TestUtil.deployProxy(address(new L2ArbitrumGovernor())))); + L2ArbitrumGovernor(payable(TestUtil.deployProxy(pa, address(new L2ArbitrumGovernor())))); address[] memory proposers = new address[](1); proposers[0] = address(coreGov); coreTimelock.initialize(5, proposers, new address[](0)); @@ -117,9 +123,9 @@ abstract contract ActionTestBase { coreGov.initialize(arbOneToken, coreTimelock, address(arbOneUe), 3, 4, 500, 50, 50); treasuryTimelock = - ArbitrumTimelock(payable(TestUtil.deployProxy(address(new ArbitrumTimelock())))); + ArbitrumTimelock(payable(TestUtil.deployProxy(pa, address(new ArbitrumTimelock())))); treasuryGov = - L2ArbitrumGovernor(payable(TestUtil.deployProxy(address(new L2ArbitrumGovernor())))); + L2ArbitrumGovernor(payable(TestUtil.deployProxy(pa, address(new L2ArbitrumGovernor())))); address[] memory proposers2 = new address[](1); proposers[0] = address(treasuryGov); treasuryTimelock.initialize(7, proposers2, new address[](0)); @@ -130,13 +136,20 @@ abstract contract ActionTestBase { treasuryTimelock.revokeRole(treasuryTimelock.TIMELOCK_ADMIN_ROLE(), address(this)); treasuryGov.initialize(arbOneToken, treasuryTimelock, address(arbOneUe), 7, 8, 600, 60, 60); - treasuryWallet = - FixedDelegateErc20Wallet(TestUtil.deployProxy(address(new FixedDelegateErc20Wallet()))); + treasuryWallet = FixedDelegateErc20Wallet( + TestUtil.deployProxy(pa, address(new FixedDelegateErc20Wallet())) + ); treasuryWallet.initialize( address(arbOneToken), treasuryGov.EXCLUDE_ADDRESS(), address(treasuryTimelock) ); - arbOneAddressRegistry = - new _ar1.L2AddressRegistry(_ar1.IL2ArbitrumGoverner(address(coreGov)), _ar1.IL2ArbitrumGoverner(address(treasuryGov)), _ar1.IFixedDelegateErc20Wallet(address(treasuryWallet)), _ar1.IArbitrumDAOConstitution(address(arbitrumDAOConstitution))); + arbOneAddressRegistry = new _ar1.L2AddressRegistry( + _ar1.IL2ArbitrumGoverner(address(coreGov)), + _ar1.IL2ArbitrumGoverner(address(treasuryGov)), + _ar1.IFixedDelegateErc20Wallet(address(treasuryWallet)), + _ar1.IArbitrumDAOConstitution(address(arbitrumDAOConstitution)), + pa, + ISecurityCouncilNomineeElectionGovernor(payable(address(0))) + ); } } diff --git a/test/util/TestUtil.sol b/test/util/TestUtil.sol index e63aa15c4..38033be1e 100644 --- a/test/util/TestUtil.sol +++ b/test/util/TestUtil.sol @@ -13,6 +13,10 @@ library TestUtil { return address(new TransparentUpgradeableProxy(address(logic), address(pa), "")); } + function deployProxy(ProxyAdmin pa, address logic) public returns (address) { + return address(new TransparentUpgradeableProxy(address(logic), address(pa), "")); + } + function deployStubContract() public returns (address) { return address(new StubContract()); }