diff --git a/.gas-snapshot b/.gas-snapshot index f2854ac..b36936f 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,31 +1,34 @@ -AcceptManagerTransfer:testFuzz_acceptManagerTransfer(address) (runs: 256, μ: 185205, ~: 185205) -AcceptManagerTransfer:test_RevertIf_NoPendingManager() (gas: 170816) +AcceptManagerTransfer:testFuzz_acceptManagerTransfer(address) (runs: 256, μ: 185139, ~: 185139) +AcceptManagerTransfer:test_RevertIf_NoPendingManager() (gas: 170772) AcceptManagerTransfer:test_isAdmin() (gas: 953761) -AddAdminVerifier:testFuzz_RevertIf_addAdminVerifierByUnauthorized(address) (runs: 256, μ: 270819, ~: 270819) -AddAdminVerifier:testFuzz_RevertIf_addAdminVerifierTwice(address) (runs: 256, μ: 47771, ~: 47771) -AddAdminVerifier:test_addAdminVerifier(address) (runs: 256, μ: 49506, ~: 49506) +AddAdminVerifier:testFuzz_RevertIf_addAdminVerifierByUnauthorized(address) (runs: 256, μ: 270817, ~: 270817) +AddAdminVerifier:testFuzz_RevertIf_addAdminVerifierTwice(address) (runs: 256, μ: 47899, ~: 47899) +AddAdminVerifier:test_addAdminVerifier(address) (runs: 256, μ: 49570, ~: 49570) AddAdminVerifier:test_isAdmin() (gas: 953695) -AddAssertion:testFuzz_RevertIf_addAssertionByUnauthorized(bytes32,address) (runs: 256, μ: 168554, ~: 168554) -AddAssertion:testFuzz_RevertIf_addAssertionNotRegistered(address,bytes32) (runs: 256, μ: 21802, ~: 21802) -AddAssertion:testFuzz_RevertIf_addDuplicateAssertion(bytes32) (runs: 256, μ: 240904, ~: 240904) -AddAssertion:testFuzz_addAssertion(bytes32) (runs: 256, μ: 234653, ~: 234653) -AddAssertion:testFuzz_addMultipleAssertions(bytes32,bytes32) (runs: 256, μ: 282925, ~: 282925) -AddAssertion:testFuzz_addTooManyAssertions(bytes32) (runs: 256, μ: 511735, ~: 511735) -AddAssertion:testFuzz_expectAssertionAdded(bytes32) (runs: 256, μ: 242023, ~: 242023) -AddAssertion:test_RevertIf_addAssertionWithUnregisteredDAVerifier() (gas: 168781) +AddAssertion:testFuzz_RevertIf_addAssertionByUnauthorized(bytes32,address) (runs: 256, μ: 168510, ~: 168510) +AddAssertion:testFuzz_RevertIf_addAssertionNotRegistered(address,bytes32) (runs: 256, μ: 21780, ~: 21780) +AddAssertion:testFuzz_RevertIf_addDuplicateAssertion(bytes32) (runs: 256, μ: 240838, ~: 240838) +AddAssertion:testFuzz_addAssertion(bytes32) (runs: 256, μ: 234609, ~: 234609) +AddAssertion:testFuzz_addMultipleAssertions(bytes32,bytes32) (runs: 256, μ: 282859, ~: 282859) +AddAssertion:testFuzz_addTooManyAssertions(bytes32) (runs: 256, μ: 511537, ~: 511537) +AddAssertion:testFuzz_expectAssertionAdded(bytes32) (runs: 256, μ: 241979, ~: 241979) +AddAssertion:test_RevertIf_addAssertionWithUnregisteredDAVerifier() (gas: 168737) AddAssertion:test_isAdmin() (gas: 953695) -AddAssertionWithWhitelist:testFuzz_RevertIf_addAssertionByNonWhitelistedManager(bytes32) (runs: 256, μ: 177531, ~: 177531) -AddAssertionWithWhitelist:test_addAssertionAfterAddingToWhitelist() (gas: 254234) -AddAssertionWithWhitelist:test_addAssertionWhenWhitelistDisabled() (gas: 233237) +AddAssertionWithWhitelist:testFuzz_RevertIf_addAssertionByNonWhitelistedManager(bytes32) (runs: 256, μ: 177443, ~: 177443) +AddAssertionWithWhitelist:test_addAssertionAfterAddingToWhitelist() (gas: 254197) +AddAssertionWithWhitelist:test_addAssertionWhenWhitelistDisabled() (gas: 233171) AddAssertionWithWhitelist:test_isAdmin() (gas: 953695) -AddDAVerifier:testFuzz_RevertIf_addDAVerifierByUnauthorized(address) (runs: 256, μ: 26949, ~: 26949) -AddDAVerifier:testFuzz_RevertIf_addDAVerifierTwice(address) (runs: 256, μ: 48425, ~: 48425) -AddDAVerifier:test_addDAVerifier(address) (runs: 256, μ: 52844, ~: 52844) +AddDAVerifier:testFuzz_RevertIf_addDAVerifierByUnauthorized(address) (runs: 256, μ: 26861, ~: 26861) +AddDAVerifier:testFuzz_RevertIf_addDAVerifierTwice(address) (runs: 256, μ: 48381, ~: 48381) +AddDAVerifier:test_addDAVerifier(address) (runs: 256, μ: 52778, ~: 52778) AddDAVerifier:test_isAdmin() (gas: 953695) -AddToWhitelist:testFuzz_RevertIf_addToWhitelistByUnauthorized(address) (runs: 256, μ: 30865, ~: 30865) -AddToWhitelist:test_RevertIf_addAlreadyWhitelistedAccount() (gas: 45839) -AddToWhitelist:test_addToWhitelist() (gas: 54605) +AddToWhitelist:testFuzz_RevertIf_addToWhitelistByUnauthorized(address) (runs: 256, μ: 30933, ~: 30933) +AddToWhitelist:test_RevertIf_addAlreadyWhitelistedAccount() (gas: 45842) +AddToWhitelist:test_addToWhitelist() (gas: 54590) AddToWhitelist:test_isAdmin() (gas: 953695) +AdminVerifierAlwaysApproveTest:test_verifyAdminReturnsTrueForAnyRequester() (gas: 10911) +AdminVerifierAlwaysApproveTest:test_verifyAdminReturnsTrueForZeroAddresses() (gas: 7049) +AdminVerifierAlwaysApproveTest:test_verifyAdminReturnsTrueWithData() (gas: 8290) AdminVerifierSuperAdminTest:test_verifyAdminReflectsOwnershipTransfer() (gas: 23022) AdminVerifierSuperAdminTest:test_verifyAdminReturnsFalseForOthers() (gas: 9984) AdminVerifierSuperAdminTest:test_verifyAdminReturnsTrueForOwner() (gas: 10026) @@ -49,20 +52,20 @@ AdminVerifierWhitelistTest:test_excludeRemovesWhitelistEntry() (gas: 56055) AdminVerifierWhitelistTest:test_releaseExclusionByReleaser() (gas: 32588) AdminVerifierWhitelistTest:test_removeFromWhitelist() (gas: 35882) AdminVerifierWhitelistTest:test_verifyAdmin() (gas: 57812) -Batch:testFuzz_batchResetStorage(bytes32) (runs: 256, μ: 171867, ~: 171867) -Batch:test_batch(bytes32,bytes32) (runs: 256, μ: 316333, ~: 316333) +Batch:testFuzz_batchResetStorage(bytes32) (runs: 256, μ: 171911, ~: 171911) +Batch:test_batch(bytes32,bytes32) (runs: 256, μ: 316223, ~: 316223) Batch:test_isAdmin() (gas: 953739) BatchTest:test_batchExecutesAllCalls() (gas: 34598) BatchTest:test_batchIsNotPayable() (gas: 16262) BatchTest:test_batchRevertsWhenInnerCallFails() (gas: 12304) -Constructor:test_assertionTimelockZero() (gas: 98568) +Constructor:test_assertionTimelockZero() (gas: 98888) Constructor:test_isAdmin() (gas: 953761) DAVerifierECDSATest:testFuzz_RevertIf_verifyDAWithWrongProver(bytes32,bytes,uint256) (runs: 256, μ: 18769, ~: 18765) -DAVerifierECDSATest:testFuzz_RevertIf_verifyDAwithInvalidSignature(bytes32,bytes,bytes) (runs: 256, μ: 11413, ~: 11410) +DAVerifierECDSATest:testFuzz_RevertIf_verifyDAwithInvalidSignature(bytes32,bytes,bytes) (runs: 256, μ: 11412, ~: 11410) DAVerifierECDSATest:testFuzz_verifyDA(bytes32,bytes) (runs: 256, μ: 17575, ~: 17571) -DAVerifierOnChainTest:testFuzz_verifyDA_invalidProof(bytes32,bytes,bytes) (runs: 256, μ: 11683, ~: 11673) -DAVerifierOnChainTest:testFuzz_verifyDA_metadataIgnored(bytes,bytes,bytes) (runs: 256, μ: 11626, ~: 11604) -DAVerifierOnChainTest:testFuzz_verifyDA_validProof(bytes,bytes) (runs: 256, μ: 8575, ~: 8563) +DAVerifierOnChainTest:testFuzz_verifyDA_invalidProof(bytes32,bytes,bytes) (runs: 256, μ: 11682, ~: 11671) +DAVerifierOnChainTest:testFuzz_verifyDA_metadataIgnored(bytes,bytes,bytes) (runs: 256, μ: 11626, ~: 11601) +DAVerifierOnChainTest:testFuzz_verifyDA_validProof(bytes,bytes) (runs: 256, μ: 8574, ~: 8563) DAVerifierOnChainTest:test_verifyDA_deterministicPure() (gas: 10415) DAVerifierOnChainTest:test_verifyDA_emptyProof() (gas: 7323) DAVerifierOnChainTest:test_verifyDA_emptyProofMismatch() (gas: 7397) @@ -75,201 +78,212 @@ DAVerifierRegistryTest:test_isRegistered_returnsTrueAfterAdd() (gas: 34462) DAVerifierRegistryTest:test_remove() (gas: 26284) DAVerifierRegistryTest:test_remove_RevertIf_NotRegistered() (gas: 13624) DAVerifierRegistryTest:test_remove_emitsEvent() (gas: 28380) -DeployCoreWithStagingIntegrationTest:test_AddAssertionWithOnChainDAVerifierOnBothOracles() (gas: 393263) -DeployCoreWithStagingIntegrationTest:test_AddToWhitelistOnBothOracles() (gas: 91286) +DeployAdminVerifiersTest:test_RevertIf_coreDeployWhitelistVerifierWithoutAdmin() (gas: 10651341) +DeployAdminVerifiersTest:test_RevertIf_testingScriptAddsVerifiersWithoutStateOracle() (gas: 2918354) +DeployAdminVerifiersTest:test_RevertIf_testingScriptDeploysSuperAdminVerifierWithoutAdmin() (gas: 2691075) +DeployAdminVerifiersTest:test_RevertIf_testingScriptEntrypointsRunOutsideTestingDeployment() (gas: 2557920) +DeployAdminVerifiersTest:test_coreDeployAdminVerifiersAddsOnlyProductionVerifiersToStateOracle() (gas: 17766308) +DeployAdminVerifiersTest:test_testingScriptAllowsEntrypointsInTestingDeployment() (gas: 2789938) +DeployAdminVerifiersTest:test_testingScriptDeploysAndAddsTestVerifiersToStateOracle() (gas: 8796534) +DeployCoreWithStagingIntegrationTest:test_AddAssertionWithOnChainDAVerifierOnBothOracles() (gas: 393189) +DeployCoreWithStagingIntegrationTest:test_AddToWhitelistOnBothOracles() (gas: 91300) DeployCoreWithStagingIntegrationTest:test_BothOraclesDeployed() (gas: 5105) DeployCoreWithStagingIntegrationTest:test_BothOraclesHaveBothDAVerifiers() (gas: 41415) -DeployCoreWithStagingIntegrationTest:test_BothOraclesShareSameAdminVerifier() (gas: 29848) +DeployCoreWithStagingIntegrationTest:test_BothOraclesShareSameAdminVerifier() (gas: 29804) DeployCoreWithStagingIntegrationTest:test_ECDSAVerifierIsSharedAcrossOracles() (gas: 29871) -DeployCoreWithStagingIntegrationTest:test_FullWorkflow_WhitelistAndRegisterOnBothOracles() (gas: 281775) +DeployCoreWithStagingIntegrationTest:test_FullWorkflow_WhitelistAndRegisterOnBothOracles() (gas: 281701) DeployCoreWithStagingIntegrationTest:test_OnChainVerifierIsSharedAcrossOracles() (gas: 29849) DeployCoreWithStagingIntegrationTest:test_OraclesHaveDifferentConfigs() (gas: 29364) DeployCoreWithStagingIntegrationTest:test_PersistentAccountsFunded() (gas: 8375) -DeployCoreWithStagingIntegrationTest:test_RegisterContractOnBothOracles() (gas: 268796) -DisableWhitelist:testFuzz_RevertIf_disableWhitelistByUnauthorized(address) (runs: 256, μ: 22700, ~: 22700) -DisableWhitelist:test_RevertIf_disableAlreadyDisabledWhitelist() (gas: 25195) -DisableWhitelist:test_disableWhitelist() (gas: 28255) +DeployCoreWithStagingIntegrationTest:test_RegisterContractOnBothOracles() (gas: 268766) +DeployCoreWithStagingScriptIntegrationTest:test_CompleteDeterministicDeploymentSupportsTestingConfiguration() (gas: 24010955) +DeployWizardCreateXIntegrationTest:test_TestingDeploymentUsesCreateXForEveryContract() (gas: 11910662) +DisableWhitelist:testFuzz_RevertIf_disableWhitelistByUnauthorized(address) (runs: 256, μ: 22634, ~: 22634) +DisableWhitelist:test_RevertIf_disableAlreadyDisabledWhitelist() (gas: 25151) +DisableWhitelist:test_disableWhitelist() (gas: 28189) DisableWhitelist:test_isAdmin() (gas: 953761) -DisableWhitelist:test_nonWhitelistedUserWhenDisabled() (gas: 30133) -DisableWhitelist:test_whitelistedUserAfterReenabling() (gas: 60127) -DisableWhitelist:test_whitelistedUserWhenDisabled() (gas: 59811) -EnableWhitelist:testFuzz_RevertIf_enableWhitelistByUnauthorized(address) (runs: 256, μ: 22721, ~: 22721) -EnableWhitelist:test_RevertIf_enableAlreadyEnabledWhitelist() (gas: 25245) -EnableWhitelist:test_enableWhitelist() (gas: 28214) +DisableWhitelist:test_nonWhitelistedUserWhenDisabled() (gas: 30089) +DisableWhitelist:test_whitelistedUserAfterReenabling() (gas: 60090) +DisableWhitelist:test_whitelistedUserWhenDisabled() (gas: 59774) +EnableWhitelist:testFuzz_RevertIf_enableWhitelistByUnauthorized(address) (runs: 256, μ: 22655, ~: 22655) +EnableWhitelist:test_RevertIf_enableAlreadyEnabledWhitelist() (gas: 25201) +EnableWhitelist:test_enableWhitelist() (gas: 28148) EnableWhitelist:test_isAdmin() (gas: 953761) -GrantGuardianAdminRole:testFuzz_RevertIf_nonOwnerGrantsGuardianAdminRole(address) (runs: 256, μ: 18745, ~: 18745) +GrantGuardianAdminRole:testFuzz_RevertIf_nonOwnerGrantsGuardianAdminRole(address) (runs: 256, μ: 18723, ~: 18723) GrantGuardianAdminRole:test_isAdmin() (gas: 953695) -GrantGuardianAdminRole:test_ownerCanGrantGuardianAdminRole() (gas: 50860) -GrantGuardianRole:testFuzz_RevertIf_nonGuardianAdminGrantsGuardianRole(address) (runs: 256, μ: 20748, ~: 20748) -GrantGuardianRole:test_guardianAdminCanGrantGuardianRole() (gas: 51186) +GrantGuardianAdminRole:test_ownerCanGrantGuardianAdminRole() (gas: 50926) +GrantGuardianRole:testFuzz_RevertIf_nonGuardianAdminGrantsGuardianRole(address) (runs: 256, μ: 20814, ~: 20814) +GrantGuardianRole:test_guardianAdminCanGrantGuardianRole() (gas: 51142) GrantGuardianRole:test_isAdmin() (gas: 953761) -GrantOperatorAdminRole:testFuzz_RevertIf_nonOwnerGrantsOperatorAdminRole(address) (runs: 256, μ: 18720, ~: 18720) +GrantOperatorAdminRole:testFuzz_RevertIf_nonOwnerGrantsOperatorAdminRole(address) (runs: 256, μ: 18698, ~: 18698) GrantOperatorAdminRole:test_isAdmin() (gas: 953739) -GrantOperatorAdminRole:test_ownerCanGrantOperatorAdminRole() (gas: 50814) -GrantOperatorRole:testFuzz_RevertIf_nonOperatorAdminGrantsOperatorRole(address) (runs: 256, μ: 28131, ~: 28131) -GrantOperatorRole:testFuzz_RevertIf_nonOperatorAdminGrantsOperatorRoleUsingHelper(address) (runs: 256, μ: 24026, ~: 24026) +GrantOperatorAdminRole:test_ownerCanGrantOperatorAdminRole() (gas: 50882) +GrantOperatorRole:testFuzz_RevertIf_nonOperatorAdminGrantsOperatorRole(address) (runs: 256, μ: 28243, ~: 28243) +GrantOperatorRole:testFuzz_RevertIf_nonOperatorAdminGrantsOperatorRoleUsingHelper(address) (runs: 256, μ: 24071, ~: 24071) GrantOperatorRole:test_isAdmin() (gas: 953783) -GrantOperatorRole:test_ownerCanGrantOperatorRole() (gas: 56290) -GrantOperatorRole:test_ownerCanGrantOperatorRoleUsingHelper() (gas: 51069) -GrantOperatorRole:test_stateOracleAdminHasDefaultAdminRole() (gas: 16070) -GuardianAdminCanManageGuardians:testFuzz_RevertIf_nonGuardianAdminGrantsGuardianRole(address) (runs: 256, μ: 21640, ~: 21640) -GuardianAdminCanManageGuardians:testFuzz_RevertIf_nonGuardianAdminRevokesGuardianRole(address) (runs: 256, μ: 51968, ~: 51968) -GuardianAdminCanManageGuardians:test_guardianAdminCanGrantGuardianRole() (gas: 51908) -GuardianAdminCanManageGuardians:test_guardianAdminCanRevokeGuardianRole() (gas: 42415) +GrantOperatorRole:test_ownerCanGrantOperatorRole() (gas: 56447) +GrantOperatorRole:test_ownerCanGrantOperatorRoleUsingHelper() (gas: 51159) +GrantOperatorRole:test_stateOracleAdminHasDefaultAdminRole() (gas: 16114) +GuardianAdminCanManageGuardians:testFuzz_RevertIf_nonGuardianAdminGrantsGuardianRole(address) (runs: 256, μ: 21706, ~: 21706) +GuardianAdminCanManageGuardians:testFuzz_RevertIf_nonGuardianAdminRevokesGuardianRole(address) (runs: 256, μ: 52101, ~: 52101) +GuardianAdminCanManageGuardians:test_guardianAdminCanGrantGuardianRole() (gas: 51864) +GuardianAdminCanManageGuardians:test_guardianAdminCanRevokeGuardianRole() (gas: 42433) GuardianAdminCanManageGuardians:test_isAdmin() (gas: 953695) -GuardianEmergencyActions:test_guardianCanRemoveAssertion() (gas: 271169) -GuardianEmergencyActions:test_guardianCanRevokeManager() (gas: 181949) -GuardianEmergencyActions:test_guardianCannotAddAdminVerifier() (gas: 264624) -GuardianEmergencyActions:test_guardianCannotAddDAVerifier() (gas: 20692) -GuardianEmergencyActions:test_guardianCannotAddToWhitelist() (gas: 27789) -GuardianEmergencyActions:test_guardianCannotDisableWhitelist() (gas: 19939) -GuardianEmergencyActions:test_guardianCannotEnableWhitelist() (gas: 30615) -GuardianEmergencyActions:test_guardianCannotRemoveAdminVerifier() (gas: 22749) -GuardianEmergencyActions:test_guardianCannotRemoveDAVerifier() (gas: 22794) -GuardianEmergencyActions:test_guardianCannotRemoveFromWhitelist() (gas: 54997) -GuardianEmergencyActions:test_guardianCannotSetMaxAssertionsPerAA() (gas: 20420) +GuardianEmergencyActions:test_guardianCanRemoveAssertion() (gas: 271110) +GuardianEmergencyActions:test_guardianCanRevokeManager() (gas: 181934) +GuardianEmergencyActions:test_guardianCannotAddAdminVerifier() (gas: 264666) +GuardianEmergencyActions:test_guardianCannotAddDAVerifier() (gas: 20648) +GuardianEmergencyActions:test_guardianCannotAddToWhitelist() (gas: 27834) +GuardianEmergencyActions:test_guardianCannotDisableWhitelist() (gas: 19895) +GuardianEmergencyActions:test_guardianCannotEnableWhitelist() (gas: 30549) +GuardianEmergencyActions:test_guardianCannotRemoveAdminVerifier() (gas: 22705) +GuardianEmergencyActions:test_guardianCannotRemoveDAVerifier() (gas: 22750) +GuardianEmergencyActions:test_guardianCannotRemoveFromWhitelist() (gas: 55049) +GuardianEmergencyActions:test_guardianCannotSetMaxAssertionsPerAA() (gas: 20398) GuardianEmergencyActions:test_isAdmin() (gas: 953761) GuardianRoleBase:test_isAdmin() (gas: 953761) GuardianRoleBase:test_isAdmin() (gas: 953761) -Initialize:test_RevertIf_alreadyInitialized() (gas: 21222) -Initialize:test_RevertIf_initializeNonProxy() (gas: 4097793) -Initialize:test_isAdmin() (gas: 953761) -InitializeWhitelist:test_initialNonWhitelistedUser() (gas: 19126) -InitializeWhitelist:test_initialWhitelistState() (gas: 13110) +Initialize:test_RevertIf_alreadyInitialized() (gas: 21244) +Initialize:test_RevertIf_initializeNonProxy() (gas: 4293371) +Initialize:test_initializeWithWhitelistDisabled() (gas: 5459554) +Initialize:test_initializeWithWhitelistEnabledAndInitialAccount() (gas: 5496038) +Initialize:test_isAdmin() (gas: 953783) +InitializeWhitelist:test_initialNonWhitelistedUser() (gas: 19104) +InitializeWhitelist:test_initialWhitelistState() (gas: 13088) InitializeWhitelist:test_isAdmin() (gas: 953761) -OperatorAdminCanManageOperators:testFuzz_RevertIf_nonOperatorAdminGrantsOperatorRole(address) (runs: 256, μ: 24963, ~: 24963) -OperatorAdminCanManageOperators:testFuzz_RevertIf_nonOperatorAdminRevokesOperatorRole(address) (runs: 256, μ: 55290, ~: 55290) +OperatorAdminCanManageOperators:testFuzz_RevertIf_nonOperatorAdminGrantsOperatorRole(address) (runs: 256, μ: 25008, ~: 25008) +OperatorAdminCanManageOperators:testFuzz_RevertIf_nonOperatorAdminRevokesOperatorRole(address) (runs: 256, μ: 55313, ~: 55313) OperatorAdminCanManageOperators:test_isAdmin() (gas: 953739) -OperatorAdminCanManageOperators:test_operatorAdminCanGrantOperatorRole() (gas: 51812) -OperatorAdminCanManageOperators:test_operatorAdminCanRevokeOperatorRole() (gas: 42391) -OperatorAdminCanManageOperators:test_operatorAdminCannotManageWhitelist() (gas: 20800) -OperatorAdminCanManageOperators:test_operatorAdminCannotRemoveAssertionByOwner() (gas: 260112) -OperatorAdminCanManageOperators:test_operatorAdminCannotRevokeManager() (gas: 195238) -OperatorCanManageWhitelist:testFuzz_RevertIf_nonOperatorAddsToWhitelist(address) (runs: 256, μ: 31801, ~: 31801) -OperatorCanManageWhitelist:testFuzz_RevertIf_nonOperatorRemovesFromWhitelist(address) (runs: 256, μ: 60955, ~: 60955) +OperatorAdminCanManageOperators:test_operatorAdminCanGrantOperatorRole() (gas: 51902) +OperatorAdminCanManageOperators:test_operatorAdminCanRevokeOperatorRole() (gas: 42445) +OperatorAdminCanManageOperators:test_operatorAdminCannotManageWhitelist() (gas: 20845) +OperatorAdminCanManageOperators:test_operatorAdminCannotRemoveAssertionByOwner() (gas: 260053) +OperatorAdminCanManageOperators:test_operatorAdminCannotRevokeManager() (gas: 195223) +OperatorCanManageWhitelist:testFuzz_RevertIf_nonOperatorAddsToWhitelist(address) (runs: 256, μ: 31869, ~: 31869) +OperatorCanManageWhitelist:testFuzz_RevertIf_nonOperatorRemovesFromWhitelist(address) (runs: 256, μ: 61030, ~: 61030) OperatorCanManageWhitelist:test_isAdmin() (gas: 953783) -OperatorCanManageWhitelist:test_operatorCanAddToWhitelist() (gas: 51900) -OperatorCanManageWhitelist:test_operatorCanRemoveFromWhitelist() (gas: 41553) +OperatorCanManageWhitelist:test_operatorCanAddToWhitelist() (gas: 51885) +OperatorCanManageWhitelist:test_operatorCanRemoveFromWhitelist() (gas: 41524) OperatorRoleBase:test_isAdmin() (gas: 953761) OwnableTest:test_isAdmin() (gas: 953717) -OwnableTest:test_newOwnerCanGrantRolesAsOwner() (gas: 229628) -OwnableTest:test_ownerIsInitializerOnProxy() (gas: 13577) -OwnableTest:test_ownerIsZeroOnImplementation() (gas: 3833400) -OwnableTest:test_transferOwnership() (gas: 230046) +OwnableTest:test_newOwnerCanGrantRolesAsOwner() (gas: 229518) +OwnableTest:test_ownerIsInitializerOnProxy() (gas: 13555) +OwnableTest:test_ownerIsZeroOnImplementation() (gas: 4028956) +OwnableTest:test_transferOwnership() (gas: 230222) OwnerOnlyFunctionsRemainProtected:test_isAdmin() (gas: 953805) -OwnerOnlyFunctionsRemainProtected:test_operatorCannotAddAdminVerifier() (gas: 264602) -OwnerOnlyFunctionsRemainProtected:test_operatorCannotAddDAVerifier() (gas: 20650) -OwnerOnlyFunctionsRemainProtected:test_operatorCannotDisableWhitelist() (gas: 19961) -OwnerOnlyFunctionsRemainProtected:test_operatorCannotEnableWhitelist() (gas: 30658) -OwnerOnlyFunctionsRemainProtected:test_operatorCannotRemoveAdminVerifier() (gas: 22749) -OwnerOnlyFunctionsRemainProtected:test_operatorCannotRemoveAssertionByOwner() (gas: 260107) -OwnerOnlyFunctionsRemainProtected:test_operatorCannotRemoveDAVerifier() (gas: 22748) -OwnerOnlyFunctionsRemainProtected:test_operatorCannotRevokeManager() (gas: 195232) -OwnerOnlyFunctionsRemainProtected:test_operatorCannotSetMaxAssertionsPerAA() (gas: 20376) +OwnerOnlyFunctionsRemainProtected:test_operatorCannotAddAdminVerifier() (gas: 264644) +OwnerOnlyFunctionsRemainProtected:test_operatorCannotAddDAVerifier() (gas: 20606) +OwnerOnlyFunctionsRemainProtected:test_operatorCannotDisableWhitelist() (gas: 19917) +OwnerOnlyFunctionsRemainProtected:test_operatorCannotEnableWhitelist() (gas: 30592) +OwnerOnlyFunctionsRemainProtected:test_operatorCannotRemoveAdminVerifier() (gas: 22705) +OwnerOnlyFunctionsRemainProtected:test_operatorCannotRemoveAssertionByOwner() (gas: 260048) +OwnerOnlyFunctionsRemainProtected:test_operatorCannotRemoveDAVerifier() (gas: 22704) +OwnerOnlyFunctionsRemainProtected:test_operatorCannotRevokeManager() (gas: 195217) +OwnerOnlyFunctionsRemainProtected:test_operatorCannotSetMaxAssertionsPerAA() (gas: 20354) ProxyHelper:test_isAdmin() (gas: 953783) -Register:testFuzz_RevertIf_registerAssertionAdopterAdminVerifierNotAdded() (gas: 264776) -Register:testFuzz_RevertIf_registerByUnauthorized(address) (runs: 256, μ: 142245, ~: 142245) -Register:test_RevertIf_registerAssertionAdopterTwice() (gas: 167308) -Register:test_expectAssertionAdopterAdded() (gas: 162235) +Register:testFuzz_RevertIf_registerAssertionAdopterAdminVerifierNotAdded() (gas: 264754) +Register:testFuzz_RevertIf_registerByUnauthorized(address) (runs: 256, μ: 142213, ~: 142223) +Register:test_RevertIf_registerAssertionAdopterTwice() (gas: 167264) +Register:test_expectAssertionAdopterAdded() (gas: 162213) Register:test_isAdmin() (gas: 953695) -Register:test_registerByOwner() (gas: 159694) -RegisterAssertionAdopterWithWhitelist:testFuzz_RevertIf_registerByNonWhitelistedUser(address) (runs: 256, μ: 126516, ~: 126516) +Register:test_registerByOwner() (gas: 159672) +RegisterAssertionAdopterWithWhitelist:testFuzz_RevertIf_registerByNonWhitelistedUser(address) (runs: 256, μ: 126494, ~: 126494) RegisterAssertionAdopterWithWhitelist:test_isAdmin() (gas: 953761) -RegisterAssertionAdopterWithWhitelist:test_registerByNonWhitelistedUserWhenDisabled() (gas: 168714) -RegisterAssertionAdopterWithWhitelist:test_registerByWhitelistedUser() (gas: 189529) -RemoveAdminVerifier:testFuzz_RevertIf_removeAdminVerifierByUnauthorized(address) (runs: 256, μ: 303005, ~: 303005) -RemoveAdminVerifier:testFuzz_RevertIf_removeAdminVerifierNotRegistered(address) (runs: 256, μ: 22963, ~: 22963) +RegisterAssertionAdopterWithWhitelist:test_registerByNonWhitelistedUserWhenDisabled() (gas: 168670) +RegisterAssertionAdopterWithWhitelist:test_registerByWhitelistedUser() (gas: 189514) +RemoveAdminVerifier:testFuzz_RevertIf_removeAdminVerifierByUnauthorized(address) (runs: 256, μ: 302981, ~: 302981) +RemoveAdminVerifier:testFuzz_RevertIf_removeAdminVerifierNotRegistered(address) (runs: 256, μ: 22941, ~: 22941) RemoveAdminVerifier:test_isAdmin() (gas: 953695) -RemoveAdminVerifier:test_removeAdminVerifier() (gas: 275567) -RemoveAssertion:testFuzz_RevertIf_removeAssertionAlreadyRemoved(bytes32) (runs: 256, μ: 254138, ~: 254138) -RemoveAssertion:testFuzz_RevertIf_removeAssertionByUnauthorized(bytes32,address) (runs: 256, μ: 240379, ~: 240379) -RemoveAssertion:testFuzz_RevertIf_removeAssertionByUnauthorizedAdmin(bytes32,address) (runs: 256, μ: 252869, ~: 252869) -RemoveAssertion:testFuzz_RevertIf_removeNonExistentAssertion(bytes32) (runs: 256, μ: 164568, ~: 164568) -RemoveAssertion:testFuzz_removeAssertion(bytes32) (runs: 256, μ: 250783, ~: 250783) -RemoveAssertion:testFuzz_removeAssertionByAdmin(bytes32) (runs: 256, μ: 256811, ~: 256811) -RemoveAssertion:test_expectAssertionRemoved(bytes32) (runs: 256, μ: 247151, ~: 247151) +RemoveAdminVerifier:test_removeAdminVerifier() (gas: 275609) +RemoveAssertion:testFuzz_RevertIf_removeAssertionAlreadyRemoved(bytes32) (runs: 256, μ: 254251, ~: 254251) +RemoveAssertion:testFuzz_RevertIf_removeAssertionByUnauthorized(bytes32,address) (runs: 256, μ: 240313, ~: 240313) +RemoveAssertion:testFuzz_RevertIf_removeAssertionByUnauthorizedAdmin(bytes32,address) (runs: 256, μ: 252759, ~: 252759) +RemoveAssertion:testFuzz_RevertIf_removeNonExistentAssertion(bytes32) (runs: 256, μ: 164524, ~: 164524) +RemoveAssertion:testFuzz_removeAssertion(bytes32) (runs: 256, μ: 250717, ~: 250717) +RemoveAssertion:testFuzz_removeAssertionByAdmin(bytes32) (runs: 256, μ: 256723, ~: 256723) +RemoveAssertion:test_expectAssertionRemoved(bytes32) (runs: 256, μ: 247085, ~: 247085) RemoveAssertion:test_isAdmin() (gas: 953761) RemoveAssertionWithWhitelist:test_isAdmin() (gas: 953695) -RemoveAssertionWithWhitelist:test_removeAssertionByNonWhitelistedManager() (gas: 250085) -RemoveDAVerifier:testFuzz_RevertIf_removeDAVerifierByUnauthorized(address) (runs: 256, μ: 59097, ~: 59097) -RemoveDAVerifier:testFuzz_RevertIf_removeDAVerifierNotRegistered(address) (runs: 256, μ: 23005, ~: 23005) +RemoveAssertionWithWhitelist:test_removeAssertionByNonWhitelistedManager() (gas: 250004) +RemoveDAVerifier:testFuzz_RevertIf_removeDAVerifierByUnauthorized(address) (runs: 256, μ: 58965, ~: 58965) +RemoveDAVerifier:testFuzz_RevertIf_removeDAVerifierNotRegistered(address) (runs: 256, μ: 22983, ~: 22983) RemoveDAVerifier:test_isAdmin() (gas: 953695) -RemoveDAVerifier:test_removeDAVerifier() (gas: 39256) -RemoveFromWhitelist:testFuzz_RevertIf_removeFromWhitelistByUnauthorized(address) (runs: 256, μ: 60107, ~: 60107) -RemoveFromWhitelist:test_RevertIf_removeNonWhitelistedAccount() (gas: 21207) +RemoveDAVerifier:test_removeDAVerifier() (gas: 39186) +RemoveFromWhitelist:testFuzz_RevertIf_removeFromWhitelistByUnauthorized(address) (runs: 256, μ: 60182, ~: 60182) +RemoveFromWhitelist:test_RevertIf_removeNonWhitelistedAccount() (gas: 21185) RemoveFromWhitelist:test_isAdmin() (gas: 953761) -RemoveFromWhitelist:test_removeFromWhitelist() (gas: 43248) +RemoveFromWhitelist:test_removeFromWhitelist() (gas: 43219) RenounceOwnershipTest:test_isAdmin() (gas: 953695) -RenounceOwnershipTest:test_renounceOwnershipMaintainsInvariant() (gas: 107828) -ResetStorage:testFuzz_RevertIf_resetStorageByUnauthorized(bytes32,address) (runs: 256, μ: 164399, ~: 164399) -ResetStorage:testFuzz_RevertIf_resetStorageForUnregisteredAdopter(address,bytes32,address) (runs: 256, μ: 16873, ~: 16873) -ResetStorage:testFuzz_resetStorage(bytes32) (runs: 256, μ: 175835, ~: 175835) +RenounceOwnershipTest:test_renounceOwnershipMaintainsInvariant() (gas: 107935) +ResetStorage:testFuzz_RevertIf_resetStorageByUnauthorized(bytes32,address) (runs: 256, μ: 164443, ~: 164443) +ResetStorage:testFuzz_RevertIf_resetStorageForUnregisteredAdopter(address,bytes32,address) (runs: 256, μ: 16939, ~: 16939) +ResetStorage:testFuzz_resetStorage(bytes32) (runs: 256, μ: 175946, ~: 175946) ResetStorage:test_isAdmin() (gas: 953783) -ResetStorage:test_resetStorageAllowsZeroStorageKey() (gas: 168440) -RevokeGuardianAdminRole:testFuzz_RevertIf_nonOwnerRevokesGuardianAdminRole(address) (runs: 256, μ: 18701, ~: 18701) +ResetStorage:test_resetStorageAllowsZeroStorageKey() (gas: 168484) +RevokeGuardianAdminRole:testFuzz_RevertIf_nonOwnerRevokesGuardianAdminRole(address) (runs: 256, μ: 18766, ~: 18766) RevokeGuardianAdminRole:test_isAdmin() (gas: 953739) -RevokeGuardianAdminRole:test_ownerCanRevokeGuardianAdminRole() (gas: 29640) -RevokeGuardianRole:testFuzz_RevertIf_nonGuardianAdminRevokesGuardianRole(address) (runs: 256, μ: 20720, ~: 20720) -RevokeGuardianRole:test_guardianAdminCanRevokeGuardianRole() (gas: 29892) +RevokeGuardianAdminRole:test_ownerCanRevokeGuardianAdminRole() (gas: 29793) +RevokeGuardianRole:testFuzz_RevertIf_nonGuardianAdminRevokesGuardianRole(address) (runs: 256, μ: 20853, ~: 20853) +RevokeGuardianRole:test_guardianAdminCanRevokeGuardianRole() (gas: 29915) RevokeGuardianRole:test_isAdmin() (gas: 953695) -RevokeManager:testFuzz_RevertIf_revokeManagerByUnauthorized(address) (runs: 256, μ: 176360, ~: 176360) -RevokeManager:testFuzz_revokeManager(address) (runs: 256, μ: 171606, ~: 171606) -RevokeManager:test_RevertIf_revokeManagerOfNonExistentAdopter() (gas: 129205) +RevokeManager:testFuzz_RevertIf_revokeManagerByUnauthorized(address) (runs: 256, μ: 176294, ~: 176294) +RevokeManager:testFuzz_revokeManager(address) (runs: 256, μ: 171540, ~: 171540) +RevokeManager:test_RevertIf_revokeManagerOfNonExistentAdopter() (gas: 129183) RevokeManager:test_isAdmin() (gas: 953695) -RevokeOperatorAdminRole:testFuzz_RevertIf_nonOwnerRevokesOperatorAdminRole(address) (runs: 256, μ: 18678, ~: 18678) +RevokeOperatorAdminRole:testFuzz_RevertIf_nonOwnerRevokesOperatorAdminRole(address) (runs: 256, μ: 18745, ~: 18745) RevokeOperatorAdminRole:test_isAdmin() (gas: 953739) -RevokeOperatorAdminRole:test_ownerCanRevokeOperatorAdminRole() (gas: 29662) -RevokeOperatorRole:testFuzz_RevertIf_nonOperatorAdminRevokesOperatorRole(address) (runs: 256, μ: 28131, ~: 28131) -RevokeOperatorRole:testFuzz_RevertIf_nonOperatorAdminRevokesOperatorRoleUsingHelper(address) (runs: 256, μ: 23977, ~: 23977) +RevokeOperatorAdminRole:test_ownerCanRevokeOperatorAdminRole() (gas: 29819) +RevokeOperatorRole:testFuzz_RevertIf_nonOperatorAdminRevokesOperatorRole(address) (runs: 256, μ: 28221, ~: 28221) +RevokeOperatorRole:testFuzz_RevertIf_nonOperatorAdminRevokesOperatorRoleUsingHelper(address) (runs: 256, μ: 24000, ~: 24000) RevokeOperatorRole:test_isAdmin() (gas: 953783) -RevokeOperatorRole:test_ownerCanRevokeOperatorRole() (gas: 35064) -RevokeOperatorRole:test_ownerCanRevokeOperatorRoleUsingHelper() (gas: 29907) +RevokeOperatorRole:test_ownerCanRevokeOperatorRole() (gas: 35199) +RevokeOperatorRole:test_ownerCanRevokeOperatorRoleUsingHelper() (gas: 29975) RevokeOperatorRoleBase:test_isAdmin() (gas: 953761) -SetMaxAssertionsPerAA:testFuzz_RevertIf_setMaxAssertionsPerAAByUnauthorized(uint16,address) (runs: 256, μ: 26703, ~: 26703) -SetMaxAssertionsPerAA:test_AddAssertionsThenLowerMaxAndRevertOnAdd() (gas: 312177) +SetMaxAssertionsPerAA:testFuzz_RevertIf_setMaxAssertionsPerAAByUnauthorized(uint16,address) (runs: 256, μ: 26637, ~: 26637) +SetMaxAssertionsPerAA:test_AddAssertionsThenLowerMaxAndRevertOnAdd() (gas: 312089) SetMaxAssertionsPerAA:test_isAdmin() (gas: 953761) -SetMaxAssertionsPerAA:test_setMaxAssertionsPerAA(uint16) (runs: 256, μ: 23728, ~: 24230) +SetMaxAssertionsPerAA:test_setMaxAssertionsPerAA(uint16) (runs: 256, μ: 23623, ~: 24230) StateOracleAccessControlBase:test_isAdmin() (gas: 953761) StateOracleBase:test_isAdmin() (gas: 953761) -StateOracleOwnerECDSATest:testFuzz_addAssertionWithValidProof(bytes32) (runs: 256, μ: 93774, ~: 93774) -StateOracleOwnerECDSATest:test_RevertIf_addAssertionWithInvalidProof() (gas: 42527) -StateOracleOwnerECDSATest:test_addAndRemoveAssertion() (gas: 113950) -StateOracleOwnerECDSATest:test_addAssertionWithValidProof() (gas: 100965) -StateOracleOwnerECDSATest:test_addMultipleAssertions() (gas: 141263) -StateOracleOwnerECDSATest:test_assertionAddedEventEmitted() (gas: 98593) +StateOracleOwnerECDSATest:testFuzz_addAssertionWithValidProof(bytes32) (runs: 256, μ: 93752, ~: 93752) +StateOracleOwnerECDSATest:test_RevertIf_addAssertionWithInvalidProof() (gas: 42505) +StateOracleOwnerECDSATest:test_addAndRemoveAssertion() (gas: 113906) +StateOracleOwnerECDSATest:test_addAssertionWithValidProof() (gas: 100943) +StateOracleOwnerECDSATest:test_addMultipleAssertions() (gas: 141286) +StateOracleOwnerECDSATest:test_assertionAddedEventEmitted() (gas: 98571) StateOracleOwnerECDSATest:test_isAdmin() (gas: 953695) -StateOracleOwnerOnChainTest:testFuzz_addAssertionWithValidProof(bytes32) (runs: 256, μ: 88319, ~: 88319) -StateOracleOwnerOnChainTest:test_RevertIf_addAssertionWithInvalidProof() (gas: 39504) -StateOracleOwnerOnChainTest:test_addAndRemoveAssertion() (gas: 108495) -StateOracleOwnerOnChainTest:test_addAssertionWithValidProof() (gas: 95510) -StateOracleOwnerOnChainTest:test_addMultipleAssertions() (gas: 130356) -StateOracleOwnerOnChainTest:test_assertionAddedEventEmitted() (gas: 92620) +StateOracleOwnerOnChainTest:testFuzz_addAssertionWithValidProof(bytes32) (runs: 256, μ: 88297, ~: 88297) +StateOracleOwnerOnChainTest:test_RevertIf_addAssertionWithInvalidProof() (gas: 39482) +StateOracleOwnerOnChainTest:test_addAndRemoveAssertion() (gas: 108451) +StateOracleOwnerOnChainTest:test_addAssertionWithValidProof() (gas: 95488) +StateOracleOwnerOnChainTest:test_addMultipleAssertions() (gas: 130379) +StateOracleOwnerOnChainTest:test_assertionAddedEventEmitted() (gas: 92598) StateOracleOwnerOnChainTest:test_isAdmin() (gas: 953695) -StateOracleWhitelistECDSATest:testFuzz_addAssertionWithValidProof(bytes32) (runs: 256, μ: 93774, ~: 93774) -StateOracleWhitelistECDSATest:test_RevertIf_addAssertionWithInvalidProof() (gas: 42527) -StateOracleWhitelistECDSATest:test_addAndRemoveAssertion() (gas: 113950) -StateOracleWhitelistECDSATest:test_addAssertionWithValidProof() (gas: 100965) -StateOracleWhitelistECDSATest:test_addMultipleAssertions() (gas: 141263) -StateOracleWhitelistECDSATest:test_assertionAddedEventEmitted() (gas: 98593) +StateOracleWhitelistECDSATest:testFuzz_addAssertionWithValidProof(bytes32) (runs: 256, μ: 93752, ~: 93752) +StateOracleWhitelistECDSATest:test_RevertIf_addAssertionWithInvalidProof() (gas: 42505) +StateOracleWhitelistECDSATest:test_addAndRemoveAssertion() (gas: 113906) +StateOracleWhitelistECDSATest:test_addAssertionWithValidProof() (gas: 100943) +StateOracleWhitelistECDSATest:test_addMultipleAssertions() (gas: 141286) +StateOracleWhitelistECDSATest:test_assertionAddedEventEmitted() (gas: 98571) StateOracleWhitelistECDSATest:test_isAdmin() (gas: 953695) -StateOracleWhitelistOnChainTest:testFuzz_addAssertionWithValidProof(bytes32) (runs: 256, μ: 88319, ~: 88319) -StateOracleWhitelistOnChainTest:test_RevertIf_addAssertionWithInvalidProof() (gas: 39504) -StateOracleWhitelistOnChainTest:test_addAndRemoveAssertion() (gas: 108495) -StateOracleWhitelistOnChainTest:test_addAssertionWithValidProof() (gas: 95510) -StateOracleWhitelistOnChainTest:test_addMultipleAssertions() (gas: 130356) -StateOracleWhitelistOnChainTest:test_assertionAddedEventEmitted() (gas: 92620) +StateOracleWhitelistOnChainTest:testFuzz_addAssertionWithValidProof(bytes32) (runs: 256, μ: 88297, ~: 88297) +StateOracleWhitelistOnChainTest:test_RevertIf_addAssertionWithInvalidProof() (gas: 39482) +StateOracleWhitelistOnChainTest:test_addAndRemoveAssertion() (gas: 108451) +StateOracleWhitelistOnChainTest:test_addAssertionWithValidProof() (gas: 95488) +StateOracleWhitelistOnChainTest:test_addMultipleAssertions() (gas: 130379) +StateOracleWhitelistOnChainTest:test_assertionAddedEventEmitted() (gas: 92598) StateOracleWhitelistOnChainTest:test_isAdmin() (gas: 953695) -StateOracleWithDAVerifierECDSATest:testFuzz_RevertIf_addAssertionWithWrongProver(bytes32,bytes,uint256) (runs: 256, μ: 42675, ~: 42632) -StateOracleWithDAVerifierECDSATest:testFuzz_addAssertionWithECDSAProof(bytes32,bytes) (runs: 256, μ: 91496, ~: 91306) +StateOracleWithDAVerifierECDSATest:testFuzz_RevertIf_addAssertionWithWrongProver(bytes32,bytes,uint256) (runs: 256, μ: 42658, ~: 42610) +StateOracleWithDAVerifierECDSATest:testFuzz_addAssertionWithECDSAProof(bytes32,bytes) (runs: 256, μ: 91471, ~: 91284) StateOracleWithDAVerifierECDSATest:test_isAdmin() (gas: 953739) StorageIntegrity:test_isAdmin() (gas: 953695) -StorageIntegrity:test_storageIntegrity() (gas: 27097) -StorageIntegrity:test_storageIntegrityAfterDAVerifierRemoval() (gas: 269424) -StorageIntegrity:test_storageIntegrityAfterOperations() (gas: 279138) +StorageIntegrity:test_storageIntegrity() (gas: 27075) +StorageIntegrity:test_storageIntegrityAfterDAVerifierRemoval() (gas: 269292) +StorageIntegrity:test_storageIntegrityAfterOperations() (gas: 279028) TransferManagementBase:test_isAdmin() (gas: 953761) -TransferManager:testFuzz_RevertIf_transferManagerByUnauthorized(address,address) (runs: 256, μ: 203561, ~: 203561) -TransferManager:testFuzz_changePendingManager(address,address) (runs: 256, μ: 199181, ~: 199181) -TransferManager:testFuzz_transferManager(address) (runs: 256, μ: 194926, ~: 194926) -TransferManager:test_RevertIf_transferManagerToZeroAddress() (gas: 169304) +TransferManager:testFuzz_RevertIf_transferManagerByUnauthorized(address,address) (runs: 256, μ: 203407, ~: 203407) +TransferManager:testFuzz_changePendingManager(address,address) (runs: 256, μ: 199071, ~: 199071) +TransferManager:testFuzz_transferManager(address) (runs: 256, μ: 194882, ~: 194882) +TransferManager:test_RevertIf_transferManagerToZeroAddress() (gas: 169260) TransferManager:test_isAdmin() (gas: 953783) WhitelistBase:test_isAdmin() (gas: 953761) \ No newline at end of file diff --git a/.github/workflows/solidity-test.yml b/.github/workflows/solidity-test.yml index ea488e0..de36a60 100644 --- a/.github/workflows/solidity-test.yml +++ b/.github/workflows/solidity-test.yml @@ -113,3 +113,13 @@ jobs: *) echo "::error title=Storage layout check failed to run::shell/check_storage_layout.sh could not inspect the contract." exit 1 ;; esac + + deploy-wizard: + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - name: Test deployment wizard + run: python3 test/shell/test_deploy_wizard.py diff --git a/.storage-layout b/.storage-layout index b185919..f8162ce 100644 --- a/.storage-layout +++ b/.storage-layout @@ -25,7 +25,7 @@ "type": "t_mapping(t_bytes32,t_struct(RoleData)22_storage)" }, { - "astId": 1058, + "astId": 1067, "contract": "src/StateOracle.sol:StateOracle", "label": "assertionAdopters", "offset": 0, @@ -33,23 +33,23 @@ "type": "t_mapping(t_address,t_struct(AssertionAdopter)979_storage)" }, { - "astId": 1064, + "astId": 1073, "contract": "src/StateOracle.sol:StateOracle", "label": "adminVerifiers", "offset": 0, "slot": "4", - "type": "t_mapping(t_contract(IAdminVerifier)2524,t_bool)" + "type": "t_mapping(t_contract(IAdminVerifier)2650,t_bool)" }, { - "astId": 1070, + "astId": 1079, "contract": "src/StateOracle.sol:StateOracle", "label": "daVerifiers", "offset": 0, "slot": "5", - "type": "t_mapping(t_contract(IDAVerifier)2553,t_bool)" + "type": "t_mapping(t_contract(IDAVerifier)2679,t_bool)" }, { - "astId": 1075, + "astId": 1084, "contract": "src/StateOracle.sol:StateOracle", "label": "whitelist", "offset": 0, @@ -57,7 +57,7 @@ "type": "t_mapping(t_address,t_bool)" }, { - "astId": 1078, + "astId": 1087, "contract": "src/StateOracle.sol:StateOracle", "label": "whitelistEnabled", "offset": 0, @@ -65,7 +65,7 @@ "type": "t_bool" }, { - "astId": 1081, + "astId": 1090, "contract": "src/StateOracle.sol:StateOracle", "label": "maxAssertionsPerAA", "offset": 1, @@ -89,12 +89,12 @@ "label": "bytes32", "numberOfBytes": "32" }, - "t_contract(IAdminVerifier)2524": { + "t_contract(IAdminVerifier)2650": { "encoding": "inplace", "label": "contract IAdminVerifier", "numberOfBytes": "20" }, - "t_contract(IDAVerifier)2553": { + "t_contract(IDAVerifier)2679": { "encoding": "inplace", "label": "contract IDAVerifier", "numberOfBytes": "20" @@ -127,16 +127,16 @@ "numberOfBytes": "32", "value": "t_struct(RoleData)22_storage" }, - "t_mapping(t_contract(IAdminVerifier)2524,t_bool)": { + "t_mapping(t_contract(IAdminVerifier)2650,t_bool)": { "encoding": "mapping", - "key": "t_contract(IAdminVerifier)2524", + "key": "t_contract(IAdminVerifier)2650", "label": "mapping(contract IAdminVerifier => bool)", "numberOfBytes": "32", "value": "t_bool" }, - "t_mapping(t_contract(IDAVerifier)2553,t_bool)": { + "t_mapping(t_contract(IDAVerifier)2679,t_bool)": { "encoding": "mapping", - "key": "t_contract(IDAVerifier)2553", + "key": "t_contract(IDAVerifier)2679", "label": "mapping(contract IDAVerifier => bool)", "numberOfBytes": "32", "value": "t_bool" diff --git a/Makefile b/Makefile index 90a1349..17b54e6 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: check-storage-layout update-storage-layout check-abi +.PHONY: check-storage-layout deploy update-storage-layout check-abi check-storage-layout: @bash shell/check_storage_layout.sh @@ -10,3 +10,6 @@ update-storage-layout: # Compares against ABI_BASE_REF, defaulting to origin/main. check-abi: @bash shell/check_abi.sh $(ABI_BASE_REF) + +deploy: + ./shell/deploy_wizard.sh diff --git a/README.md b/README.md index 864d5f2..965ecc1 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,9 @@ Set the following environment variables before running the deployment scripts: - `DEPLOY_ADMIN_VERIFIER_OWNER` (true/false) - `DEPLOY_ADMIN_VERIFIER_WHITELIST` (true/false) - `ADMIN_VERIFIER_WHITELIST_ADMIN_ADDRESS` (required when whitelist verifier is enabled) +- `STATE_ORACLE_WHITELIST_ENABLED` (optional, defaults to true) +- `DEPLOY_ADMIN_VERIFIER_ALWAYS_APPROVE` (optional, defaults to false; testing only) +- `DEPLOYMENT_IS_TESTING` (must be true when the Always Approve verifier is enabled) The following additional variables apply only to `DeployCoreWithStaging.s.sol`: @@ -95,14 +98,28 @@ The following additional variables apply only to `DeployCoreWithStaging.s.sol`: ### Deployment Overview -Running `DeployCore` or `DeployCoreWithCreateX` will: +Running `DeployCore`, `DeployCoreWithCreateX`, or `DeployCoreWithStaging` will: 1. Deploy the DA verifier (ECDSA) and log its address. 2. Deploy the DA verifier (OnChain) and log its address. 3. Deploy `AdminVerifierOwner` if `DEPLOY_ADMIN_VERIFIER_OWNER=true` and log its address. 4. Deploy `AdminVerifierWhitelist` if `DEPLOY_ADMIN_VERIFIER_WHITELIST=true` (using `ADMIN_VERIFIER_WHITELIST_ADMIN_ADDRESS` as constructor default admin and initial whitelist admin) and log its address. -5. Deploy the `StateOracle` implementation and log its address. -6. Deploy the proxy, initialize it with the configured admin verifiers and DA verifiers, and log the proxy address. +5. Deploy `AdminVerifierAlwaysApprove` if `DEPLOY_ADMIN_VERIFIER_ALWAYS_APPROVE=true` and `DEPLOYMENT_IS_TESTING=true`, and log its address. +6. Deploy the `StateOracle` implementation and log its address. +7. Deploy the proxy, initialize it with the configured admin verifiers, DA verifiers, and initial whitelist state, and log the proxy address. + +`DeployCoreWithStaging` repeats the last two steps for the staging oracle. It inherits the CreateX deployment path, so both State Oracle implementations, both proxies, both DA verifiers, and every selected admin verifier use named CREATE3 salts. + +For a local testing deployment with registration whitelisting disabled and the Always Approve verifier enabled, combine the required values above with: + +```sh +DEPLOYMENT_IS_TESTING=true \ +STATE_ORACLE_WHITELIST_ENABLED=false \ +DEPLOY_ADMIN_VERIFIER_OWNER=false \ +DEPLOY_ADMIN_VERIFIER_WHITELIST=false \ +DEPLOY_ADMIN_VERIFIER_ALWAYS_APPROVE=true \ +forge script script/DeployCoreWithStaging.s.sol --rpc-url "$RPC_URL" --private-key "$DEPLOYER_PRIVATE_KEY" --broadcast +``` Console output will include labeled addresses for each deployed contract, e.g.: @@ -115,6 +132,36 @@ State Oracle Implementation deployed at
State Oracle Proxy deployed at
``` +### Interactive deployment wizard + +Run the terminal wizard from the repository root: + +```sh +make deploy +``` + +The wizard uses the deterministic CreateX deployment backend and guides you through: + +- production or testing mode, plus an optional staging State Oracle; +- admin verifier selection and assignment to the production and/or staging oracle; +- ECDSA and/or on-chain DA verification, independently assigned to each State Oracle; +- initial State Oracle whitelist state and addresses; +- optional explorer verification using `ETHERSCAN_API_KEY`; +- all State Oracle limits, timelocks, admins, and verifier-specific addresses; and +- a Foundry keystore account selected from `cast wallet list`. + +The wallet password and explorer API key are read without echoing. The password is checked before +deployment and stored only in a temporary mode-`600` file that is removed when the wizard exits. +Before broadcasting, the wizard prints a redacted Forge command and a complete configuration +summary. After broadcasting, it reads Foundry's receipt file and prints every deployment address, +block number, transaction hash, and proxy admin address. + +Testing mode also exposes the `Super Admin` and `Always Approve` admin verifiers. These options are +rejected by the Solidity deployment backend unless testing mode is explicitly enabled. Both testing +verifiers use named CREATE3 salts, as do every production and staging contract deployed by the wizard. +Every broadcast entrypoint in `DeployTestingAdminVerifiers.s.sol` likewise requires +`DEPLOYMENT_IS_TESTING=true`. + ## Installation 1. Clone the repository: @@ -132,8 +179,9 @@ forge install ### CreateX -The forge script `script/DeployCoreWithCreateX` uses the CreateX contract factory for maintaining and controlling contract addresses -of the protocol. +The forge scripts `script/DeployCoreWithCreateX.s.sol`, `script/DeployCoreWithStaging.s.sol`, `script/DeployWizard.s.sol`, and `script/DeployTestingAdminVerifiers.s.sol` use the CreateX contract factory to maintain stable protocol contract addresses. CREATE3 derives each address from the deployer and a contract-specific salt, so transaction order, deployer nonce, and contract init code do not change it. Production and staging State Oracles use different salts; the testing-only SuperAdmin and AlwaysApprove verifiers also have distinct salts shared by the wizard and standalone testing script. + +The fixed address must be empty when first deployed. On a retained chain, rerunning a salt whose contract already exists will revert; preserve the existing proxy and upgrade it for code changes, or reset the chain before performing a clean redeployment. The deployment address of CreateX is `0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed`. If CreateX is not deployed on your chain, you will find a helper script to deploy CreateX at `shell/deploy_create_x.sh`. @@ -169,6 +217,7 @@ STATE_ORACLE_ADMIN_ADDRESS=0xD2EfB83dd46094775188d927323b2523EaE3d087 \ DA_PROVER_ADDRESS=0x670cFA8781BF365Aefb6c048CDc522B857946C71 \ DEPLOY_ADMIN_VERIFIER_OWNER=true \ DEPLOY_ADMIN_VERIFIER_WHITELIST=true \ +STATE_ORACLE_WHITELIST_ENABLED=true \ ADMIN_VERIFIER_WHITELIST_ADMIN_ADDRESS=0xD2EfB83dd46094775188d927323b2523EaE3d087 \ forge script script/DeployCoreWithCreateX.s.sol --rpc-url http://localhost:8545 --private-key 0xac431098061ca49f5b36121d01a17d30e1d0624227d08b583ff328f1efe0d4a2 --broadcast ``` @@ -193,8 +242,8 @@ Private Key: 0xac431098061ca49f5b36121d01a17d30e1d0624227d08b583ff328f1efe0d4a2 Account: (0x8d63e0FE87CA36E06a076584fCA651A684D4c97d) ``` -When broadcasting `script/DeployCore.s.sol ` with the above key, the contracts will always be deployed -at the same address. Neither the initCode of the contracts nor the nonce influence the address generation. +When broadcasting `script/DeployCoreWithCreateX.s.sol` with the above key, the contracts will always be +deployed at the same addresses on a clean chain. Neither init code nor nonce influences address generation. ## Compatibility Checks diff --git a/package.json b/package.json index f394d15..71a427a 100644 --- a/package.json +++ b/package.json @@ -14,8 +14,10 @@ ], "scripts": { "prepare": "./shell/create_artifacts.sh", - "test": "forge test", + "test": "forge test && python3 test/shell/test_deploy_wizard.py", + "test:deploy-wizard": "python3 test/shell/test_deploy_wizard.py", "build": "forge build", + "deploy:wizard": "./shell/deploy_wizard.sh", "format": "forge fmt", "clean": "forge clean" }, diff --git a/script/CreateXDeployer.s.sol b/script/CreateXDeployer.s.sol new file mode 100644 index 0000000..f24dbfe --- /dev/null +++ b/script/CreateXDeployer.s.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: CC0-1.0 +pragma solidity ^0.8.28; + +import {ICreateX, CREATE_X_ADDRESS} from "./ICreateX.sol"; + +abstract contract CreateXDeployer { + string public constant SALT_DA_VERIFIER_ECDSA_NAME = "credible-layer-da-verifier-ecdsa"; + string public constant SALT_DA_VERIFIER_ONCHAIN_NAME = "credible-layer-da-verifier-onchain"; + string public constant SALT_ADMIN_VERIFIER_OWNER_NAME = "credible-layer-admin-verifier-owner"; + string public constant SALT_ADMIN_VERIFIER_WHITELIST_NAME = "credible-layer-admin-verifier-whitelist"; + string public constant SALT_ADMIN_VERIFIER_SUPER_ADMIN_NAME = "credible-layer-admin-verifier-super-admin"; + string public constant SALT_ADMIN_VERIFIER_ALWAYS_APPROVE_NAME = "credible-layer-admin-verifier-always-approve"; + string public constant SALT_STATE_ORACLE_NAME = "credible-layer-state-oracle-implementation"; + string public constant SALT_STATE_ORACLE_PROXY_NAME = "credible-layer-state-oracle-proxy"; + string public constant SALT_STAGING_STATE_ORACLE_NAME = "credible-layer-staging-state-oracle-implementation"; + string public constant SALT_STAGING_STATE_ORACLE_PROXY_NAME = "credible-layer-staging-state-oracle-proxy"; + + ICreateX internal constant CREATE_X = ICreateX(CREATE_X_ADDRESS); + + function _deployCreate3(string memory name, bytes memory initCode) internal returns (address) { + bytes32 salt = _generateCreateXSalt(msg.sender, name); + return CREATE_X.deployCreate3(salt, initCode); + } + + // Set salt with frontrunning protection, i.e. first 20 bytes = deployer; + // 0 byte to switch off cross-chain redeploy protection; 11 bytes salt + // Details: https://github.com/pcaversaccio/createx#permissioned-deploy-protection-and-cross-chain-redeploy-protection + function _generateCreateXSalt(address sender, string memory name) internal pure returns (bytes32) { + return bytes32(abi.encodePacked(sender, hex"00", bytes11(keccak256(bytes(name))))); + } +} diff --git a/script/DeployCore.s.sol b/script/DeployCore.s.sol index 27caf5e..8fe1a2c 100644 --- a/script/DeployCore.s.sol +++ b/script/DeployCore.s.sol @@ -5,6 +5,7 @@ import {StateOracle} from "../src/StateOracle.sol"; import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import {IAdminVerifier} from "../src/interfaces/IAdminVerifier.sol"; import {IDAVerifier} from "../src/interfaces/IDAVerifier.sol"; +import {AdminVerifierAlwaysApprove} from "../src/verification/admin/AdminVerifierAlwaysApprove.sol"; import {AdminVerifierOwner} from "../src/verification/admin/AdminVerifierOwner.sol"; import {DAVerifierECDSA} from "../src/verification/da/DAVerifierECDSA.sol"; import {DAVerifierOnChain} from "../src/verification/da/DAVerifierOnChain.sol"; @@ -29,6 +30,9 @@ contract DeployCore is Script { address daProver; bool deployOwnerVerifier; bool deployWhitelistVerifier; + bool deployAlwaysApproveVerifier; + bool stateOracleWhitelistEnabled; + bool testingDeployment; address whitelistAdmin; function setUp() public virtual { @@ -40,12 +44,18 @@ contract DeployCore is Script { // Verifiers deployOwnerVerifier = vm.envBool("DEPLOY_ADMIN_VERIFIER_OWNER"); deployWhitelistVerifier = vm.envBool("DEPLOY_ADMIN_VERIFIER_WHITELIST"); - whitelistAdmin = vm.envAddress("ADMIN_VERIFIER_WHITELIST_ADMIN_ADDRESS"); + deployAlwaysApproveVerifier = vm.envOr("DEPLOY_ADMIN_VERIFIER_ALWAYS_APPROVE", false); + stateOracleWhitelistEnabled = vm.envOr("STATE_ORACLE_WHITELIST_ENABLED", true); + testingDeployment = vm.envOr("DEPLOYMENT_IS_TESTING", false); + if (deployWhitelistVerifier) { + whitelistAdmin = vm.envAddress("ADMIN_VERIFIER_WHITELIST_ADMIN_ADDRESS"); + } assert(daProver != address(0)); assert(assertionTimelockBlocks > 0); assert(admin != address(0)); assert(deployWhitelistVerifier && whitelistAdmin != address(0) || !deployWhitelistVerifier); + require(testingDeployment || !deployAlwaysApproveVerifier, "Always Approve verifier is test-only"); } modifier broadcast() { @@ -54,6 +64,11 @@ contract DeployCore is Script { vm.stopBroadcast(); } + modifier testingOnly() { + require(testingDeployment, "Always Approve verifier is test-only"); + _; + } + function run() public virtual broadcast { _fundPersistentAccounts(); @@ -102,6 +117,10 @@ contract DeployCore is Script { _deployWhitelistAdminVerifier(); } + function deployAlwaysApproveAdminVerifier() public testingOnly broadcast { + _deployAlwaysApproveAdminVerifier(); + } + function fundPersistentAccounts() public broadcast { _fundPersistentAccounts(); } @@ -122,13 +141,17 @@ contract DeployCore is Script { uint256 count; if (deployOwnerVerifier) count++; if (deployWhitelistVerifier) count++; + if (deployAlwaysApproveVerifier) count++; deployments = new address[](count); uint256 index; if (deployOwnerVerifier) { deployments[index++] = _deployOwnerAdminVerifier(); } if (deployWhitelistVerifier) { - deployments[index] = _deployWhitelistAdminVerifier(); + deployments[index++] = _deployWhitelistAdminVerifier(); + } + if (deployAlwaysApproveVerifier) { + deployments[index] = _deployAlwaysApproveAdminVerifier(); } } @@ -152,8 +175,10 @@ contract DeployCore is Script { for (uint256 i = 0; i < daVerifierAddresses.length; i++) { daVfrs[i] = IDAVerifier(daVerifierAddresses[i]); } - bytes memory initCallData = - abi.encodeWithSelector(StateOracle.initialize.selector, admin, adminVerifiers, daVfrs, maxAssertions); + bytes memory initCallData = abi.encodeCall( + StateOracle.initializeWithWhitelist, + (admin, adminVerifiers, daVfrs, maxAssertions, stateOracleWhitelistEnabled, new address[](0)) + ); address proxyAddress = address(new TransparentUpgradeableProxy(address(stateOracle), admin, initCallData)); console2.log("State Oracle Proxy deployed at", proxyAddress); return proxyAddress; @@ -166,11 +191,18 @@ contract DeployCore is Script { } function _deployWhitelistAdminVerifier() internal virtual returns (address verifier) { + require(whitelistAdmin != address(0), "Invalid whitelist admin"); verifier = address(new AdminVerifierWhitelist(whitelistAdmin)); console2.log("Admin Verifier (Whitelist) deployed at", verifier); return verifier; } + function _deployAlwaysApproveAdminVerifier() internal virtual returns (address verifier) { + verifier = address(new AdminVerifierAlwaysApprove()); + console2.log("Testing Admin Verifier (Always Approve) deployed at", verifier); + return verifier; + } + function _fundPersistentAccounts() internal { _fundIfEmpty(CALLER_ADDRESS, "CALLER_ADDRESS"); _fundIfEmpty(ASSERTION_CONTRACT_ADDRESS, "ASSERTION_CONTRACT_ADDRESS"); diff --git a/script/DeployCoreWithCreateX.s.sol b/script/DeployCoreWithCreateX.s.sol index 04fcdce..e2f7b57 100644 --- a/script/DeployCoreWithCreateX.s.sol +++ b/script/DeployCoreWithCreateX.s.sol @@ -5,26 +5,18 @@ import {StateOracle} from "../src/StateOracle.sol"; import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import {IAdminVerifier} from "../src/interfaces/IAdminVerifier.sol"; import {IDAVerifier} from "../src/interfaces/IDAVerifier.sol"; +import {AdminVerifierAlwaysApprove} from "../src/verification/admin/AdminVerifierAlwaysApprove.sol"; import {AdminVerifierOwner} from "../src/verification/admin/AdminVerifierOwner.sol"; import {AdminVerifierWhitelist} from "../src/verification/admin/AdminVerifierWhitelist.sol"; import {DAVerifierECDSA} from "../src/verification/da/DAVerifierECDSA.sol"; import {DAVerifierOnChain} from "../src/verification/da/DAVerifierOnChain.sol"; -import {ICreateX, CREATE_X_ADDRESS} from "./ICreateX.sol"; +import {CreateXDeployer} from "./CreateXDeployer.s.sol"; import {DeployCore} from "./DeployCore.s.sol"; import {console2} from "forge-std/console2.sol"; -contract DeployCoreWithCreateX is DeployCore { - string public constant SALT_DA_VERIFIER_ECDSA_NAME = "credible-layer-da-verifier-ecdsa"; - string public constant SALT_DA_VERIFIER_ONCHAIN_NAME = "credible-layer-da-verifier-onchain"; - string public constant SALT_ADMIN_VERIFIER_OWNER_NAME = "credible-layer-admin-verifier-owner"; - string public constant SALT_ADMIN_VERIFIER_WHITELIST_NAME = "credible-layer-admin-verifier-whitelist"; - string public constant SALT_STATE_ORACLE_NAME = "credible-layer-state-oracle-implementation"; - string public constant SALT_STATE_ORACLE_PROXY_NAME = "credible-layer-state-oracle-proxy"; - - ICreateX internal constant CREATE_X = ICreateX(CREATE_X_ADDRESS); - +contract DeployCoreWithCreateX is DeployCore, CreateXDeployer { function _deployDAVerifierECDSA() internal override returns (address) { - address daVerifier = deployCreate3( + address daVerifier = _deployCreate3( SALT_DA_VERIFIER_ECDSA_NAME, abi.encodePacked(type(DAVerifierECDSA).creationCode, abi.encode(daProver)) ); console2.log("DA Verifier (ECDSA) deployed at", daVerifier); @@ -32,19 +24,19 @@ contract DeployCoreWithCreateX is DeployCore { } function _deployDAVerifierOnChain() internal override returns (address) { - address daVerifierOnChain = deployCreate3(SALT_DA_VERIFIER_ONCHAIN_NAME, type(DAVerifierOnChain).creationCode); + address daVerifierOnChain = _deployCreate3(SALT_DA_VERIFIER_ONCHAIN_NAME, type(DAVerifierOnChain).creationCode); console2.log("DA Verifier (OnChain) deployed at", daVerifierOnChain); return daVerifierOnChain; } function _deployOwnerAdminVerifier() internal override returns (address verifier) { - verifier = deployCreate3(SALT_ADMIN_VERIFIER_OWNER_NAME, type(AdminVerifierOwner).creationCode); + verifier = _deployCreate3(SALT_ADMIN_VERIFIER_OWNER_NAME, type(AdminVerifierOwner).creationCode); console2.log("Admin Verifier (Owner) deployed at", verifier); return verifier; } function _deployWhitelistAdminVerifier() internal override returns (address verifier) { - verifier = deployCreate3( + verifier = _deployCreate3( SALT_ADMIN_VERIFIER_WHITELIST_NAME, abi.encodePacked(type(AdminVerifierWhitelist).creationCode, abi.encode(whitelistAdmin)) ); @@ -52,17 +44,31 @@ contract DeployCoreWithCreateX is DeployCore { return verifier; } + function _deployAlwaysApproveAdminVerifier() internal override returns (address verifier) { + verifier = + _deployCreate3(SALT_ADMIN_VERIFIER_ALWAYS_APPROVE_NAME, type(AdminVerifierAlwaysApprove).creationCode); + console2.log("Testing Admin Verifier (Always Approve) deployed at", verifier); + return verifier; + } + function _deployStateOracle(uint256 assertionTimelockBlocks, string memory contractName) internal override returns (address) { - address stateOracle = deployCreate3( - SALT_STATE_ORACLE_NAME, - abi.encodePacked(type(StateOracle).creationCode, abi.encode(assertionTimelockBlocks)) + address stateOracle = _deployStateOracleWithSalt(assertionTimelockBlocks, contractName, SALT_STATE_ORACLE_NAME); + return stateOracle; + } + + function _deployStateOracleWithSalt( + uint256 assertionTimelockBlocks, + string memory contractName, + string memory saltName + ) internal returns (address stateOracle) { + stateOracle = _deployCreate3( + saltName, abi.encodePacked(type(StateOracle).creationCode, abi.encode(assertionTimelockBlocks)) ); console2.log(string.concat(contractName, " Implementation deployed at"), stateOracle); - return stateOracle; } function _deployStateOracleProxy( @@ -71,6 +77,46 @@ contract DeployCoreWithCreateX is DeployCore { address[] memory daVerifierAddresses, uint16 maxAssertions ) internal override returns (address) { + return _deployStateOracleProxyWithSalt( + stateOracle, + adminVerifierDeployments, + daVerifierAddresses, + maxAssertions, + SALT_STATE_ORACLE_PROXY_NAME, + "State Oracle" + ); + } + + function _deployStateOracleProxyWithSalt( + address stateOracle, + address[] memory adminVerifierDeployments, + address[] memory daVerifierAddresses, + uint16 maxAssertions, + string memory saltName, + string memory contractName + ) internal returns (address proxyAddress) { + return _deployStateOracleProxyWithConfig( + stateOracle, + adminVerifierDeployments, + daVerifierAddresses, + maxAssertions, + stateOracleWhitelistEnabled, + new address[](0), + saltName, + contractName + ); + } + + function _deployStateOracleProxyWithConfig( + address stateOracle, + address[] memory adminVerifierDeployments, + address[] memory daVerifierAddresses, + uint16 maxAssertions, + bool whitelistEnabled, + address[] memory initialWhitelist, + string memory saltName, + string memory contractName + ) internal returns (address proxyAddress) { IAdminVerifier[] memory adminVerifiers = new IAdminVerifier[](adminVerifierDeployments.length); for (uint256 i = 0; i < adminVerifierDeployments.length; i++) { adminVerifiers[i] = IAdminVerifier(adminVerifierDeployments[i]); @@ -79,27 +125,16 @@ contract DeployCoreWithCreateX is DeployCore { for (uint256 i = 0; i < daVerifierAddresses.length; i++) { daVfrs[i] = IDAVerifier(daVerifierAddresses[i]); } - bytes memory initCallData = - abi.encodeWithSelector(StateOracle.initialize.selector, admin, adminVerifiers, daVfrs, maxAssertions); - address proxyAddress = deployCreate3( - SALT_STATE_ORACLE_PROXY_NAME, + bytes memory initCallData = abi.encodeCall( + StateOracle.initializeWithWhitelist, + (admin, adminVerifiers, daVfrs, maxAssertions, whitelistEnabled, initialWhitelist) + ); + proxyAddress = _deployCreate3( + saltName, abi.encodePacked( type(TransparentUpgradeableProxy).creationCode, abi.encode(address(stateOracle), admin, initCallData) ) ); - console2.log("State Oracle Proxy deployed at", proxyAddress); - return proxyAddress; - } - - function deployCreate3(string memory name, bytes memory initCode) private returns (address) { - bytes32 salt = generateCreateXSalt(msg.sender, name); - return CREATE_X.deployCreate3(salt, initCode); - } - - // Set salt with frontrunning protection, i.e. first 20 bytes = deployer; - // 0 byte to switch off cross-chain redeploy protection; 11 bytes salt - // Details: https://github.com/pcaversaccio/createx#permissioned-deploy-protection-and-cross-chain-redeploy-protection - function generateCreateXSalt(address sender, string memory name) internal pure returns (bytes32) { - return bytes32(abi.encodePacked(sender, hex"00", bytes11(keccak256(bytes(name))))); + console2.log(string.concat(contractName, " Proxy deployed at"), proxyAddress); } } diff --git a/script/DeployCoreWithStaging.s.sol b/script/DeployCoreWithStaging.s.sol index 5bab00b..065a063 100644 --- a/script/DeployCoreWithStaging.s.sol +++ b/script/DeployCoreWithStaging.s.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: CC0-1.0 pragma solidity ^0.8.28; -import {DeployCore} from "./DeployCore.s.sol"; +import {DeployCoreWithCreateX} from "./DeployCoreWithCreateX.s.sol"; -contract DeployCoreWithStaging is DeployCore { +contract DeployCoreWithStaging is DeployCoreWithCreateX { uint256 stagingAssertionTimelockBlocks; uint16 stagingMaxAssertionsPerAA; @@ -44,8 +44,16 @@ contract DeployCoreWithStaging is DeployCore { _deployStateOracleProxy(stateOracle, deployedAdminVerifiers, daVerifiers, maxAssertionsPerAA); // Staging oracle - address stagingOracle = _deployStateOracle(stagingAssertionTimelockBlocks, "Staging State Oracle"); - deployedStagingOracle = - _deployStateOracleProxy(stagingOracle, deployedAdminVerifiers, daVerifiers, stagingMaxAssertionsPerAA); + address stagingOracle = _deployStateOracleWithSalt( + stagingAssertionTimelockBlocks, "Staging State Oracle", SALT_STAGING_STATE_ORACLE_NAME + ); + deployedStagingOracle = _deployStateOracleProxyWithSalt( + stagingOracle, + deployedAdminVerifiers, + daVerifiers, + stagingMaxAssertionsPerAA, + SALT_STAGING_STATE_ORACLE_PROXY_NAME, + "Staging State Oracle" + ); } } diff --git a/script/DeployTestingAdminVerifiers.s.sol b/script/DeployTestingAdminVerifiers.s.sol new file mode 100644 index 0000000..d0296ea --- /dev/null +++ b/script/DeployTestingAdminVerifiers.s.sol @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: CC0-1.0 +pragma solidity ^0.8.28; + +import {StateOracle} from "../src/StateOracle.sol"; +import {IAdminVerifier} from "../src/interfaces/IAdminVerifier.sol"; +import {AdminVerifierAlwaysApprove} from "../src/verification/admin/AdminVerifierAlwaysApprove.sol"; +import {AdminVerifierSuperAdmin} from "../src/verification/admin/AdminVerifierSuperAdmin.sol"; +import {console2} from "forge-std/console2.sol"; +import {Script} from "forge-std/Script.sol"; +import {CreateXDeployer} from "./CreateXDeployer.s.sol"; + +contract DeployTestingAdminVerifiers is Script, CreateXDeployer { + address stateOracle; + address superAdmin; + bool deploySuperAdminVerifierEnabled; + bool deployAlwaysApproveVerifierEnabled; + bool addToStateOracle; + bool testingDeployment; + + function setUp() public virtual { + stateOracle = vm.envOr("TEST_STATE_ORACLE_ADDRESS", address(0)); + superAdmin = vm.envOr("TEST_ADMIN_VERIFIER_SUPER_ADMIN_ADDRESS", address(0)); + deploySuperAdminVerifierEnabled = vm.envOr("DEPLOY_TEST_ADMIN_VERIFIER_SUPER_ADMIN", false); + deployAlwaysApproveVerifierEnabled = vm.envOr("DEPLOY_TEST_ADMIN_VERIFIER_ALWAYS_APPROVE", false); + addToStateOracle = vm.envOr("ADD_TEST_ADMIN_VERIFIERS_TO_STATE_ORACLE", false); + testingDeployment = vm.envOr("DEPLOYMENT_IS_TESTING", false); + } + + modifier broadcast() { + vm.startBroadcast(); + _; + vm.stopBroadcast(); + } + + modifier testingOnly() { + require(testingDeployment, "Testing admin verifiers are test-only"); + _; + } + + function run() public testingOnly broadcast { + _run(); + } + + function deploySuperAdminVerifier(address _superAdmin) public testingOnly broadcast returns (address) { + return _deploySuperAdminVerifier(_superAdmin); + } + + function deployAlwaysApproveAdminVerifier() public testingOnly broadcast returns (address) { + return _deployAlwaysApproveAdminVerifier(); + } + + function deployAndAddSuperAdminVerifier(address _stateOracle, address _superAdmin) + public + testingOnly + broadcast + returns (address verifier) + { + verifier = _deploySuperAdminVerifier(_superAdmin); + _addAdminVerifier(_stateOracle, verifier); + } + + function deployAndAddAlwaysApproveAdminVerifier(address _stateOracle) + public + testingOnly + broadcast + returns (address verifier) + { + verifier = _deployAlwaysApproveAdminVerifier(); + _addAdminVerifier(_stateOracle, verifier); + } + + function addAdminVerifier(address _stateOracle, address verifier) public testingOnly broadcast { + _addAdminVerifier(_stateOracle, verifier); + } + + function _run() internal returns (address[] memory deployments) { + deployments = _deployEnabledAdminVerifiers(); + if (addToStateOracle) { + _addAdminVerifiers(stateOracle, deployments); + } + } + + function _deployEnabledAdminVerifiers() internal returns (address[] memory deployments) { + uint256 count; + if (deploySuperAdminVerifierEnabled) count++; + if (deployAlwaysApproveVerifierEnabled) count++; + require(count > 0, "No test verifiers enabled"); + + deployments = new address[](count); + uint256 index; + if (deploySuperAdminVerifierEnabled) { + deployments[index++] = _deploySuperAdminVerifier(superAdmin); + } + if (deployAlwaysApproveVerifierEnabled) { + deployments[index++] = _deployAlwaysApproveAdminVerifier(); + } + } + + function _deploySuperAdminVerifier(address _superAdmin) internal virtual returns (address verifier) { + require(_superAdmin != address(0), "Invalid super admin"); + verifier = _deployCreate3( + SALT_ADMIN_VERIFIER_SUPER_ADMIN_NAME, + abi.encodePacked(type(AdminVerifierSuperAdmin).creationCode, abi.encode(_superAdmin)) + ); + console2.log("Testing Admin Verifier (Super Admin) deployed at", verifier); + } + + function _deployAlwaysApproveAdminVerifier() internal virtual returns (address verifier) { + verifier = + _deployCreate3(SALT_ADMIN_VERIFIER_ALWAYS_APPROVE_NAME, type(AdminVerifierAlwaysApprove).creationCode); + console2.log("Testing Admin Verifier (Always Approve) deployed at", verifier); + } + + function _addAdminVerifiers(address _stateOracle, address[] memory verifiers) internal { + require(_stateOracle != address(0), "Invalid state oracle"); + for (uint256 i = 0; i < verifiers.length; i++) { + _addAdminVerifier(_stateOracle, verifiers[i]); + } + } + + function _addAdminVerifier(address _stateOracle, address verifier) internal { + require(_stateOracle != address(0), "Invalid state oracle"); + require(verifier != address(0), "Invalid admin verifier"); + StateOracle(_stateOracle).addAdminVerifier(IAdminVerifier(verifier)); + console2.log("Testing admin verifier added to StateOracle", verifier); + } +} diff --git a/script/DeployWizard.s.sol b/script/DeployWizard.s.sol new file mode 100644 index 0000000..de4b58b --- /dev/null +++ b/script/DeployWizard.s.sol @@ -0,0 +1,257 @@ +// SPDX-License-Identifier: CC0-1.0 +pragma solidity ^0.8.28; + +import {DeployCoreWithCreateX} from "./DeployCoreWithCreateX.s.sol"; +import {AdminVerifierSuperAdmin} from "../src/verification/admin/AdminVerifierSuperAdmin.sol"; +import {console2} from "forge-std/console2.sol"; + +/// @notice Deployment backend for shell/deploy_wizard.sh. +/// @dev The wizard adds per-oracle verifier selection and initial whitelist +/// configuration to the deterministic CreateX deployment primitives. +contract DeployWizard is DeployCoreWithCreateX { + struct VerifierDeployments { + address daECDSA; + address daOnChain; + address adminOwner; + address adminWhitelist; + address adminSuperAdmin; + address adminAlwaysApprove; + } + + bool internal deployStaging; + + bool internal daECDSAProduction; + bool internal daECDSAStaging; + bool internal daOnChainProduction; + bool internal daOnChainStaging; + + bool internal adminOwnerProduction; + bool internal adminOwnerStaging; + bool internal adminWhitelistProduction; + bool internal adminWhitelistStaging; + bool internal adminSuperAdminProduction; + bool internal adminSuperAdminStaging; + bool internal adminAlwaysApproveProduction; + bool internal adminAlwaysApproveStaging; + + uint256 internal stagingAssertionTimelockBlocks; + uint16 internal stagingMaxAssertionsPerAA; + address internal testSuperAdmin; + address[] internal initialWhitelist; + + function setUp() public override { + testingDeployment = vm.envOr("DEPLOYMENT_IS_TESTING", false); + deployStaging = vm.envOr("DEPLOY_STAGING_STATE_ORACLE", false); + stateOracleWhitelistEnabled = vm.envOr("STATE_ORACLE_WHITELIST_ENABLED", true); + + admin = vm.envAddress("STATE_ORACLE_ADMIN_ADDRESS"); + require(admin != address(0), "Invalid State Oracle admin"); + + uint256 rawMaxAssertions = vm.envUint("STATE_ORACLE_MAX_ASSERTIONS_PER_AA"); + require(rawMaxAssertions > 0 && rawMaxAssertions <= type(uint16).max, "Invalid max assertions"); + // The bounds check above makes this narrowing conversion safe. + // forge-lint: disable-next-line(unsafe-typecast) + maxAssertionsPerAA = uint16(rawMaxAssertions); + + uint256 rawTimelock = vm.envUint("STATE_ORACLE_ASSERTION_TIMELOCK_BLOCKS"); + require(rawTimelock > 0, "Invalid assertion timelock"); + assertionTimelockBlocks = rawTimelock; + + if (deployStaging) { + uint256 rawStagingMaxAssertions = vm.envUint("STAGING_STATE_ORACLE_MAX_ASSERTIONS_PER_AA"); + require( + rawStagingMaxAssertions > 0 && rawStagingMaxAssertions <= type(uint16).max, + "Invalid staging max assertions" + ); + // The bounds check above makes this narrowing conversion safe. + // forge-lint: disable-next-line(unsafe-typecast) + stagingMaxAssertionsPerAA = uint16(rawStagingMaxAssertions); + + uint256 rawStagingTimelock = vm.envUint("STAGING_STATE_ORACLE_ASSERTION_TIMELOCK_BLOCKS"); + require(rawStagingTimelock > 0, "Invalid staging assertion timelock"); + stagingAssertionTimelockBlocks = rawStagingTimelock; + } + + _loadVerifierConfiguration(); + + initialWhitelist = vm.envOr("STATE_ORACLE_INITIAL_WHITELIST", ",", new address[](0)); + } + + function run() public override broadcast { + _fundPersistentAccounts(); + + VerifierDeployments memory deployed = _deploySelectedVerifiers(); + + _deployConfiguredOracle( + true, + "Production", + _selectedAdminVerifiers(true, deployed), + _selectedDAVerifiers(true, deployed), + assertionTimelockBlocks, + maxAssertionsPerAA + ); + + if (deployStaging) { + _deployConfiguredOracle( + false, + "Staging", + _selectedAdminVerifiers(false, deployed), + _selectedDAVerifiers(false, deployed), + stagingAssertionTimelockBlocks, + stagingMaxAssertionsPerAA + ); + } + } + + function _loadVerifierConfiguration() internal { + daECDSAProduction = vm.envOr("DA_VERIFIER_ECDSA_PRODUCTION", false); + daECDSAStaging = vm.envOr("DA_VERIFIER_ECDSA_STAGING", false); + daOnChainProduction = vm.envOr("DA_VERIFIER_ONCHAIN_PRODUCTION", false); + daOnChainStaging = vm.envOr("DA_VERIFIER_ONCHAIN_STAGING", false); + + require(_selectedDAVerifierCount(true) > 0, "Production requires a DA verifier"); + require(!deployStaging || _selectedDAVerifierCount(false) > 0, "Staging requires a DA verifier"); + require(deployStaging || !(daECDSAStaging || daOnChainStaging), "Staging DA verifier selected without staging"); + + if (daECDSAProduction || daECDSAStaging) { + daProver = vm.envAddress("DA_PROVER_ADDRESS"); + require(daProver != address(0), "Invalid DA prover"); + } + + adminOwnerProduction = vm.envOr("ADMIN_VERIFIER_OWNER_PRODUCTION", false); + adminOwnerStaging = vm.envOr("ADMIN_VERIFIER_OWNER_STAGING", false); + adminWhitelistProduction = vm.envOr("ADMIN_VERIFIER_WHITELIST_PRODUCTION", false); + adminWhitelistStaging = vm.envOr("ADMIN_VERIFIER_WHITELIST_STAGING", false); + adminSuperAdminProduction = vm.envOr("ADMIN_VERIFIER_SUPER_ADMIN_PRODUCTION", false); + adminSuperAdminStaging = vm.envOr("ADMIN_VERIFIER_SUPER_ADMIN_STAGING", false); + adminAlwaysApproveProduction = vm.envOr("ADMIN_VERIFIER_ALWAYS_APPROVE_PRODUCTION", false); + adminAlwaysApproveStaging = vm.envOr("ADMIN_VERIFIER_ALWAYS_APPROVE_STAGING", false); + + require(_selectedAdminVerifierCount(true) > 0, "Production requires an admin verifier"); + require(!deployStaging || _selectedAdminVerifierCount(false) > 0, "Staging requires an admin verifier"); + require( + deployStaging + || !(adminOwnerStaging || adminWhitelistStaging || adminSuperAdminStaging || adminAlwaysApproveStaging), + "Staging admin verifier selected without staging" + ); + + if (adminWhitelistProduction || adminWhitelistStaging) { + whitelistAdmin = vm.envAddress("ADMIN_VERIFIER_WHITELIST_ADMIN_ADDRESS"); + require(whitelistAdmin != address(0), "Invalid admin verifier whitelist owner"); + } + + if (adminSuperAdminProduction || adminSuperAdminStaging) { + require(testingDeployment, "Super Admin verifier is test-only"); + testSuperAdmin = vm.envAddress("TEST_ADMIN_VERIFIER_SUPER_ADMIN_ADDRESS"); + require(testSuperAdmin != address(0), "Invalid test super admin"); + } + require( + testingDeployment || !(adminAlwaysApproveProduction || adminAlwaysApproveStaging), + "Always Approve verifier is test-only" + ); + } + + function _deploySelectedVerifiers() internal returns (VerifierDeployments memory deployed) { + if (daECDSAProduction || daECDSAStaging) { + deployed.daECDSA = _deployDAVerifierECDSA(); + _logDeployment("DA Verifier (ECDSA)", deployed.daECDSA); + } + if (daOnChainProduction || daOnChainStaging) { + deployed.daOnChain = _deployDAVerifierOnChain(); + _logDeployment("DA Verifier (On-chain)", deployed.daOnChain); + } + if (adminOwnerProduction || adminOwnerStaging) { + deployed.adminOwner = _deployOwnerAdminVerifier(); + _logDeployment("Admin Verifier (Owner)", deployed.adminOwner); + } + if (adminWhitelistProduction || adminWhitelistStaging) { + deployed.adminWhitelist = _deployWhitelistAdminVerifier(); + _logDeployment("Admin Verifier (Whitelist)", deployed.adminWhitelist); + } + if (adminSuperAdminProduction || adminSuperAdminStaging) { + deployed.adminSuperAdmin = _deployCreate3( + SALT_ADMIN_VERIFIER_SUPER_ADMIN_NAME, + abi.encodePacked(type(AdminVerifierSuperAdmin).creationCode, abi.encode(testSuperAdmin)) + ); + console2.log("Testing Admin Verifier (Super Admin) deployed at", deployed.adminSuperAdmin); + _logDeployment("Testing Admin Verifier (Super Admin)", deployed.adminSuperAdmin); + } + if (adminAlwaysApproveProduction || adminAlwaysApproveStaging) { + deployed.adminAlwaysApprove = _deployAlwaysApproveAdminVerifier(); + _logDeployment("Testing Admin Verifier (Always Approve)", deployed.adminAlwaysApprove); + } + } + + function _selectedDAVerifiers(bool production, VerifierDeployments memory deployed) + internal + view + returns (address[] memory verifiers) + { + verifiers = new address[](_selectedDAVerifierCount(production)); + uint256 index; + if (production ? daECDSAProduction : daECDSAStaging) verifiers[index++] = deployed.daECDSA; + if (production ? daOnChainProduction : daOnChainStaging) verifiers[index] = deployed.daOnChain; + } + + function _selectedDAVerifierCount(bool production) internal view returns (uint256 count) { + if (production ? daECDSAProduction : daECDSAStaging) count++; + if (production ? daOnChainProduction : daOnChainStaging) count++; + } + + function _selectedAdminVerifiers(bool production, VerifierDeployments memory deployed) + internal + view + returns (address[] memory verifiers) + { + verifiers = new address[](_selectedAdminVerifierCount(production)); + uint256 index; + if (production ? adminOwnerProduction : adminOwnerStaging) verifiers[index++] = deployed.adminOwner; + if (production ? adminWhitelistProduction : adminWhitelistStaging) { + verifiers[index++] = deployed.adminWhitelist; + } + if (production ? adminSuperAdminProduction : adminSuperAdminStaging) { + verifiers[index++] = deployed.adminSuperAdmin; + } + if (production ? adminAlwaysApproveProduction : adminAlwaysApproveStaging) { + verifiers[index] = deployed.adminAlwaysApprove; + } + } + + function _selectedAdminVerifierCount(bool production) internal view returns (uint256 count) { + if (production ? adminOwnerProduction : adminOwnerStaging) count++; + if (production ? adminWhitelistProduction : adminWhitelistStaging) count++; + if (production ? adminSuperAdminProduction : adminSuperAdminStaging) count++; + if (production ? adminAlwaysApproveProduction : adminAlwaysApproveStaging) count++; + } + + function _deployConfiguredOracle( + bool production, + string memory environment, + address[] memory adminVerifierDeployments, + address[] memory daVerifierDeployments, + uint256 timelockBlocks, + uint16 maxAssertions + ) internal returns (address proxyAddress) { + string memory contractName = string.concat(environment, " State Oracle"); + string memory implementationSalt = production ? SALT_STATE_ORACLE_NAME : SALT_STAGING_STATE_ORACLE_NAME; + string memory proxySalt = production ? SALT_STATE_ORACLE_PROXY_NAME : SALT_STAGING_STATE_ORACLE_PROXY_NAME; + address implementation = _deployStateOracleWithSalt(timelockBlocks, contractName, implementationSalt); + _logDeployment(string.concat(environment, " State Oracle Implementation"), implementation); + + proxyAddress = _deployStateOracleProxyWithConfig( + implementation, + adminVerifierDeployments, + daVerifierDeployments, + maxAssertions, + stateOracleWhitelistEnabled, + initialWhitelist, + proxySalt, + contractName + ); + _logDeployment(string.concat(environment, " State Oracle Proxy"), proxyAddress); + } + + function _logDeployment(string memory name, address deployedAddress) internal pure { + console2.log(string.concat("WIZARD_DEPLOYMENT|", name, "|"), deployedAddress); + } +} diff --git a/shell/deploy_wizard.sh b/shell/deploy_wizard.sh new file mode 100755 index 0000000..4ad6734 --- /dev/null +++ b/shell/deploy_wizard.sh @@ -0,0 +1,851 @@ +#!/usr/bin/env bash + +set -Eeuo pipefail + +ROOT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) +PASSWORD_FILE="" +FORGE_OUTPUT_FILE="" +CURSOR_HIDDEN=false +COLOR_RESET="" +COLOR_BOLD="" +COLOR_DIM="" +COLOR_RED="" +COLOR_GREEN="" +COLOR_YELLOW="" +COLOR_BLUE="" +COLOR_MAGENTA="" +COLOR_CYAN="" + +cleanup() { + if [[ "$CURSOR_HIDDEN" == "true" ]]; then + { printf '\033[?25h' >/dev/tty; } 2>/dev/null || true + fi + if [[ -n "$PASSWORD_FILE" && -f "$PASSWORD_FILE" ]]; then + rm -f "$PASSWORD_FILE" + fi + if [[ -n "$FORGE_OUTPUT_FILE" && -f "$FORGE_OUTPUT_FILE" ]]; then + rm -f "$FORGE_OUTPUT_FILE" + fi +} +trap cleanup EXIT +trap 'exit 130' INT TERM + +usage() { + cat <<'EOF' +Usage: ./shell/deploy_wizard.sh + +Interactively configures and deploys the Credible Layer contracts. Use the +arrow keys to move, Space to toggle multi-select entries, and Enter to accept. + +The wizard reads existing values from these variables as prompt defaults: + RPC_URL or ETH_RPC_URL + ETHERSCAN_API_KEY + STATE_ORACLE_MAX_ASSERTIONS_PER_AA + STATE_ORACLE_ASSERTION_TIMELOCK_BLOCKS + STATE_ORACLE_ADMIN_ADDRESS + STAGING_STATE_ORACLE_MAX_ASSERTIONS_PER_AA + STAGING_STATE_ORACLE_ASSERTION_TIMELOCK_BLOCKS + DA_PROVER_ADDRESS + ADMIN_VERIFIER_WHITELIST_ADMIN_ADDRESS + TEST_ADMIN_VERIFIER_SUPER_ADMIN_ADDRESS +EOF +} + +die() { + printf '%sError:%s %s\n' "${COLOR_BOLD}${COLOR_RED}" "$COLOR_RESET" "$*" >&2 + exit 1 +} + +init_colors() { + if [[ -n ${NO_COLOR+x} || ${TERM:-} == "dumb" ]]; then + return + fi + + COLOR_RESET=$'\033[0m' + COLOR_BOLD=$'\033[1m' + COLOR_DIM=$'\033[2m' + COLOR_RED=$'\033[31m' + COLOR_GREEN=$'\033[32m' + COLOR_YELLOW=$'\033[33m' + COLOR_BLUE=$'\033[34m' + COLOR_MAGENTA=$'\033[35m' + COLOR_CYAN=$'\033[36m' +} + +tty_print() { + printf '%s' "$*" >/dev/tty +} + +tty_println() { + printf '%s\n' "$*" >/dev/tty +} + +tty_heading() { + tty_println "${COLOR_BOLD}${COLOR_CYAN}$*${COLOR_RESET}" +} + +tty_section() { + tty_println "${COLOR_BOLD}${COLOR_MAGENTA}$*${COLOR_RESET}" +} + +tty_hint() { + tty_println "${COLOR_DIM}$*${COLOR_RESET}" +} + +tty_warn() { + tty_println "${COLOR_BOLD}${COLOR_YELLOW}Warning:${COLOR_RESET} $*" +} + +tty_success() { + tty_println "${COLOR_BOLD}${COLOR_GREEN}$*${COLOR_RESET}" +} + +tty_field() { + local label=$1 + shift + printf ' %s%-21s%s %s%s%s\n' \ + "$COLOR_CYAN" "${label}:" "$COLOR_RESET" "$COLOR_GREEN" "$*" "$COLOR_RESET" >/dev/tty +} + +require_command() { + command -v "$1" >/dev/null 2>&1 || die "Required command not found: $1" +} + +read_key() { + local key suffix + IFS= read -rsn1 key /dev/tty + CURSOR_HIDDEN=true + + while true; do + if [[ $rendered -eq 1 ]]; then + printf '\033[%dA' "$count" >/dev/tty + fi + for ((i = 0; i < count; i++)); do + prefix=" " + if [[ $i -eq $selected ]]; then + prefix="${COLOR_BOLD}${COLOR_BLUE}› " + printf '\r\033[2K%s%s%s\n' "$prefix" "${items[$i]}" "$COLOR_RESET" >/dev/tty + else + printf '\r\033[2K%s%s\n' "$prefix" "${items[$i]}" >/dev/tty + fi + done + rendered=1 + + read_key + case "$KEY_RESULT" in + $'\033[A') + selected=$(((selected - 1 + count) % count)) + ;; + $'\033[B') + selected=$(((selected + 1) % count)) + ;; + "") + break + ;; + esac + done + + printf '\033[?25h' >/dev/tty + CURSOR_HIDDEN=false + MENU_INDEX=$selected + MENU_VALUE=${items[$selected]} +} + +menu_select_many() { + local question=$1 + shift + local items=("$@") + local cursor=0 + local rendered=0 + local count=${#items[@]} + local selected_count i marker prefix + + MULTI_SELECTED=() + for ((i = 0; i < count; i++)); do + MULTI_SELECTED[$i]=0 + done + + tty_println "" + tty_heading "$question" + tty_hint " ↑/↓ move · Space toggle · Enter continue" + printf '\033[?25l' >/dev/tty + CURSOR_HIDDEN=true + + while true; do + if [[ $rendered -eq 1 ]]; then + printf '\033[%dA' "$count" >/dev/tty + fi + for ((i = 0; i < count; i++)); do + marker="[ ]" + prefix=" " + if [[ ${MULTI_SELECTED[$i]} -eq 1 ]]; then + marker="${COLOR_BOLD}${COLOR_GREEN}[x]${COLOR_RESET}" + fi + if [[ $i -eq $cursor ]]; then + prefix="${COLOR_BOLD}${COLOR_BLUE}› ${COLOR_RESET}" + fi + printf '\r\033[2K%s%s %s\n' "$prefix" "$marker" "${items[$i]}" >/dev/tty + done + rendered=1 + + read_key + case "$KEY_RESULT" in + $'\033[A') + cursor=$(((cursor - 1 + count) % count)) + ;; + $'\033[B') + cursor=$(((cursor + 1) % count)) + ;; + " ") + if [[ ${MULTI_SELECTED[$cursor]} -eq 1 ]]; then + MULTI_SELECTED[$cursor]=0 + else + MULTI_SELECTED[$cursor]=1 + fi + ;; + "") + selected_count=0 + for ((i = 0; i < count; i++)); do + selected_count=$((selected_count + MULTI_SELECTED[i])) + done + if [[ $selected_count -gt 0 ]]; then + break + fi + printf '\a' >/dev/tty + ;; + esac + done + + printf '\033[?25h' >/dev/tty + CURSOR_HIDDEN=false +} + +prompt_value() { + local label=$1 + local default_value=${2:-} + local value + + while true; do + if [[ -n "$default_value" ]]; then + printf '%s%s%s %s[%s]%s: ' \ + "$COLOR_BOLD" "$label" "$COLOR_RESET" "$COLOR_DIM" "$default_value" "$COLOR_RESET" >/dev/tty + else + printf '%s%s%s: ' "$COLOR_BOLD" "$label" "$COLOR_RESET" >/dev/tty + fi + IFS= read -r value /dev/tty + IFS= read -rs value /dev/null); then + lower=$(printf '%s' "$checksum" | tr '[:upper:]' '[:lower:]') + if [[ "$lower" != "0x0000000000000000000000000000000000000000" ]]; then + PROMPT_RESULT=$checksum + return + fi + fi + tty_warn "Enter a non-zero Ethereum address." + done +} + +decimal_le() { + local value=$1 + local maximum=$2 + + value=$(printf '%s' "$value" | sed 's/^0*//') + [[ -n "$value" ]] || value=0 + if [[ ${#value} -lt ${#maximum} ]]; then + return 0 + fi + if [[ ${#value} -gt ${#maximum} ]]; then + return 1 + fi + [[ "$value" == "$maximum" || "$value" < "$maximum" ]] +} + +prompt_uint() { + local label=$1 + local default_value=$2 + local maximum=$3 + + while true; do + prompt_value "$label" "$default_value" + if [[ "$PROMPT_RESULT" =~ ^[0-9]+$ ]] && [[ "$PROMPT_RESULT" != "0" ]] \ + && decimal_le "$PROMPT_RESULT" "$maximum"; then + PROMPT_RESULT=$(printf '%s' "$PROMPT_RESULT" | sed 's/^0*//') + [[ -n "$PROMPT_RESULT" ]] || PROMPT_RESULT=0 + return + fi + tty_warn "Enter an integer from 1 through $maximum." + done +} + +bool_for_selection() { + if [[ $1 -eq 1 ]]; then + printf 'true' + else + printf 'false' + fi +} + +join_by_comma() { + local joined="" + local value + for value in "$@"; do + if [[ -n "$joined" ]]; then + joined="${joined},${value}" + else + joined=$value + fi + done + printf '%s' "$joined" +} + +print_redacted_command() { + local item redact_next=false + tty_println "" + tty_section "Wallet password validation command" + printf ' cast wallet address --account %q --password-file \n' "$wallet_account" >/dev/tty + tty_println "" + tty_section "Forge deployment command" + tty_println " env \\" + for item in "${env_unset_args[@]}"; do + printf ' %q \\\n' "$item" >/dev/tty + done + for item in "${env_args[@]}"; do + case "$item" in + ETH_RPC_URL=*) item='ETH_RPC_URL=' ;; + ETHERSCAN_API_KEY=*) item='ETHERSCAN_API_KEY=' ;; + esac + printf ' %q \\\n' "$item" >/dev/tty + done + tty_print " forge" + for item in "${forge_args[@]}"; do + if [[ "$redact_next" == "true" ]]; then + item='' + redact_next=false + elif [[ "$item" == "--rpc-url" ]]; then + redact_next=true + elif [[ "$item" == "$PASSWORD_FILE" ]]; then + item='' + fi + printf ' %q' "$item" >/dev/tty + done + tty_println "" +} + +lookup_transaction() { + local broadcast_file=$1 + local address=$2 + jq -r --arg address "$address" ' + [.transactions[] + | select( + ((.contractAddress // "") | ascii_downcase) == ($address | ascii_downcase) + or any( + .additionalContracts[]?; + ((.address // "") | ascii_downcase) == ($address | ascii_downcase) + ) + )] + | last + | .hash // empty + ' "$broadcast_file" +} + +lookup_block_hex() { + local broadcast_file=$1 + local transaction_hash=$2 + jq -r --arg hash "$transaction_hash" ' + [.receipts[] | select((.transactionHash // "") == $hash)] + | last + | .blockNumber // empty + ' "$broadcast_file" +} + +print_deployment_summary() { + local broadcast_file=$1 + local deployments label address transaction_hash block_hex block_number proxy_admin + + deployments=$(sed -nE \ + 's/^.*WIZARD_DEPLOYMENT\|([^|]+)\|[[:space:]]*(0x[[:xdigit:]]{40}).*$/\1|\2/p' \ + "$FORGE_OUTPUT_FILE" | awk -F'|' '!seen[$1 "|" tolower($2)]++') + + tty_println "" + tty_success "Deployment complete" + tty_field "Chain ID" "$chain_id" + tty_field "Deployer" "$wallet_address ($wallet_account)" + tty_field "Broadcast data" "$broadcast_file" + tty_println "" + + if [[ -z "$deployments" ]]; then + tty_warn "Foundry completed, but no deployment markers were found in its output." + return + fi + + while IFS='|' read -r label address; do + transaction_hash=$(lookup_transaction "$broadcast_file" "$address") + block_hex="" + block_number="unknown" + if [[ -n "$transaction_hash" ]]; then + block_hex=$(lookup_block_hex "$broadcast_file" "$transaction_hash") + if [[ -n "$block_hex" ]]; then + block_number=$(printf '%d' "$block_hex") + fi + else + transaction_hash="unknown" + fi + + tty_section "$label" + tty_field "Address" "$address" + tty_field "Block" "$block_number" + tty_field "Transaction" "$transaction_hash" + + case "$label" in + *"State Oracle Proxy") + proxy_admin=$(jq -r --arg hash "$transaction_hash" ' + .transactions[] + | select((.hash // "") == $hash) + | .additionalContracts[]? + | select(.contractName == "ProxyAdmin") + | .address + ' "$broadcast_file" | tail -n 1) + if [[ -n "$proxy_admin" ]]; then + tty_field "Proxy admin" "$proxy_admin (same block and transaction)" + fi + ;; + esac + tty_println "" + done </dev/null || ! { : >/dev/tty; } 2>/dev/null; then + die "This wizard requires an interactive terminal" +fi + +init_colors +require_command forge +require_command cast +require_command jq + +cd "$ROOT_DIR" + +tty_heading "Credible Layer deployment wizard" +tty_hint "================================" + +menu_select_one "What kind of deployment is this?" "Production" "Testing" +deployment_kind=$MENU_VALUE +deployment_is_testing=false +if [[ "$deployment_kind" == "Testing" ]]; then + deployment_is_testing=true +fi + +menu_select_one "Deploy a staging State Oracle as well?" "No" "Yes" +deploy_staging=false +if [[ "$MENU_VALUE" == "Yes" ]]; then + deploy_staging=true +fi + +admin_options=("Owner" "Whitelist") +if [[ "$deployment_is_testing" == "true" ]]; then + admin_options[2]="Super Admin (test-only)" + admin_options[3]="Always Approve (test-only, unsafe)" +fi +menu_select_many "Which admin verifiers should be deployed?" "${admin_options[@]}" +admin_selected=("${MULTI_SELECTED[@]}") + +admin_production=(0 0 0 0) +admin_staging=(0 0 0 0) +while true; do + admin_production=(0 0 0 0) + admin_staging=(0 0 0 0) + for ((i = 0; i < ${#admin_options[@]}; i++)); do + [[ ${admin_selected[$i]} -eq 1 ]] || continue + if [[ "$deploy_staging" == "true" ]]; then + menu_select_many "Add ${admin_options[$i]} to which State Oracle(s)?" "Production" "Staging" + admin_production[$i]=${MULTI_SELECTED[0]} + admin_staging[$i]=${MULTI_SELECTED[1]} + else + menu_select_many "Add ${admin_options[$i]} to which State Oracle(s)?" "Production" + admin_production[$i]=${MULTI_SELECTED[0]} + fi + done + + production_count=0 + staging_count=0 + for ((i = 0; i < ${#admin_options[@]}; i++)); do + production_count=$((production_count + admin_production[i])) + staging_count=$((staging_count + admin_staging[i])) + done + if [[ $production_count -gt 0 ]] && { [[ "$deploy_staging" == "false" ]] || [[ $staging_count -gt 0 ]]; }; then + break + fi + tty_warn "Each State Oracle needs at least one admin verifier. Please assign them again." +done + +da_options=("ECDSA signatures" "On-chain bytecode") +menu_select_many "Which DA verifiers should be deployed?" "${da_options[@]}" +da_selected=("${MULTI_SELECTED[@]}") + +da_production=(0 0) +da_staging=(0 0) +while true; do + da_production=(0 0) + da_staging=(0 0) + for ((i = 0; i < ${#da_options[@]}; i++)); do + [[ ${da_selected[$i]} -eq 1 ]] || continue + if [[ "$deploy_staging" == "true" ]]; then + menu_select_many "Add ${da_options[$i]} to which State Oracle(s)?" "Production" "Staging" + da_production[$i]=${MULTI_SELECTED[0]} + da_staging[$i]=${MULTI_SELECTED[1]} + else + menu_select_many "Add ${da_options[$i]} to which State Oracle(s)?" "Production" + da_production[$i]=${MULTI_SELECTED[0]} + fi + done + + production_count=$((da_production[0] + da_production[1])) + staging_count=$((da_staging[0] + da_staging[1])) + if [[ $production_count -gt 0 ]] && { [[ "$deploy_staging" == "false" ]] || [[ $staging_count -gt 0 ]]; }; then + break + fi + tty_warn "Each State Oracle needs at least one DA verifier. Please assign them again." +done + +menu_select_one "Should the State Oracle whitelist be enabled?" "Enabled" "Disabled" +whitelist_enabled=true +if [[ "$MENU_VALUE" == "Disabled" ]]; then + whitelist_enabled=false +fi + +whitelist_addresses=() +if [[ "$whitelist_enabled" == "true" ]]; then + menu_select_one "Add addresses to the initial whitelist?" "No" "Yes" + if [[ "$MENU_VALUE" == "Yes" ]]; then + while true; do + prompt_address "Address to whitelist" "" + candidate=$PROMPT_RESULT + duplicate=false + for existing in "${whitelist_addresses[@]-}"; do + if [[ "$(printf '%s' "$existing" | tr '[:upper:]' '[:lower:]')" \ + == "$(printf '%s' "$candidate" | tr '[:upper:]' '[:lower:]')" ]]; then + duplicate=true + fi + done + if [[ "$duplicate" == "true" ]]; then + tty_warn "That address is already in the initial whitelist." + else + whitelist_addresses[${#whitelist_addresses[@]}]=$candidate + fi + + menu_select_one "Initial whitelist now contains ${#whitelist_addresses[@]} address(es)." \ + "Add another address" "Done" + [[ "$MENU_VALUE" == "Add another address" ]] || break + done + fi +fi + +menu_select_one "Verify deployed contracts on the block explorer?" "Yes" "No" +verify_contracts=false +etherscan_api_key="" +if [[ "$MENU_VALUE" == "Yes" ]]; then + verify_contracts=true + etherscan_api_key=${ETHERSCAN_API_KEY:-} + if [[ -z "$etherscan_api_key" ]]; then + while ! prompt_secret "ETHERSCAN_API_KEY"; do + tty_warn "An API key is required when verification is enabled." + done + etherscan_api_key=$PROMPT_RESULT + PROMPT_RESULT="" + else + tty_success "Using the non-empty ETHERSCAN_API_KEY from the environment." + fi +fi + +default_rpc=${RPC_URL:-${ETH_RPC_URL:-}} +while true; do + prompt_value "RPC URL" "$default_rpc" + rpc_url=$PROMPT_RESULT + if chain_id=$(cast chain-id --rpc-url "$rpc_url" 2>/dev/null); then + break + fi + tty_warn "Could not connect to that RPC URL." +done +tty_success "Connected to chain ID $chain_id." + +prompt_uint "Maximum assertions per adopter" "${STATE_ORACLE_MAX_ASSERTIONS_PER_AA:-}" "65535" +max_assertions=$PROMPT_RESULT +prompt_uint "Assertion timelock in blocks" "${STATE_ORACLE_ASSERTION_TIMELOCK_BLOCKS:-}" \ + "115792089237316195423570985008687907853269984665640564039457584007913129639935" +assertion_timelock=$PROMPT_RESULT +prompt_address "State Oracle admin" "${STATE_ORACLE_ADMIN_ADDRESS:-}" +state_oracle_admin=$PROMPT_RESULT + +staging_max_assertions="" +staging_assertion_timelock="" +if [[ "$deploy_staging" == "true" ]]; then + prompt_uint "Staging maximum assertions per adopter" \ + "${STAGING_STATE_ORACLE_MAX_ASSERTIONS_PER_AA:-}" "65535" + staging_max_assertions=$PROMPT_RESULT + prompt_uint "Staging assertion timelock in blocks" \ + "${STAGING_STATE_ORACLE_ASSERTION_TIMELOCK_BLOCKS:-}" \ + "115792089237316195423570985008687907853269984665640564039457584007913129639935" + staging_assertion_timelock=$PROMPT_RESULT +fi + +da_prover="" +if [[ ${da_selected[0]} -eq 1 ]]; then + prompt_address "DA prover address" "${DA_PROVER_ADDRESS:-}" + da_prover=$PROMPT_RESULT +fi + +admin_verifier_whitelist_admin="" +if [[ ${admin_selected[1]} -eq 1 ]]; then + prompt_address "Admin Verifier Whitelist owner" "${ADMIN_VERIFIER_WHITELIST_ADMIN_ADDRESS:-}" + admin_verifier_whitelist_admin=$PROMPT_RESULT +fi + +test_super_admin="" +if [[ ${admin_selected[2]:-0} -eq 1 ]]; then + prompt_address "Testing Super Admin address" "${TEST_ADMIN_VERIFIER_SUPER_ADMIN_ADDRESS:-}" + test_super_admin=$PROMPT_RESULT +fi + +wallet_output=$(cast wallet list 2>/dev/null) || die "Unable to list Foundry keystore accounts" +wallet_names=() +wallet_labels=() +while IFS= read -r wallet_line; do + [[ -n "$wallet_line" ]] || continue + wallet_labels[${#wallet_labels[@]}]=$wallet_line + wallet_names[${#wallet_names[@]}]=${wallet_line%% (*} +done <"$PASSWORD_FILE" + wallet_password="" + + if wallet_address=$(cast wallet address --account "$wallet_account" --password-file "$PASSWORD_FILE" 2>/dev/null); then + break + fi + + rm -f "$PASSWORD_FILE" + PASSWORD_FILE="" + tty_warn "That password did not unlock $wallet_account. Try again." +done + +if ! wallet_balance_wei=$(cast balance "$wallet_address" --rpc-url "$rpc_url" 2>/dev/null); then + die "Unable to read the balance for $wallet_address on chain $chain_id" +fi +if [[ ! "$wallet_balance_wei" =~ ^[0-9]+$ ]]; then + die "Received an invalid balance for $wallet_address on chain $chain_id" +fi +if [[ "$wallet_balance_wei" =~ ^0+$ ]]; then + die "Selected wallet $wallet_address has no funds on chain $chain_id. Fund it before deploying." +fi + +wallet_balance=$(cast balance --ether "$wallet_address" --rpc-url "$rpc_url" 2>/dev/null || printf 'unknown') +wallet_nonce=$(cast nonce "$wallet_address" --rpc-url "$rpc_url" 2>/dev/null || printf 'unknown') +tty_success "Wallet unlocked successfully: $wallet_address" + +env_args=( + "ETH_RPC_URL=$rpc_url" + "DEPLOYMENT_IS_TESTING=$deployment_is_testing" + "DEPLOY_STAGING_STATE_ORACLE=$deploy_staging" + "STATE_ORACLE_WHITELIST_ENABLED=$whitelist_enabled" + "STATE_ORACLE_MAX_ASSERTIONS_PER_AA=$max_assertions" + "STATE_ORACLE_ASSERTION_TIMELOCK_BLOCKS=$assertion_timelock" + "STATE_ORACLE_ADMIN_ADDRESS=$state_oracle_admin" + "DA_VERIFIER_ECDSA_PRODUCTION=$(bool_for_selection "${da_production[0]}")" + "DA_VERIFIER_ECDSA_STAGING=$(bool_for_selection "${da_staging[0]}")" + "DA_VERIFIER_ONCHAIN_PRODUCTION=$(bool_for_selection "${da_production[1]}")" + "DA_VERIFIER_ONCHAIN_STAGING=$(bool_for_selection "${da_staging[1]}")" + "ADMIN_VERIFIER_OWNER_PRODUCTION=$(bool_for_selection "${admin_production[0]}")" + "ADMIN_VERIFIER_OWNER_STAGING=$(bool_for_selection "${admin_staging[0]}")" + "ADMIN_VERIFIER_WHITELIST_PRODUCTION=$(bool_for_selection "${admin_production[1]}")" + "ADMIN_VERIFIER_WHITELIST_STAGING=$(bool_for_selection "${admin_staging[1]}")" + "ADMIN_VERIFIER_SUPER_ADMIN_PRODUCTION=$(bool_for_selection "${admin_production[2]}")" + "ADMIN_VERIFIER_SUPER_ADMIN_STAGING=$(bool_for_selection "${admin_staging[2]}")" + "ADMIN_VERIFIER_ALWAYS_APPROVE_PRODUCTION=$(bool_for_selection "${admin_production[3]}")" + "ADMIN_VERIFIER_ALWAYS_APPROVE_STAGING=$(bool_for_selection "${admin_staging[3]}")" +) +env_unset_args=(-u STATE_ORACLE_INITIAL_WHITELIST) + +if [[ "$deploy_staging" == "true" ]]; then + env_args[${#env_args[@]}]="STAGING_STATE_ORACLE_MAX_ASSERTIONS_PER_AA=$staging_max_assertions" + env_args[${#env_args[@]}]="STAGING_STATE_ORACLE_ASSERTION_TIMELOCK_BLOCKS=$staging_assertion_timelock" +fi +if [[ -n "$da_prover" ]]; then + env_args[${#env_args[@]}]="DA_PROVER_ADDRESS=$da_prover" +fi +if [[ -n "$admin_verifier_whitelist_admin" ]]; then + env_args[${#env_args[@]}]="ADMIN_VERIFIER_WHITELIST_ADMIN_ADDRESS=$admin_verifier_whitelist_admin" +fi +if [[ -n "$test_super_admin" ]]; then + env_args[${#env_args[@]}]="TEST_ADMIN_VERIFIER_SUPER_ADMIN_ADDRESS=$test_super_admin" +fi +if [[ ${#whitelist_addresses[@]} -gt 0 ]]; then + whitelist_csv=$(join_by_comma "${whitelist_addresses[@]}") + env_args[${#env_args[@]}]="STATE_ORACLE_INITIAL_WHITELIST=$whitelist_csv" +fi +if [[ "$verify_contracts" == "true" ]]; then + env_args[${#env_args[@]}]="ETHERSCAN_API_KEY=$etherscan_api_key" +fi + +forge_args=( + script + "script/DeployWizard.s.sol:DeployWizard" + --rpc-url "$rpc_url" + --account "$wallet_account" + --sender "$wallet_address" + --password-file "$PASSWORD_FILE" + --broadcast +) +if [[ "$verify_contracts" == "true" ]]; then + forge_args[${#forge_args[@]}]=--verify +fi + +production_admin_summary="" +staging_admin_summary="" +for ((i = 0; i < ${#admin_options[@]}; i++)); do + if [[ ${admin_production[$i]} -eq 1 ]]; then + production_admin_summary="${production_admin_summary}${production_admin_summary:+, }${admin_options[$i]}" + fi + if [[ ${admin_staging[$i]} -eq 1 ]]; then + staging_admin_summary="${staging_admin_summary}${staging_admin_summary:+, }${admin_options[$i]}" + fi +done +production_da_summary="" +staging_da_summary="" +for ((i = 0; i < ${#da_options[@]}; i++)); do + if [[ ${da_production[$i]} -eq 1 ]]; then + production_da_summary="${production_da_summary}${production_da_summary:+, }${da_options[$i]}" + fi + if [[ ${da_staging[$i]} -eq 1 ]]; then + staging_da_summary="${staging_da_summary}${staging_da_summary:+, }${da_options[$i]}" + fi +done + +tty_println "" +tty_section "Deployment summary" +tty_field "Kind" "$deployment_kind" +tty_field "Chain ID" "$chain_id" +tty_field "Wallet" "$wallet_account ($wallet_address)" +tty_field "Wallet balance" "$wallet_balance ETH" +tty_field "Wallet nonce" "$wallet_nonce" +tty_field "State Oracle admin" "$state_oracle_admin" +tty_field "Staging oracle" "$deploy_staging" +tty_field "Whitelist enabled" "$whitelist_enabled" +tty_field "Verify contracts" "$verify_contracts" +tty_field "Production limits" "$max_assertions assertions, $assertion_timelock blocks" +tty_field "Production admin" "$production_admin_summary" +tty_field "Production DA" "$production_da_summary" +if [[ "$deploy_staging" == "true" ]]; then + tty_field "Staging limits" "$staging_max_assertions assertions, $staging_assertion_timelock blocks" + tty_field "Staging admin" "$staging_admin_summary" + tty_field "Staging DA" "$staging_da_summary" +fi +if [[ -n "$da_prover" ]]; then + tty_field "DA prover" "$da_prover" +fi +if [[ -n "$admin_verifier_whitelist_admin" ]]; then + tty_field "Verifier WL owner" "$admin_verifier_whitelist_admin" +fi +if [[ -n "$test_super_admin" ]]; then + tty_field "Test super admin" "$test_super_admin" +fi +if [[ ${#whitelist_addresses[@]} -gt 0 ]]; then + tty_field "Initial whitelist" "$(join_by_comma "${whitelist_addresses[@]}")" +fi +if [[ "$deployment_kind" == "Production" ]]; then + tty_println "" + tty_warn "Production deployment selected. Confirm the chain, admin, and wallet carefully." +fi + +print_redacted_command + +menu_select_one "Ready to continue?" "Deploy now" "Print command only" "Cancel" +case "$MENU_VALUE" in + "Print command only") + tty_success "No transactions were sent." + exit 0 + ;; + "Cancel") + tty_warn "Deployment cancelled." + exit 0 + ;; +esac + +FORGE_OUTPUT_FILE=$(mktemp "${TMPDIR:-/tmp}/credible-layer-forge.XXXXXX") +set +e +env "${env_unset_args[@]}" "${env_args[@]}" forge "${forge_args[@]}" 2>&1 | tee "$FORGE_OUTPUT_FILE" +forge_status=${PIPESTATUS[0]} +set -e +[[ $forge_status -eq 0 ]] || die "Forge deployment failed with status $forge_status" + +broadcast_file="$ROOT_DIR/broadcast/DeployWizard.s.sol/$chain_id/run-latest.json" +[[ -f "$broadcast_file" ]] || die "Deployment succeeded, but the broadcast receipt was not found at $broadcast_file" +print_deployment_summary "$broadcast_file" diff --git a/src/StateOracle.sol b/src/StateOracle.sol index 92e02b0..156bce0 100644 --- a/src/StateOracle.sol +++ b/src/StateOracle.sol @@ -201,15 +201,47 @@ contract StateOracle is Batch, Initializable, StateOracleAccessControl { IDAVerifier[] calldata _daVerifiers, uint16 _maxAssertionsPerAA ) external initializer { + _initialize(admin, _adminVerifiers, _daVerifiers, _maxAssertionsPerAA, true, new address[](0)); + } + + /// @notice Initializes the contract with an explicit whitelist configuration + /// @param admin The address to set as the admin + /// @param _adminVerifiers The admin verifiers to add + /// @param _daVerifiers The DA verifiers to add + /// @param _maxAssertionsPerAA Maximum number of assertions per assertion adopter + /// @param _whitelistEnabled Whether whitelist checks are enabled initially + /// @param _initialWhitelist Addresses to add to the initial whitelist + function initializeWithWhitelist( + address admin, + IAdminVerifier[] calldata _adminVerifiers, + IDAVerifier[] calldata _daVerifiers, + uint16 _maxAssertionsPerAA, + bool _whitelistEnabled, + address[] calldata _initialWhitelist + ) external initializer { + _initialize(admin, _adminVerifiers, _daVerifiers, _maxAssertionsPerAA, _whitelistEnabled, _initialWhitelist); + } + + function _initialize( + address admin, + IAdminVerifier[] memory _adminVerifiers, + IDAVerifier[] memory _daVerifiers, + uint16 _maxAssertionsPerAA, + bool _whitelistEnabled, + address[] memory _initialWhitelist + ) internal { _initializeRoles(admin); - whitelistEnabled = true; + whitelistEnabled = _whitelistEnabled; for (uint256 i = 0; i < _adminVerifiers.length; i++) { _addAdminVerifier(_adminVerifiers[i]); } for (uint256 i = 0; i < _daVerifiers.length; i++) { _addDAVerifier(_daVerifiers[i]); } + for (uint256 i = 0; i < _initialWhitelist.length; i++) { + _addToWhitelist(_initialWhitelist[i]); + } _setMaxAssertionsPerAA(_maxAssertionsPerAA); } @@ -292,6 +324,10 @@ contract StateOracle is Batch, Initializable, StateOracleAccessControl { /// @notice Adds an account to the whitelist /// @param account The address to add function addToWhitelist(address account) external onlyOperator { + _addToWhitelist(account); + } + + function _addToWhitelist(address account) internal { require(!whitelist[account], AlreadyWhitelisted(account)); whitelist[account] = true; emit AddedToWhitelist(account); diff --git a/src/verification/admin/AdminVerifierAlwaysApprove.sol b/src/verification/admin/AdminVerifierAlwaysApprove.sol new file mode 100644 index 0000000..c161ea8 --- /dev/null +++ b/src/verification/admin/AdminVerifierAlwaysApprove.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {IAdminVerifier} from "../../interfaces/IAdminVerifier.sol"; + +/// @title AdminVerifierAlwaysApprove +/// @notice Test-only admin verifier that accepts every requester for every assertion adopter. +/// @dev WARNING: This component is intended strictly for internal testing. Deploying this +/// contract in production would allow any address to register as manager for any adopter. +contract AdminVerifierAlwaysApprove is IAdminVerifier { + /// @inheritdoc IAdminVerifier + /// @notice Always returns true. + function verifyAdmin(address, address, bytes calldata) external pure returns (bool) { + return true; + } +} diff --git a/test/AdminVerifierAlwaysApprove.t.sol b/test/AdminVerifierAlwaysApprove.t.sol new file mode 100644 index 0000000..51f170f --- /dev/null +++ b/test/AdminVerifierAlwaysApprove.t.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: CC0-1.0 +pragma solidity ^0.8.28; + +import {Test} from "forge-std/Test.sol"; +import {AdminVerifierAlwaysApprove} from "../src/verification/admin/AdminVerifierAlwaysApprove.sol"; + +contract AdminVerifierAlwaysApproveTest is Test { + AdminVerifierAlwaysApprove verifier; + address constant REQUESTER = + address(uint160(uint256(keccak256(abi.encode("pcl.test.AdminVerifierAlwaysApprove.REQUESTER"))))); + address constant OTHER = + address(uint160(uint256(keccak256(abi.encode("pcl.test.AdminVerifierAlwaysApprove.OTHER"))))); + address constant ADOPTER = + address(uint160(uint256(keccak256(abi.encode("pcl.test.AdminVerifierAlwaysApprove.ADOPTER"))))); + + function setUp() public { + verifier = new AdminVerifierAlwaysApprove(); + } + + function test_verifyAdminReturnsTrueForAnyRequester() public view { + assertTrue(verifier.verifyAdmin(ADOPTER, REQUESTER, "")); + assertTrue(verifier.verifyAdmin(ADOPTER, OTHER, "")); + } + + function test_verifyAdminReturnsTrueForZeroAddresses() public view { + assertTrue(verifier.verifyAdmin(address(0), address(0), "")); + } + + function test_verifyAdminReturnsTrueWithData() public view { + assertTrue(verifier.verifyAdmin(ADOPTER, REQUESTER, abi.encode("ignored"))); + } +} diff --git a/test/DeployAdminVerifiers.t.sol b/test/DeployAdminVerifiers.t.sol new file mode 100644 index 0000000..58b7c85 --- /dev/null +++ b/test/DeployAdminVerifiers.t.sol @@ -0,0 +1,194 @@ +// SPDX-License-Identifier: CC0-1.0 +pragma solidity ^0.8.28; + +import {Test} from "forge-std/Test.sol"; +import {DeployCore} from "../script/DeployCore.s.sol"; +import {DeployTestingAdminVerifiers} from "../script/DeployTestingAdminVerifiers.s.sol"; +import {IAdminVerifier} from "../src/interfaces/IAdminVerifier.sol"; +import {IDAVerifier} from "../src/interfaces/IDAVerifier.sol"; +import {StateOracle} from "../src/StateOracle.sol"; +import {AdminVerifierSuperAdmin} from "../src/verification/admin/AdminVerifierSuperAdmin.sol"; +import {AdminVerifierWhitelist} from "../src/verification/admin/AdminVerifierWhitelist.sol"; +import {CREATE_X_ADDRESS} from "../script/ICreateX.sol"; +import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {DAVerifierMock} from "./utils/DAVerifierMock.sol"; +import {OwnableAdopter} from "./utils/Adopter.sol"; +import {CreateXTestDouble} from "./utils/CreateXTestDouble.sol"; + +contract DeployCoreHarness is DeployCore { + function configureAdminVerifiers( + address _admin, + bool _deployOwnerVerifier, + bool _deployWhitelistVerifier, + address _whitelistAdmin + ) external { + admin = _admin; + deployOwnerVerifier = _deployOwnerVerifier; + deployWhitelistVerifier = _deployWhitelistVerifier; + whitelistAdmin = _whitelistAdmin; + } + + function deployAdminVerifiersForTest() external returns (address[] memory) { + return _deployAdminVerifiers(); + } + + function deployStateOracleProxyForTest( + address stateOracle, + address[] memory adminVerifierDeployments, + address[] memory daVerifierAddresses, + uint16 maxAssertions + ) external returns (address) { + return _deployStateOracleProxy(stateOracle, adminVerifierDeployments, daVerifierAddresses, maxAssertions); + } +} + +contract DeployTestingAdminVerifiersHarness is DeployTestingAdminVerifiers { + function configureTestingAdminVerifiers( + address _stateOracle, + address _superAdmin, + bool _deploySuperAdminVerifier, + bool _deployAlwaysApproveVerifier, + bool _addToStateOracle + ) external { + stateOracle = _stateOracle; + superAdmin = _superAdmin; + deploySuperAdminVerifierEnabled = _deploySuperAdminVerifier; + deployAlwaysApproveVerifierEnabled = _deployAlwaysApproveVerifier; + addToStateOracle = _addToStateOracle; + } + + function runForTest() external returns (address[] memory) { + return _run(); + } +} + +contract DeployAdminVerifiersTest is Test { + address constant ADMIN = address(uint160(uint256(keccak256(abi.encode("pcl.test.DeployAdminVerifiers.ADMIN"))))); + address constant OWNER = address(uint160(uint256(keccak256(abi.encode("pcl.test.DeployAdminVerifiers.OWNER"))))); + address constant WHITELIST_ADMIN = + address(uint160(uint256(keccak256(abi.encode("pcl.test.DeployAdminVerifiers.WHITELIST_ADMIN"))))); + address constant SUPER_ADMIN = + address(uint160(uint256(keccak256(abi.encode("pcl.test.DeployAdminVerifiers.SUPER_ADMIN"))))); + address constant OTHER = address(uint160(uint256(keccak256(abi.encode("pcl.test.DeployAdminVerifiers.OTHER"))))); + uint16 constant MAX_ASSERTIONS_PER_AA = 5; + + function setUp() public { + CreateXTestDouble createXImplementation = new CreateXTestDouble(); + vm.etch(CREATE_X_ADDRESS, address(createXImplementation).code); + } + + function test_coreDeployAdminVerifiersAddsOnlyProductionVerifiersToStateOracle() public { + DeployCoreHarness deployer = new DeployCoreHarness(); + deployer.configureAdminVerifiers(ADMIN, true, true, WHITELIST_ADMIN); + + address[] memory verifiers = deployer.deployAdminVerifiersForTest(); + assertEq(verifiers.length, 2); + + OwnableAdopter adopter = new OwnableAdopter(OWNER); + assertTrue(IAdminVerifier(verifiers[0]).verifyAdmin(address(adopter), OWNER, "")); + assertTrue(AdminVerifierWhitelist(verifiers[1]).hasRole(bytes32(0), WHITELIST_ADMIN)); + assertTrue( + AdminVerifierWhitelist(verifiers[1]) + .hasRole(AdminVerifierWhitelist(verifiers[1]).WHITELIST_ADMIN_ROLE(), WHITELIST_ADMIN) + ); + + address[] memory daVerifiers = new address[](1); + daVerifiers[0] = address(new DAVerifierMock()); + StateOracle stateOracle = StateOracle( + deployer.deployStateOracleProxyForTest( + address(_deployStateOracleImplementation()), verifiers, daVerifiers, MAX_ASSERTIONS_PER_AA + ) + ); + + for (uint256 i = 0; i < verifiers.length; i++) { + assertTrue(stateOracle.isAdminVerifierRegistered(IAdminVerifier(verifiers[i]))); + } + } + + function test_RevertIf_coreDeployWhitelistVerifierWithoutAdmin() public { + DeployCoreHarness deployer = new DeployCoreHarness(); + deployer.configureAdminVerifiers(ADMIN, false, true, address(0)); + + vm.expectRevert(bytes("Invalid whitelist admin")); + deployer.deployAdminVerifiersForTest(); + } + + function test_testingScriptDeploysAndAddsTestVerifiersToStateOracle() public { + DeployTestingAdminVerifiersHarness deployer = new DeployTestingAdminVerifiersHarness(); + StateOracle stateOracle = _deployStateOracle(address(deployer), new IAdminVerifier[](0)); + deployer.configureTestingAdminVerifiers(address(stateOracle), SUPER_ADMIN, true, true, true); + + address[] memory verifiers = deployer.runForTest(); + assertEq(verifiers.length, 2); + assertEq(CreateXTestDouble(CREATE_X_ADDRESS).deploymentCount(), 2); + + assertTrue(AdminVerifierSuperAdmin(verifiers[0]).verifyAdmin(address(1), SUPER_ADMIN, "")); + assertTrue(IAdminVerifier(verifiers[1]).verifyAdmin(address(1), OTHER, "")); + assertTrue(stateOracle.isAdminVerifierRegistered(IAdminVerifier(verifiers[0]))); + assertTrue(stateOracle.isAdminVerifierRegistered(IAdminVerifier(verifiers[1]))); + } + + function test_RevertIf_testingScriptEntrypointsRunOutsideTestingDeployment() public { + DeployTestingAdminVerifiers deployer = new DeployTestingAdminVerifiers(); + vm.setEnv("DEPLOYMENT_IS_TESTING", "false"); + deployer.setUp(); + + vm.expectRevert(bytes("Testing admin verifiers are test-only")); + deployer.run(); + vm.expectRevert(bytes("Testing admin verifiers are test-only")); + deployer.deploySuperAdminVerifier(SUPER_ADMIN); + vm.expectRevert(bytes("Testing admin verifiers are test-only")); + deployer.deployAlwaysApproveAdminVerifier(); + vm.expectRevert(bytes("Testing admin verifiers are test-only")); + deployer.deployAndAddSuperAdminVerifier(address(1), SUPER_ADMIN); + vm.expectRevert(bytes("Testing admin verifiers are test-only")); + deployer.deployAndAddAlwaysApproveAdminVerifier(address(1)); + vm.expectRevert(bytes("Testing admin verifiers are test-only")); + deployer.addAdminVerifier(address(1), address(1)); + } + + function test_testingScriptAllowsEntrypointsInTestingDeployment() public { + DeployTestingAdminVerifiers deployer = new DeployTestingAdminVerifiers(); + vm.setEnv("DEPLOYMENT_IS_TESTING", "true"); + deployer.setUp(); + vm.setEnv("DEPLOYMENT_IS_TESTING", "false"); + + address verifier = deployer.deployAlwaysApproveAdminVerifier(); + + assertTrue(IAdminVerifier(verifier).verifyAdmin(address(1), OTHER, "")); + } + + function test_RevertIf_testingScriptDeploysSuperAdminVerifierWithoutAdmin() public { + DeployTestingAdminVerifiersHarness deployer = new DeployTestingAdminVerifiersHarness(); + deployer.configureTestingAdminVerifiers(address(1), address(0), true, false, false); + + vm.expectRevert(bytes("Invalid super admin")); + deployer.runForTest(); + } + + function test_RevertIf_testingScriptAddsVerifiersWithoutStateOracle() public { + DeployTestingAdminVerifiersHarness deployer = new DeployTestingAdminVerifiersHarness(); + deployer.configureTestingAdminVerifiers(address(0), SUPER_ADMIN, false, true, true); + + vm.expectRevert(bytes("Invalid state oracle")); + deployer.runForTest(); + } + + function _deployStateOracle(address oracleAdmin, IAdminVerifier[] memory adminVerifiers) + internal + returns (StateOracle) + { + StateOracle implementation = _deployStateOracleImplementation(); + IDAVerifier[] memory daVerifiers = new IDAVerifier[](1); + daVerifiers[0] = IDAVerifier(address(new DAVerifierMock())); + bytes memory initCallData = abi.encodeWithSelector( + StateOracle.initialize.selector, oracleAdmin, adminVerifiers, daVerifiers, MAX_ASSERTIONS_PER_AA + ); + return + StateOracle(address(new TransparentUpgradeableProxy(address(implementation), address(this), initCallData))); + } + + function _deployStateOracleImplementation() internal returns (StateOracle) { + return new StateOracle(10); + } +} diff --git a/test/StateOracle.t.sol b/test/StateOracle.t.sol index 7d7574e..c0fdd5a 100644 --- a/test/StateOracle.t.sol +++ b/test/StateOracle.t.sol @@ -86,6 +86,9 @@ contract Constructor is StateOracleBase { } contract Initialize is StateOracleBase { + address constant INITIAL_WHITELIST_ACCOUNT = address(0xBEEF); + IDAVerifier additionalDAVerifier; + function test_RevertIf_alreadyInitialized() public { vm.expectRevert(Initializable.InvalidInitialization.selector); stateOracle.initialize(OWNER, new IAdminVerifier[](0), new IDAVerifier[](0), MAX_ASSERTIONS_PER_AA); @@ -102,6 +105,45 @@ contract Initialize is StateOracleBase { vm.expectRevert(Initializable.InvalidInitialization.selector); stateOracle.initialize(STATE_ORACLE_ADMIN, verifiers, daVerifiers, MAX_ASSERTIONS_PER_AA); } + + function test_initializeWithWhitelistEnabledAndInitialAccount() public { + address[] memory initialWhitelist = new address[](1); + initialWhitelist[0] = INITIAL_WHITELIST_ACCOUNT; + + StateOracle configuredOracle = _deployWithWhitelist(true, initialWhitelist); + + assertTrue(configuredOracle.whitelistEnabled()); + assertTrue(configuredOracle.whitelist(INITIAL_WHITELIST_ACCOUNT)); + assertTrue(configuredOracle.isWhitelisted(INITIAL_WHITELIST_ACCOUNT)); + assertFalse(configuredOracle.isWhitelisted(address(0xCAFE))); + assertTrue(configuredOracle.isDAVerifierRegistered(daVerifierMock)); + assertTrue(configuredOracle.isDAVerifierRegistered(additionalDAVerifier)); + } + + function test_initializeWithWhitelistDisabled() public { + StateOracle configuredOracle = _deployWithWhitelist(false, new address[](0)); + + assertFalse(configuredOracle.whitelistEnabled()); + assertTrue(configuredOracle.isWhitelisted(address(0xCAFE))); + } + + function _deployWithWhitelist(bool enabled, address[] memory initialWhitelist) + internal + returns (StateOracle configuredOracle) + { + StateOracle implementation = new StateOracle(TIMEOUT); + IAdminVerifier[] memory verifiers = new IAdminVerifier[](1); + verifiers[0] = adminVerifier; + additionalDAVerifier = IDAVerifier(address(new DAVerifierMock())); + IDAVerifier[] memory daVerifiers = new IDAVerifier[](2); + daVerifiers[0] = daVerifierMock; + daVerifiers[1] = additionalDAVerifier; + bytes memory data = abi.encodeCall( + StateOracle.initializeWithWhitelist, + (STATE_ORACLE_ADMIN, verifiers, daVerifiers, MAX_ASSERTIONS_PER_AA, enabled, initialWhitelist) + ); + configuredOracle = StateOracle(deployProxy(address(implementation), data)); + } } contract Register is StateOracleBase { diff --git a/test/integration/DeployCoreWithStagingScript.t.sol b/test/integration/DeployCoreWithStagingScript.t.sol new file mode 100644 index 0000000..982c5fe --- /dev/null +++ b/test/integration/DeployCoreWithStagingScript.t.sol @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: CC0-1.0 +pragma solidity ^0.8.28; + +import {Test} from "forge-std/Test.sol"; +import {ASSERTION_CONTRACT_ADDRESS, CALLER_ADDRESS, PRECOMPILE_ADDRESS} from "../../script/DeployCore.s.sol"; +import {DeployCoreWithStaging} from "../../script/DeployCoreWithStaging.s.sol"; +import {CREATE_X_ADDRESS} from "../../script/ICreateX.sol"; +import {StateOracle} from "../../src/StateOracle.sol"; +import {IAdminVerifier} from "../../src/interfaces/IAdminVerifier.sol"; +import {CreateXTestDouble} from "../utils/CreateXTestDouble.sol"; + +contract DeployCoreWithStagingScriptIntegrationTest is Test { + address internal constant ADMIN = address(0xA11CE); + address internal constant DA_PROVER = address(0xDA); + + DeployCoreWithStaging internal deployment; + + function setUp() public { + vm.deal(CALLER_ADDRESS, 1); + vm.deal(ASSERTION_CONTRACT_ADDRESS, 1); + vm.deal(PRECOMPILE_ADDRESS, 1); + + CreateXTestDouble createXImplementation = new CreateXTestDouble(); + vm.etch(CREATE_X_ADDRESS, address(createXImplementation).code); + + vm.setEnv("STATE_ORACLE_MAX_ASSERTIONS_PER_AA", "10"); + vm.setEnv("STATE_ORACLE_ASSERTION_TIMELOCK_BLOCKS", "10"); + vm.setEnv("STAGING_STATE_ORACLE_MAX_ASSERTIONS_PER_AA", "20"); + vm.setEnv("STAGING_STATE_ORACLE_ASSERTION_TIMELOCK_BLOCKS", "10"); + vm.setEnv("STATE_ORACLE_ADMIN_ADDRESS", vm.toString(ADMIN)); + vm.setEnv("DA_PROVER_ADDRESS", vm.toString(DA_PROVER)); + vm.setEnv("ADMIN_VERIFIER_WHITELIST_ADMIN_ADDRESS", vm.toString(ADMIN)); + vm.setEnv("DEPLOY_ADMIN_VERIFIER_OWNER", "true"); + vm.setEnv("DEPLOY_ADMIN_VERIFIER_WHITELIST", "false"); + vm.setEnv("DEPLOY_ADMIN_VERIFIER_ALWAYS_APPROVE", "false"); + vm.setEnv("STATE_ORACLE_WHITELIST_ENABLED", "true"); + vm.setEnv("DEPLOYMENT_IS_TESTING", "false"); + + deployment = new DeployCoreWithStaging(); + } + + function test_CompleteDeterministicDeploymentSupportsTestingConfiguration() public { + uint256 cleanDeployment = vm.snapshotState(); + + deployment.setUp(); + deployment.run(); + + assertEq(CreateXTestDouble(CREATE_X_ADDRESS).deploymentCount(), 7); + address productionOracle = deployment.deployedProductionOracle(); + address stagingOracle = deployment.deployedStagingOracle(); + assertNotEq(productionOracle, address(0)); + assertNotEq(stagingOracle, address(0)); + assertTrue(StateOracle(productionOracle).whitelistEnabled()); + assertTrue(StateOracle(stagingOracle).whitelistEnabled()); + + assertTrue(vm.revertToState(cleanDeployment)); + + vm.expectRevert("Always Approve verifier is test-only"); + deployment.deployAlwaysApproveAdminVerifier(); + + vm.setEnv("STATE_ORACLE_WHITELIST_ENABLED", "false"); + vm.setEnv("DEPLOY_ADMIN_VERIFIER_OWNER", "false"); + vm.setEnv("DEPLOY_ADMIN_VERIFIER_ALWAYS_APPROVE", "true"); + + vm.expectRevert("Always Approve verifier is test-only"); + deployment.setUp(); + + vm.setEnv("DEPLOYMENT_IS_TESTING", "true"); + deployment.setUp(); + deployment.run(); + + address verifier = deployment.deployedAdminVerifiers(0); + StateOracle production = StateOracle(deployment.deployedProductionOracle()); + StateOracle staging = StateOracle(deployment.deployedStagingOracle()); + + assertEq(CreateXTestDouble(CREATE_X_ADDRESS).deploymentCount(), 7); + assertEq(address(production), productionOracle); + assertEq(address(staging), stagingOracle); + assertFalse(production.whitelistEnabled()); + assertFalse(staging.whitelistEnabled()); + assertTrue(production.isAdminVerifierRegistered(IAdminVerifier(verifier))); + assertTrue(staging.isAdminVerifierRegistered(IAdminVerifier(verifier))); + assertTrue(IAdminVerifier(verifier).verifyAdmin(address(0xBEEF), address(0xCAFE), "")); + } +} diff --git a/test/integration/DeployWizardCreateX.t.sol b/test/integration/DeployWizardCreateX.t.sol new file mode 100644 index 0000000..4773c1e --- /dev/null +++ b/test/integration/DeployWizardCreateX.t.sol @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: CC0-1.0 +pragma solidity ^0.8.28; + +import {Test} from "forge-std/Test.sol"; +import {ASSERTION_CONTRACT_ADDRESS, CALLER_ADDRESS, PRECOMPILE_ADDRESS} from "../../script/DeployCore.s.sol"; +import {DeployWizard} from "../../script/DeployWizard.s.sol"; +import {CREATE_X_ADDRESS} from "../../script/ICreateX.sol"; +import {StateOracle} from "../../src/StateOracle.sol"; +import {IAdminVerifier} from "../../src/interfaces/IAdminVerifier.sol"; +import {CreateXTestDouble} from "../utils/CreateXTestDouble.sol"; + +contract DeployWizardCreateXHarness is DeployWizard { + function configureForTest(address _admin, address _superAdmin) external { + testingDeployment = true; + deployStaging = true; + stateOracleWhitelistEnabled = false; + admin = _admin; + assertionTimelockBlocks = 10; + maxAssertionsPerAA = 10; + stagingAssertionTimelockBlocks = 20; + stagingMaxAssertionsPerAA = 20; + daOnChainProduction = true; + daOnChainStaging = true; + adminSuperAdminProduction = true; + adminSuperAdminStaging = true; + adminAlwaysApproveProduction = true; + adminAlwaysApproveStaging = true; + testSuperAdmin = _superAdmin; + initialWhitelist = new address[](0); + } +} + +contract DeployWizardCreateXIntegrationTest is Test { + address internal constant ADMIN = address(0xA11CE); + address internal constant SUPER_ADMIN = address(0x5A); + + DeployWizardCreateXHarness internal deployment; + + function setUp() public { + vm.deal(CALLER_ADDRESS, 1); + vm.deal(ASSERTION_CONTRACT_ADDRESS, 1); + vm.deal(PRECOMPILE_ADDRESS, 1); + + CreateXTestDouble createXImplementation = new CreateXTestDouble(); + vm.etch(CREATE_X_ADDRESS, address(createXImplementation).code); + + deployment = new DeployWizardCreateXHarness(); + deployment.configureForTest(ADMIN, SUPER_ADMIN); + } + + function test_TestingDeploymentUsesCreateXForEveryContract() public { + deployment.run(); + + CreateXTestDouble createX = CreateXTestDouble(CREATE_X_ADDRESS); + assertEq(createX.deploymentCount(), 7); + + IAdminVerifier superAdminVerifier = IAdminVerifier(createX.deployments(1)); + IAdminVerifier alwaysApproveVerifier = IAdminVerifier(createX.deployments(2)); + StateOracle production = StateOracle(createX.deployments(4)); + StateOracle staging = StateOracle(createX.deployments(6)); + + assertFalse(production.whitelistEnabled()); + assertFalse(staging.whitelistEnabled()); + assertTrue(production.isAdminVerifierRegistered(superAdminVerifier)); + assertTrue(production.isAdminVerifierRegistered(alwaysApproveVerifier)); + assertTrue(staging.isAdminVerifierRegistered(superAdminVerifier)); + assertTrue(staging.isAdminVerifierRegistered(alwaysApproveVerifier)); + assertTrue(superAdminVerifier.verifyAdmin(address(0xBEEF), SUPER_ADMIN, "")); + assertTrue(alwaysApproveVerifier.verifyAdmin(address(0xBEEF), address(0xCAFE), "")); + } +} diff --git a/test/shell/test_deploy_wizard.py b/test/shell/test_deploy_wizard.py new file mode 100644 index 0000000..a8329bf --- /dev/null +++ b/test/shell/test_deploy_wizard.py @@ -0,0 +1,361 @@ +#!/usr/bin/env python3 + +import errno +import json +import os +import pty +import selectors +import shutil +import signal +import subprocess +import tempfile +import time +import unittest +from pathlib import Path + + +ROOT_DIR = Path(__file__).resolve().parents[2] +WIZARD = ROOT_DIR / "shell" / "deploy_wizard.sh" +PROXY_ADDRESS = "0x1111111111111111111111111111111111111111" +PROXY_ADMIN_ADDRESS = "0x2222222222222222222222222222222222222222" +CREATE_X_ADDRESS = "0xba5ed099633d3b313e4d5f7bdc1305d3c28ba5ed" +TRANSACTION_HASH = f"0x{'a' * 64}" + + +class WizardSession: + def __init__( + self, + no_color=True, + balance_wei="0", + ambient_whitelist=None, + createx_receipt=False, + ): + self._temporary_directory = tempfile.TemporaryDirectory() + fake_bin = Path(self._temporary_directory.name) + repository = fake_bin / "repository" + (repository / "shell").mkdir(parents=True) + wizard = repository / "shell" / WIZARD.name + shutil.copy2(WIZARD, wizard) + + self.forge_arguments = fake_bin / "forge-arguments" + self.forge_environment = fake_bin / "forge-environment" + forge = fake_bin / "forge" + forge.write_text( + """#!/bin/sh +set -eu +printf '%s\n' "$@" > "$FAKE_FORGE_ARGUMENTS" +env | sort > "$FAKE_FORGE_ENVIRONMENT" +if [ -n "${FAKE_FORGE_RECEIPT:-}" ]; then + mkdir -p broadcast/DeployWizard.s.sol/31337 + cp "$FAKE_FORGE_RECEIPT" broadcast/DeployWizard.s.sol/31337/run-latest.json +fi +if [ -n "${FAKE_FORGE_OUTPUT:-}" ]; then + printf '%s\n' "$FAKE_FORGE_OUTPUT" +fi +""" + ) + forge.chmod(0o755) + cast = fake_bin / "cast" + cast.write_text( + f"""#!/bin/sh +case "$1" in + chain-id) echo 31337 ;; + to-check-sum-address) echo "$2" ;; + balance) echo {balance_wei} ;; + nonce) echo 0 ;; + wallet) + case "$2" in + list) echo "deployer (Local)" ;; + address) echo 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 ;; + esac + ;; +esac +""" + ) + cast.chmod(0o755) + + self.pid, self.master_fd = pty.fork() + if self.pid == 0: + environment = os.environ.copy() + if no_color: + environment["NO_COLOR"] = "1" + else: + environment.pop("NO_COLOR", None) + environment["TERM"] = "xterm-256color" + environment["RPC_URL"] = "http://rpc.example" + environment["STATE_ORACLE_MAX_ASSERTIONS_PER_AA"] = "5" + environment["STATE_ORACLE_ASSERTION_TIMELOCK_BLOCKS"] = "10" + environment["STATE_ORACLE_ADMIN_ADDRESS"] = ( + "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + ) + if ambient_whitelist is None: + environment.pop("STATE_ORACLE_INITIAL_WHITELIST", None) + else: + environment["STATE_ORACLE_INITIAL_WHITELIST"] = ambient_whitelist + environment["FAKE_FORGE_ARGUMENTS"] = str(self.forge_arguments) + environment["FAKE_FORGE_ENVIRONMENT"] = str(self.forge_environment) + if createx_receipt: + receipt = fake_bin / "run-latest.json" + receipt.write_text( + json.dumps( + { + "transactions": [ + { + "hash": TRANSACTION_HASH, + "contractAddress": CREATE_X_ADDRESS, + "additionalContracts": [ + { + "address": PROXY_ADDRESS, + "contractName": "TransparentUpgradeableProxy", + }, + { + "address": PROXY_ADMIN_ADDRESS, + "contractName": "ProxyAdmin", + }, + ], + } + ], + "receipts": [ + { + "transactionHash": TRANSACTION_HASH, + "blockNumber": "0x2a", + } + ], + } + ) + ) + environment["FAKE_FORGE_RECEIPT"] = str(receipt) + environment["FAKE_FORGE_OUTPUT"] = ( + f"WIZARD_DEPLOYMENT|Production State Oracle Proxy|{PROXY_ADDRESS}" + ) + environment["PATH"] = f"{fake_bin}:{environment['PATH']}" + os.chdir(repository) + os.execvpe("/bin/bash", ["/bin/bash", str(wizard)], environment) + + self.output = "" + self.transcript = "" + self.selector = selectors.DefaultSelector() + self.selector.register(self.master_fd, selectors.EVENT_READ) + + def expect_any(self, *needles, timeout=5): + deadline = time.monotonic() + timeout + while time.monotonic() < deadline: + for needle in needles: + if needle in self.output: + match_end = self.output.index(needle) + len(needle) + self.output = self.output[match_end:] + return needle + + if not self.selector.select(0.1): + continue + try: + chunk = os.read(self.master_fd, 4096) + except OSError as error: + if error.errno == errno.EIO: + break + raise + if not chunk: + break + decoded = chunk.decode(errors="replace") + self.output += decoded + self.transcript += decoded + + self.fail_with_output(f"Timed out waiting for one of: {needles}") + + def send(self, keys): + os.write(self.master_fd, keys) + + def fail_with_output(self, message): + raise AssertionError(f"{message}\nWizard output:\n{self.output}") + + def close(self): + try: + os.kill(self.pid, signal.SIGKILL) + except ProcessLookupError: + pass + self.selector.close() + try: + os.close(self.master_fd) + except OSError: + pass + try: + os.waitpid(self.pid, 0) + except ChildProcessError: + pass + self._temporary_directory.cleanup() + + +class DeployWizardTest(unittest.TestCase): + def _advance_to_confirmation(self, wizard): + wizard.expect_any("What kind of deployment is this?") + wizard.send(b"\r") + wizard.expect_any("Deploy a staging State Oracle as well?") + wizard.send(b"\r") + + wizard.expect_any("Which admin verifiers should be deployed?") + wizard.send(b" \r") + wizard.expect_any("Add Owner to which State Oracle(s)?") + wizard.send(b" \r") + + wizard.expect_any("Which DA verifiers should be deployed?") + wizard.send(b"\x1b[B \r") + wizard.expect_any("Add On-chain bytecode to which State Oracle(s)?") + wizard.send(b" \r") + + wizard.expect_any("Should the State Oracle whitelist be enabled?") + wizard.send(b"\x1b[B\r") + wizard.expect_any("Verify deployed contracts on the block explorer?") + wizard.send(b"\x1b[B\r") + + wizard.expect_any("RPC URL") + wizard.send(b"\r") + wizard.expect_any("Maximum assertions per adopter") + wizard.send(b"\r") + wizard.expect_any("Assertion timelock in blocks") + wizard.send(b"\r") + wizard.expect_any("State Oracle admin") + wizard.send(b"\r") + + wizard.expect_any("Which Foundry wallet should deploy the contracts?") + wizard.send(b"\r") + wizard.expect_any("Password for deployer") + wizard.send(b"password\r") + wizard.expect_any("Ready to continue?") + + def test_script_avoids_bash_four_only_no_color_check(self): + self.assertNotRegex(WIZARD.read_text(), r"\[\[\s+-v\s+NO_COLOR") + subprocess.run( + ["/bin/bash", "-n", str(WIZARD)], + check=True, + capture_output=True, + text=True, + ) + + def test_interactive_output_uses_color(self): + wizard = WizardSession(no_color=False) + try: + wizard.expect_any("Credible Layer deployment wizard") + self.assertIn("\x1b[1m\x1b[36m", wizard.transcript) + finally: + wizard.close() + + def test_assigning_multiple_da_verifiers_to_production_advances_to_whitelist(self): + wizard = WizardSession() + try: + wizard.expect_any("What kind of deployment is this?") + wizard.send(b"\r") + wizard.expect_any("Deploy a staging State Oracle as well?") + wizard.send(b"\r") + + wizard.expect_any("Which admin verifiers should be deployed?") + wizard.send(b" \r") + wizard.expect_any("Add Owner to which State Oracle(s)?") + wizard.send(b" \r") + + wizard.expect_any("Which DA verifiers should be deployed?") + wizard.send(b" \x1b[B \r") + wizard.expect_any("Add ECDSA signatures to which State Oracle(s)?") + wizard.send(b" \r") + wizard.expect_any("Add On-chain bytecode to which State Oracle(s)?") + wizard.send(b" \r") + wizard.expect_any("Should the State Oracle whitelist be enabled?") + finally: + wizard.close() + + def test_zero_balance_wallet_stops_before_forge(self): + wizard = WizardSession() + try: + wizard.expect_any("What kind of deployment is this?") + wizard.send(b"\r") + wizard.expect_any("Deploy a staging State Oracle as well?") + wizard.send(b"\r") + + wizard.expect_any("Which admin verifiers should be deployed?") + wizard.send(b" \r") + wizard.expect_any("Add Owner to which State Oracle(s)?") + wizard.send(b" \r") + + wizard.expect_any("Which DA verifiers should be deployed?") + wizard.send(b"\x1b[B \r") + wizard.expect_any("Add On-chain bytecode to which State Oracle(s)?") + wizard.send(b" \r") + + wizard.expect_any("Should the State Oracle whitelist be enabled?") + wizard.send(b"\x1b[B\r") + wizard.expect_any("Verify deployed contracts on the block explorer?") + wizard.send(b"\x1b[B\r") + + wizard.expect_any("RPC URL") + wizard.send(b"\r") + wizard.expect_any("Maximum assertions per adopter") + wizard.send(b"\r") + wizard.expect_any("Assertion timelock in blocks") + wizard.send(b"\r") + wizard.expect_any("State Oracle admin") + wizard.send(b"\r") + + wizard.expect_any("Which Foundry wallet should deploy the contracts?") + wizard.send(b"\r") + wizard.expect_any("Password for deployer") + wizard.send(b"password\r") + + result = wizard.expect_any( + "has no funds on chain 31337", + "Deployment summary", + ) + if result == "Deployment summary": + self.fail("zero-balance wallet reached the deployment confirmation") + finally: + wizard.close() + + def test_funded_account_sets_forge_rpc_and_sender(self): + wizard = WizardSession( + balance_wei="1000000000000000000", createx_receipt=True + ) + try: + self._advance_to_confirmation(wizard) + self.assertIn("--rpc-url \\", wizard.transcript) + wizard.send(b"\r") + wizard.expect_any("Deployment complete") + wizard.expect_any("Block:") + wizard.expect_any("42") + wizard.expect_any("Transaction:") + wizard.expect_any(TRANSACTION_HASH) + wizard.expect_any("Proxy admin:") + wizard.expect_any(PROXY_ADMIN_ADDRESS) + + forge_arguments = wizard.forge_arguments.read_text().splitlines() + rpc_index = forge_arguments.index("--rpc-url") + self.assertEqual(forge_arguments[rpc_index + 1], "http://rpc.example") + sender_index = forge_arguments.index("--sender") + self.assertEqual( + forge_arguments[sender_index + 1], + "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + ) + finally: + wizard.close() + + def test_empty_wizard_whitelist_clears_ambient_value(self): + wizard = WizardSession( + balance_wei="1000000000000000000", + ambient_whitelist="0x000000000000000000000000000000000000bEEF", + createx_receipt=True, + ) + try: + self._advance_to_confirmation(wizard) + wizard.send(b"\r") + wizard.expect_any("Deployment complete") + + forge_environment = wizard.forge_environment.read_text().splitlines() + self.assertFalse( + any( + entry.startswith("STATE_ORACLE_INITIAL_WHITELIST=") + for entry in forge_environment + ) + ) + finally: + wizard.close() + + +if __name__ == "__main__": + unittest.main() diff --git a/test/utils/CreateXTestDouble.sol b/test/utils/CreateXTestDouble.sol new file mode 100644 index 0000000..6dadb9c --- /dev/null +++ b/test/utils/CreateXTestDouble.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: CC0-1.0 +pragma solidity ^0.8.28; + +contract CreateXTestDouble { + bytes internal constant CREATE3_PROXY_INITCODE = hex"67363d3d37363d34f03d5260086018f3"; + + address[] public deployments; + + function deploymentCount() external view returns (uint256) { + return deployments.length; + } + + function deployCreate3(bytes32 salt, bytes memory initCode) external returns (address deployed) { + require(salt[20] == bytes1(0), "cross-chain protection must be disabled"); + + // The CreateX salt format intentionally stores an address in its first 20 bytes. + // forge-lint: disable-next-line(unsafe-typecast) + bytes32 guardedSalt = address(bytes20(salt)) == msg.sender + ? keccak256(abi.encode(bytes32(uint256(uint160(msg.sender))), salt)) + : keccak256(abi.encode(salt)); + bytes memory proxyInitCode = CREATE3_PROXY_INITCODE; + address proxy; + assembly ("memory-safe") { + proxy := create2(0, add(proxyInitCode, 32), mload(proxyInitCode), guardedSalt) + } + require(proxy != address(0), "CREATE3 proxy deployment failed"); + + deployed = address(uint160(uint256(keccak256(abi.encodePacked(hex"d694", proxy, hex"01"))))); + (bool success,) = proxy.call(initCode); + require(success && deployed.code.length != 0, "CREATE3 deployment failed"); + deployments.push(deployed); + } +}