From dae2bf3b6ca0888806773ac020d766ef75564d61 Mon Sep 17 00:00:00 2001 From: Sora Suegami <31360991+SoraSuegami@users.noreply.github.com> Date: Sun, 3 Nov 2024 23:45:16 +0900 Subject: [PATCH] Feat/body parsing (#45) * Use ether-email-auth for body parsing * Replace "subject" with "command" * Enable test on any branch * chore: update .env.example * Update ether-email-auth * Update ether-email-auth * Update ether-email-auth * Update ether-email-auth * Import ether-email-auth as npm package * Add EmailRecoveryManagerZkSync * Update version * Fix EmailRecoveryManagerZkSync * Fix renaming * Fix remapping * Fix EmailRecoveryManagerZkSync * Fix README to replace "subject" with "command". * fix compile warnings & format * extract more logic to base test & rename variables * remove unnecessary vm.prank * Add more logic to base test & more linting * Remove email-wallet-sdk * Update package version * Add how to debug errors to README * Fix readme * Add a CREATE2_SALT argument to DeploySafeRecovery script * add guardian vote check & recovery hash to weight storage WIP * Improve RecoveryRequest * Fix all unit tests * Feat/script tests (#49) * Remove email-wallet-sdk * Update package version * Update README * Update pnpm commands * Add tests for scripts * Add threads = 1 to foundry.toml * Prevent malicious guardians threatening liveness via cooldown * Uncomment all test assertions & use EnumerableSet * Update processRecovery tests * Add remaining tests from TODOs & add getter fn * Update existing assertions with new state * Add getAllGuardians getter function * Make getter function view * 10 Critical events are not observable * Return false for canStartRecoveryRequest when threshold is zero * Update ether-email-auth * Update package version * 10. EmailRecoveryContract is not compatible with zkSync * clearRecoveryRequest properly in deinit * Update ether-email-auth-contracts * Update version to 0.0.10 * Update natpsec * test-nexus-account * Run tests with different command handlers * Rename * Add fuzz tests for hexToBytes32 unit tests * Add tests for AccountHidingRecoveryCommandHandler * Add safe module unit tests * fuzz test getPreviousOwnerInLinkedList * Add missing low risk functions * Add script for account hiding * Add the section which is how to solve errrors * Use ForwardDKIMRegistry * Remove ForwardDKIMRegistry * Fix failing tests * revert to all github workflows without nexus account * 13 EmailRecoveryManager delay can be set to zero * Fix failing test * Fix lock file * Recover lock file * Change workflow * Fix MockGroth16Verifier * Update depndency * Specify npm package of ether-email-auth * Use salt value for deployment (#55) * Use salt value for deployment * Add minimumDelay * Add CREATE2_SALT value into all deploy scripts * Update depndencies * Fix bug from updating modulekit * Use StringUtils from ether-email-auth * 0.0.12-preview * 0.0.12 * Add kill switch functionality (#58) * Add support section (#57) * update @zk-email/ether-email-auth-contracts * Update scripts * Update scripts * Fix script * Fix script * Add a new script --------- Co-authored-by: Aditya Bisht Co-authored-by: JohnGuilding Co-authored-by: wshino Co-authored-by: John Guilding <54913924+JohnGuilding@users.noreply.github.com> --- .env.example | 14 +- .github/workflows/test.yml | 8 +- .solhint.json | 3 +- .solhintignore | 4 +- .vscode/settings.json | 3 +- README.md | 182 ++-- foundry.toml | 16 +- package.json | 31 +- pnpm-lock.yaml | 790 ++++++++---------- remappings.txt | 2 +- script/BaseDeployScript.s.sol | 61 ++ script/Compute7579RecoveryDataHash.s.sol | 4 +- script/ComputeSafeRecoveryCalldata.s.sol | 4 +- script/Deploy7579TestAccount.s.sol | 66 +- script/DeployEmailRecoveryModule.s.sol | 91 +- script/DeploySafeNativeRecovery.s.sol | 78 +- ...ySafeNativeRecoveryWithAccountHiding.s.sol | 73 ++ script/DeploySafeRecovery.s.sol | 93 ++- .../DeploySafeRecoveryWithAccountHiding.s.sol | 88 ++ .../DeployUniversalEmailRecoveryModule.s.sol | 71 +- script/test/BaseDeployTest.sol | 114 +++ script/test/Compute7579RecoveryDataHash.t.sol | 16 + script/test/DeployEmailRecoveryModule.t.sol | 69 ++ script/test/DeploySafeNativeRecovery.t.sol | 86 ++ script/test/DeploySafeRecovery.t.sol | 36 + .../DeployUniversalEmailRecoveryModule.t.sol | 50 ++ src/EmailRecoveryManager.sol | 330 ++++++-- src/EmailRecoveryManagerZkSync.sol | 94 +++ src/GuardianManager.sol | 42 +- src/factories/EmailRecoveryFactory.sol | 41 +- .../EmailRecoveryUniversalFactory.sol | 47 +- ...> AccountHidingRecoveryCommandHandler.sol} | 90 +- ...er.sol => EmailRecoveryCommandHandler.sol} | 86 +- ...ler.sol => SafeRecoveryCommandHandler.sol} | 89 +- ...r.sol => IEmailRecoveryCommandHandler.sol} | 24 +- src/interfaces/IEmailRecoveryManager.sol | 83 +- src/interfaces/IGuardianManager.sol | 9 +- src/libraries/StringUtils.sol | 42 - src/modules/EmailRecoveryModule.sol | 42 +- src/modules/SafeEmailRecoveryModule.sol | 20 +- src/modules/UniversalEmailRecoveryModule.sol | 45 +- src/test/MockGroth16Verifier.sol | 25 +- src/test/OwnableValidator.sol | 17 +- test/Base.t.sol | 528 ++++++++++++ .../EmailRecoveryManager.integration.t.sol | 380 ++++++--- test/integration/IntegrationBase.t.sol | 117 --- .../EmailRecoveryModule.t.sol | 270 ++++-- .../EmailRecoveryModuleBase.t.sol | 302 ++----- .../UniversalEmailRecoveryModule.t.sol | 283 +++++-- .../UniversalEmailRecoveryModuleBase.t.sol | 299 ++----- .../SafeRecovery/SafeIntegrationBase.t.sol | 193 ++--- .../SafeNativeIntegrationBase.t.sol | 231 ++--- .../SafeRecovery/SafeRecovery.t.sol | 73 +- .../SafeRecoveryNativeModule.t.sol | 120 +-- .../EmailRecoveryManager/acceptGuardian.t.sol | 69 +- ...t.sol => acceptanceCommandTemplates.t.sol} | 9 +- .../cancelExpiredRecovery.t.sol | 302 +++++-- .../EmailRecoveryManager/cancelRecovery.t.sol | 163 ++-- .../clearRecoveryRequest.t.sol | 166 ++++ .../completeRecovery.t.sol | 146 ++-- .../configureRecovery.t.sol | 81 +- .../EmailRecoveryManager/constructor.t.sol | 35 +- .../deInitRecoveryModule.t.sol | 127 ++- .../deInitRecoveryModuleWithAddress.t.sol | 62 +- ...ecoveredAccountFromAcceptanceCommand.t.sol | 36 + ...ecoveredAccountFromAcceptanceSubject.t.sol | 13 - ...tRecoveredAccountFromRecoveryCommand.t.sol | 36 + ...tRecoveredAccountFromRecoverySubject.t.sol | 13 - .../getPreviousRecoveryRequest.t.sol | 48 ++ .../getRecoveryConfig.t.sol | 14 +- .../getRecoveryRequest.t.sol | 31 +- .../hasGuardianVoted.t.sol | 51 ++ .../EmailRecoveryManager/isActivated.t.sol | 11 +- .../processRecovery.t.sol | 402 +++++++-- ...s.t.sol => recoveryCommandTemplates.t.sol} | 9 +- .../toggleKillSwitch.t.sol | 38 + .../updateRecoveryConfig.t.sol | 187 ++++- test/unit/EmailRecoveryModuleHarness.sol | 17 +- test/unit/GuardianManager/addGuardian.t.sol | 53 +- .../GuardianManager/changeThreshold.t.sol | 39 +- .../GuardianManager/getAllGuardians.t.sol | 18 + test/unit/GuardianManager/getGuardian.t.sol | 11 +- .../GuardianManager/getGuardianConfig.t.sol | 22 +- .../GuardianManager/removeAllGuardians.t.sol | 21 +- .../unit/GuardianManager/removeGuardian.t.sol | 62 +- .../unit/GuardianManager/setupGuardians.t.sol | 51 +- .../updateGuardianStatus.t.sol | 31 +- test/unit/SafeEmailRecoveryModuleHarness.sol | 28 + ... => SafeRecoveryCommandHandlerHarness.sol} | 7 +- test/unit/SafeUnitBase.t.sol | 161 +--- test/unit/UnitBase.t.sol | 298 ++----- .../UniversalEmailRecoveryModuleHarness.sol | 35 +- test/unit/assertErrorSelectors.t.sol | 63 +- .../EmailRecoveryFactory/constructor.t.sol | 1 - .../deployEmailRecoveryModule.t.sol | 29 +- .../constructor.t.sol | 1 - .../deployUniversalEmailRecoveryModule.t.sol | 31 +- .../acceptanceCommandTemplates.t.sol | 28 + ...ecoveredAccountFromAcceptanceCommand.t.sol | 63 ++ ...tRecoveredAccountFromRecoveryCommand.t.sol | 68 ++ .../parseRecoveryCalldataHash.t.sol | 50 ++ .../recoveryCommandTemplates.t.sol | 29 + .../storeAccountHash.t.sol | 69 ++ .../validateAcceptanceCommand.t.sol | 86 ++ .../validateRecoveryCommand.t.sol | 107 +++ .../acceptanceCommandTemplates.t.sol} | 11 +- ...ecoveredAccountFromAcceptanceCommand.t.sol | 25 + ...tRecoveredAccountFromRecoveryCommand.t.sol | 29 + .../parseRecoveryCalldataHash.t.sol | 43 + .../recoveryCommandTemplates.t.sol} | 11 +- .../validateAcceptanceCommand.t.sol | 62 ++ .../validateRecoveryCommand.t.sol | 91 ++ ...ecoveredAccountFromAcceptanceSubject.t.sol | 23 - ...tRecoveredAccountFromRecoverySubject.t.sol | 28 - .../parseRecoveryCalldataHash.t.sol | 43 - .../validateAcceptanceSubject.t.sol | 59 -- .../validateRecoverySubject.t.sol | 91 -- .../acceptanceCommandTemplates.t.sol} | 12 +- ...ecoveredAccountFromAcceptanceCommand.t.sol | 26 + ...tRecoveredAccountFromRecoveryCommand.t.sol | 28 + .../fuzz/getPreviousOwnerInLinkedList.t.sol | 58 ++ .../getPreviousOwnerInLinkedList.t.sol | 19 +- .../parseRecoveryCalldataHash.t.sol | 42 + .../recoveryCommandTemplates.t.sol} | 13 +- .../validateAcceptanceCommand.t.sol | 66 ++ .../validateRecoveryCommand.t.sol | 113 +++ ...ecoveredAccountFromAcceptanceSubject.t.sol | 24 - ...tRecoveredAccountFromRecoverySubject.t.sol | 25 - .../parseRecoveryCalldataHash.t.sol | 40 - .../validateAcceptanceSubject.t.sol | 64 -- .../validateRecoverySubject.t.sol | 103 --- .../EnumerableGuardianMap/fuzz/remove.t.sol | 191 +++++ .../EnumerableGuardianMap/fuzz/set.t.sol | 129 +++ .../libraries/EnumerableGuardianMap/get.t.sol | 11 +- .../EnumerableGuardianMap/keys.t.sol | 11 +- .../EnumerableGuardianMap/remove.t.sol | 63 +- .../EnumerableGuardianMap/removeAll.t.sol | 17 +- .../libraries/EnumerableGuardianMap/set.t.sol | 55 +- .../EmailRecoveryModuleBase.t.sol | 290 ++----- .../canStartRecoveryRequest.t.sol | 64 +- .../EmailRecoveryModule/constructor.t.sol | 56 +- .../EmailRecoveryModule/isInitialized.t.sol | 30 +- .../EmailRecoveryModule/isModuleType.t.sol | 24 +- .../modules/EmailRecoveryModule/name.t.sol | 7 +- .../EmailRecoveryModule/onInstall.t.sol | 25 +- .../EmailRecoveryModule/onUninstall.t.sol | 9 +- .../modules/EmailRecoveryModule/recover.t.sol | 52 +- .../modules/EmailRecoveryModule/version.t.sol | 7 +- .../canStartRecoveryRequest.t.sol | 113 +++ .../configureSafeRecovery.t.sol | 7 +- .../SafeEmailRecoveryModule/constructor.t.sol | 25 + .../SafeEmailRecoveryModule/recover.t.sol | 111 +++ .../resetWhenDisabled.t.sol | 94 +++ .../allowValidatorRecovery.t.sol | 96 +-- .../canStartRecoveryRequest.t.sol | 67 +- .../constructor.t.sol | 7 +- .../disallowValidatorRecovery.t.sol | 62 +- .../getAllowedSelectors.t.sol | 13 +- .../getAllowedValidators.t.sol | 19 +- .../isInitialized.t.sol | 39 +- .../isModuleType.t.sol | 11 +- .../UniversalEmailRecoveryModule/name.t.sol | 7 +- .../onInstall.t.sol | 21 +- .../onUninstall.t.sol | 29 +- .../recover.t.sol | 74 +- .../version.t.sol | 7 +- 166 files changed, 8061 insertions(+), 4434 deletions(-) create mode 100644 script/BaseDeployScript.s.sol create mode 100644 script/DeploySafeNativeRecoveryWithAccountHiding.s.sol create mode 100644 script/DeploySafeRecoveryWithAccountHiding.s.sol create mode 100644 script/test/BaseDeployTest.sol create mode 100644 script/test/Compute7579RecoveryDataHash.t.sol create mode 100644 script/test/DeployEmailRecoveryModule.t.sol create mode 100644 script/test/DeploySafeNativeRecovery.t.sol create mode 100644 script/test/DeploySafeRecovery.t.sol create mode 100644 script/test/DeployUniversalEmailRecoveryModule.t.sol create mode 100644 src/EmailRecoveryManagerZkSync.sol rename src/handlers/{AccountHidingRecoverySubjectHandler.sol => AccountHidingRecoveryCommandHandler.sol} (63%) rename src/handlers/{EmailRecoverySubjectHandler.sol => EmailRecoveryCommandHandler.sol} (60%) rename src/handlers/{SafeRecoverySubjectHandler.sol => SafeRecoveryCommandHandler.sol} (71%) rename src/interfaces/{IEmailRecoverySubjectHandler.sol => IEmailRecoveryCommandHandler.sol} (52%) delete mode 100644 src/libraries/StringUtils.sol create mode 100644 test/Base.t.sol delete mode 100644 test/integration/IntegrationBase.t.sol rename test/unit/EmailRecoveryManager/{acceptanceSubjectTemplates.t.sol => acceptanceCommandTemplates.t.sol} (65%) create mode 100644 test/unit/EmailRecoveryManager/clearRecoveryRequest.t.sol create mode 100644 test/unit/EmailRecoveryManager/extractRecoveredAccountFromAcceptanceCommand.t.sol delete mode 100644 test/unit/EmailRecoveryManager/extractRecoveredAccountFromAcceptanceSubject.t.sol create mode 100644 test/unit/EmailRecoveryManager/extractRecoveredAccountFromRecoveryCommand.t.sol delete mode 100644 test/unit/EmailRecoveryManager/extractRecoveredAccountFromRecoverySubject.t.sol create mode 100644 test/unit/EmailRecoveryManager/getPreviousRecoveryRequest.t.sol create mode 100644 test/unit/EmailRecoveryManager/hasGuardianVoted.t.sol rename test/unit/EmailRecoveryManager/{recoverySubjectTemplates.t.sol => recoveryCommandTemplates.t.sol} (69%) create mode 100644 test/unit/EmailRecoveryManager/toggleKillSwitch.t.sol create mode 100644 test/unit/GuardianManager/getAllGuardians.t.sol create mode 100644 test/unit/SafeEmailRecoveryModuleHarness.sol rename test/unit/{SafeRecoverySubjectHandlerHarness.sol => SafeRecoveryCommandHandlerHarness.sol} (52%) create mode 100644 test/unit/handlers/AccountHidingRecoveryCommandHandler/acceptanceCommandTemplates.t.sol create mode 100644 test/unit/handlers/AccountHidingRecoveryCommandHandler/extractRecoveredAccountFromAcceptanceCommand.t.sol create mode 100644 test/unit/handlers/AccountHidingRecoveryCommandHandler/extractRecoveredAccountFromRecoveryCommand.t.sol create mode 100644 test/unit/handlers/AccountHidingRecoveryCommandHandler/parseRecoveryCalldataHash.t.sol create mode 100644 test/unit/handlers/AccountHidingRecoveryCommandHandler/recoveryCommandTemplates.t.sol create mode 100644 test/unit/handlers/AccountHidingRecoveryCommandHandler/storeAccountHash.t.sol create mode 100644 test/unit/handlers/AccountHidingRecoveryCommandHandler/validateAcceptanceCommand.t.sol create mode 100644 test/unit/handlers/AccountHidingRecoveryCommandHandler/validateRecoveryCommand.t.sol rename test/unit/handlers/{EmailRecoverySubjectHandler/acceptanceSubjectTemplates.t.sol => EmailRecoveryCommandHandler/acceptanceCommandTemplates.t.sol} (53%) create mode 100644 test/unit/handlers/EmailRecoveryCommandHandler/extractRecoveredAccountFromAcceptanceCommand.t.sol create mode 100644 test/unit/handlers/EmailRecoveryCommandHandler/extractRecoveredAccountFromRecoveryCommand.t.sol create mode 100644 test/unit/handlers/EmailRecoveryCommandHandler/parseRecoveryCalldataHash.t.sol rename test/unit/handlers/{EmailRecoverySubjectHandler/recoverySubjectTemplates.t.sol => EmailRecoveryCommandHandler/recoveryCommandTemplates.t.sol} (57%) create mode 100644 test/unit/handlers/EmailRecoveryCommandHandler/validateAcceptanceCommand.t.sol create mode 100644 test/unit/handlers/EmailRecoveryCommandHandler/validateRecoveryCommand.t.sol delete mode 100644 test/unit/handlers/EmailRecoverySubjectHandler/extractRecoveredAccountFromAcceptanceSubject.t.sol delete mode 100644 test/unit/handlers/EmailRecoverySubjectHandler/extractRecoveredAccountFromRecoverySubject.t.sol delete mode 100644 test/unit/handlers/EmailRecoverySubjectHandler/parseRecoveryCalldataHash.t.sol delete mode 100644 test/unit/handlers/EmailRecoverySubjectHandler/validateAcceptanceSubject.t.sol delete mode 100644 test/unit/handlers/EmailRecoverySubjectHandler/validateRecoverySubject.t.sol rename test/unit/handlers/{SafeRecoverySubjectHandler/acceptanceSubjectTemplates.t.sol => SafeRecoveryCommandHandler/acceptanceCommandTemplates.t.sol} (55%) create mode 100644 test/unit/handlers/SafeRecoveryCommandHandler/extractRecoveredAccountFromAcceptanceCommand.t.sol create mode 100644 test/unit/handlers/SafeRecoveryCommandHandler/extractRecoveredAccountFromRecoveryCommand.t.sol create mode 100644 test/unit/handlers/SafeRecoveryCommandHandler/fuzz/getPreviousOwnerInLinkedList.t.sol rename test/unit/handlers/{SafeRecoverySubjectHandler => SafeRecoveryCommandHandler}/getPreviousOwnerInLinkedList.t.sol (72%) create mode 100644 test/unit/handlers/SafeRecoveryCommandHandler/parseRecoveryCalldataHash.t.sol rename test/unit/handlers/{SafeRecoverySubjectHandler/recoverySubjectTemplates.t.sol => SafeRecoveryCommandHandler/recoveryCommandTemplates.t.sol} (58%) create mode 100644 test/unit/handlers/SafeRecoveryCommandHandler/validateAcceptanceCommand.t.sol create mode 100644 test/unit/handlers/SafeRecoveryCommandHandler/validateRecoveryCommand.t.sol delete mode 100644 test/unit/handlers/SafeRecoverySubjectHandler/extractRecoveredAccountFromAcceptanceSubject.t.sol delete mode 100644 test/unit/handlers/SafeRecoverySubjectHandler/extractRecoveredAccountFromRecoverySubject.t.sol delete mode 100644 test/unit/handlers/SafeRecoverySubjectHandler/parseRecoveryCalldataHash.t.sol delete mode 100644 test/unit/handlers/SafeRecoverySubjectHandler/validateAcceptanceSubject.t.sol delete mode 100644 test/unit/handlers/SafeRecoverySubjectHandler/validateRecoverySubject.t.sol create mode 100644 test/unit/libraries/EnumerableGuardianMap/fuzz/remove.t.sol create mode 100644 test/unit/libraries/EnumerableGuardianMap/fuzz/set.t.sol create mode 100644 test/unit/modules/SafeEmailRecoveryModule/canStartRecoveryRequest.t.sol create mode 100644 test/unit/modules/SafeEmailRecoveryModule/constructor.t.sol create mode 100644 test/unit/modules/SafeEmailRecoveryModule/recover.t.sol create mode 100644 test/unit/modules/SafeEmailRecoveryModule/resetWhenDisabled.t.sol diff --git a/.env.example b/.env.example index 03b8f8e7..52ca70ec 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,11 @@ -BASE_SEPOLIA_RPC_URL= -PRIVATE_KEY= -BASE_SCAN_API_KEY= +LOCALHOST_RPC_URL= +SEPOLIA_RPC_URL= +MAINNET_RPC_URL= + +PRIVATE_KEY="" +CHAIN_ID= +RPC_URL="" +DKIM_SIGNER=0x6293a80bf4bd3fff995a0cab74cbf281d922da02 # Signer for the dkim oracle on IC +ETHERSCAN_API_KEY= + +CREATE2_SALT=1234 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 99238da5..a36731b5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,8 +2,6 @@ name: test on: push: - branches: - - main pull_request: env: @@ -11,11 +9,11 @@ env: jobs: build: - uses: "rhinestonewtf/reusable-workflows/.github/workflows/forge-build.yaml@15f6d25eb382057ce7c513de7f38f4b6bca49f1d" + uses: "rhinestonewtf/reusable-workflows/.github/workflows/forge-build.yaml@main" test-multi-account: needs: ["build"] - uses: "rhinestonewtf/reusable-workflows/.github/workflows/forge-test-multi-account.yaml@15f6d25eb382057ce7c513de7f38f4b6bca49f1d" + uses: "rhinestonewtf/reusable-workflows/.github/workflows/forge-test-multi-account.yaml@main" with: foundry-fuzz-runs: 5000 foundry-profile: "test" @@ -23,7 +21,7 @@ jobs: test-simulate: needs: ["build"] - uses: "rhinestonewtf/reusable-workflows/.github/workflows/forge-test-simulate.yaml@15f6d25eb382057ce7c513de7f38f4b6bca49f1d" + uses: "rhinestonewtf/reusable-workflows/.github/workflows/forge-test-simulate.yaml@main" with: foundry-fuzz-runs: 5000 diff --git a/.solhint.json b/.solhint.json index 15c2326a..37390f29 100644 --- a/.solhint.json +++ b/.solhint.json @@ -14,6 +14,7 @@ "not-rely-on-time": "off", "one-contract-per-file": "off", "var-name-mixedcase": "off", - "immutable-vars-naming": "off" + "immutable-vars-naming": "off", + "no-console": "warn" } } diff --git a/.solhintignore b/.solhintignore index 8250d100..40b878db 100644 --- a/.solhintignore +++ b/.solhintignore @@ -1,3 +1 @@ -node_modules/ -test/ -script/ \ No newline at end of file +node_modules/ \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 0feca580..4c0c8dfc 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,5 @@ { "solidity.compileUsingRemoteVersion": "v0.8.25+commit.b61c2a91", - "solidity.formatter": "forge" + "solidity.formatter": "forge", + "solidity.packageDefaultDependenciesDirectory": "node_modules" } diff --git a/README.md b/README.md index 99258102..9304f8c7 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,12 @@ ## ZK Email Recovery ## Overview + Account recovery has traditionally been one of the most complex UX hurdles that account holders have to contend with. The ZK Email Recovery contracts provide a robust and simple mechanism for account holders to recover modular accounts via email guardians. Modular accounts get account recovery for 'free' by using our pre-deployed universal email recovery module. Since Safe's can be made 7579 compatible, the following contracts also support Safe account recovery out of the box too. For older existing Safes that are not compatible with ERC4337 (and subsequenty ERC7579), we provide a native Safe module too. -Modular account developers can easily integrate email recovery with richer and more specific email subjects by writing their own subject handler contracts, which are designed to be simple and contain the modular account-specific logic to recover an account. +Modular account developers can easily integrate email recovery with richer and more specific commands in the email body by writing their own command handler contracts, which are designed to be simple and contain the modular account-specific logic to recover an account. ## Usage @@ -18,36 +19,49 @@ pnpm install ### Build ```shell -forge build +pnpm build +# or +# forge build ``` ### Test ```shell -forge test +pnpm test +# or +# forge test --match-path "test/**/*.sol" or +``` + +### Test for scripts + +```shell +pnpm test:script +# or +# forge test --match-path "script/test/**/*.sol" --threads 1 ``` # ZK Email Recovery ## High level contracts diagram (TODO: (merge-ok) out of date - needs updating to show module and manager are single contract) + Note: `EmailAccountRecovery.sol` & `EmailAuth.sol` can be found in the [ether-email-auth](https://github.com/zkemail/ether-email-auth) repo - ### EmailRecoveryManager.sol `EmailRecoveryManager.sol` is an abstract contract that defines the main logic for email-based recovery. It is designed to provide the core logic for email based account recovery that can be used across different modular account implementations. For the end user, the core `EmailRecoveryManager` contract aims to provide a robust and simple mechanism to recover accounts via email guardians. -It inherits from a ZK Email contract called `EmailAccountRecovery.sol` which defines some basic recovery logic that interacts with lower level ZK Email contracts. `EmailAccountRecovery.sol` holds the logic that interacts with the lower level ZK Email contracts `EmailAuth.sol`, verifier, dkim registry etc. More info on the underlying `EmailAccountRecovery.sol` contract can be found [here](https://github.com/zkemail/ether-email-auth/tree/main/packages/contracts#emailaccountrecovery-contract). +It inherits from a ZK Email contract called `EmailAccountRecovery.sol` which defines some basic recovery logic that interacts with lower level ZK Email contracts. `EmailAccountRecovery.sol` holds the logic that interacts with the lower level ZK Email contracts `EmailAuth.sol`, verifier, dkim registry etc. More info on the underlying `EmailAccountRecovery.sol` contract can be found [here](https://github.com/zkemail/ether-email-auth/tree/main/packages/contracts#emailaccountrecovery-contract). The guardians are represented onchain by `EmailAuth.sol` instances. `EmailAuth.sol` is designed to authenticate that a user is a correct holder of the specific email address and authorize anything described in the email. The guardians privacy is protected onchain, for more info on ZK Email privacy and EmailAuth - see the [ZK Email docs](https://zkemail.gitbook.io/zk-email). -`EmailRecoveryManager` relies on a dedicated recovery module to execute a recovery attempt - the recovery module inherits from the email recovery manager contract. The `EmailRecoveryManager` contract defines "what a valid recovery attempt is for an account", and the recovery module defines “how that recovery attempt is executed on the account”. One motivation for having the 7579 recovery module and the core `EmailRecoveryManager` contract being seperated is to allow the core recovery logic to be used across different account implementations and module standards. The core `EmailRecoveryManager.sol` contract is designed to be account implementation agnostic. For example, we have a native Safe module as well as two 7579 modules that use the same underlying manager. It's functionality can be extended by creating new subject handler contracts such as `EmailRecoverySubjectHandler.sol`. +`EmailRecoveryManager` relies on a dedicated recovery module to execute a recovery attempt - the recovery module inherits from the email recovery manager contract. The `EmailRecoveryManager` contract defines "what a valid recovery attempt is for an account", and the recovery module defines “how that recovery attempt is executed on the account”. One motivation for having the 7579 recovery module and the core `EmailRecoveryManager` contract being seperated is to allow the core recovery logic to be used across different account implementations and module standards. The core `EmailRecoveryManager.sol` contract is designed to be account implementation agnostic. For example, we have a native Safe module as well as two 7579 modules that use the same underlying manager. It's functionality can be extended by creating new command handler contracts such as `EmailRecoveryCommandHandler.sol`. ## EmailRecoveryManager flow walkthrough The core functions that must be called in the end-to-end flow for recovery are + 1. configureRecovery (does not need to be called again for subsequent recovery attempts) 2. handleAcceptance - called for each guardian. Defined on `EmailAccountRecovery.sol`, calls acceptGuardian in this contract 3. handleRecovery - called for each guardian. Defined on `EmailAccountRecovery.sol`, calls processRecovery in this contract @@ -56,6 +70,7 @@ The core functions that must be called in the end-to-end flow for recovery are Before proceeding, ensure that you deploy the email recovery contracts via one of the email recovery factories. ### Configure Recovery + After deployment, this is the first core function in the recovery flow, setting up the recovery module, guardians, guardian weights, threshold, and delay/expiry. It only needs to be called once. The threshold must be set appropriately to balance security and usability. The same goes for the delay and expiry - there is a minimum recovery window time that protects against an account giving itself a prohibitively small window in which to complete a recovery attempt. `configureRecovery` is called during the installation phase of the recovery module. This ensures that a user cannot forget to install the recovery module, go to configure recovery, and end up with a broken recovery config. @@ -71,125 +86,133 @@ function configureRecovery( ``` ### Handle Acceptance + This function handles the acceptance of each guardian. Each guardian must accept their role to be a part of the recovery process. This is an important step as it ensures that the guardian consents to the responsibility of being a guardian for a specific account, is in control of the specific email address, and protects against typos from entering the wrong email. Such a typo would render the guardian unusable. `handleAcceptance` must be called for each guardian until the threshold is reached. ```ts function handleAcceptance( address guardian, uint256 templateIdx, - bytes[] memory subjectParams, + bytes[] memory commandParams, bytes32 ) internal; ``` ### Handle Recovery + This function processes each guardian's recovery request. A guardian can initiate a recovery request by replying to an email. The contract verifies the guardian's status and checks if the threshold is met. Once the threshold is met and the delay has passed, anyone can complete the recovery process. The recovery delay is a security feature that gives the wallet owner time to react to a recovery attempt in case of a malicious guardian or guardians. This is possible from guardians who act maliciously, but also from attackers who have access to a guardians email address. Although since guardian email privacy is preserved on chain, this reduces the attack surface further since someone with access to a someone elses email account would not automatically know if the email address is used in a recovery setup, or if they did, which account to target. They could find this information out by searching for recovery setup in the email inbox however. There is also an expiry time, which once expires, invalidates the recovery attempt. This encourages timely execution of recovery attempts and reduces the attack surface that could result from recovery attempts that have been stagnent and uncompleted for long periods of time. ```ts function handleRecovery( address guardian, uint256 templateIdx, - bytes[] memory subjectParams, + bytes[] memory commandParams, bytes32 ) internal; ``` ### Complete Recovery + The final function to complete the recovery process. This function completes the recovery process by validating the recovery request and triggering the recovery module to perform the recovery on the account itself. ```ts function completeRecovery(address account, bytes memory recoveryCalldata) public; ``` -## Subject Handlers +## Command Handlers + +Command handlers define the commands for recovery emails and how they should be validated. They are designed to be simple and self-contained contracts that hold the modular account-specific logic needed for adding email recovery. We currently define three command handlers. We have a universal command handler which essentially gives 7579 module developers recovery for free that is generic to any validator (so long as the validator has functionality to recover itself). We provide a Safe account command handler which provides email account recovery for Safe's. The third is an address hiding email command handler which offers offchain privacy protection for accounts from guardians. -Subject handlers define the subjects for recovery emails and how they should be validated. They are designed to be simple and self-contained contracts that hold the modular account-specific logic needed for adding email recovery. We currently define three subject handlers. We have a universal subject handler which essentially gives 7579 module developers recovery for free that is generic to any validator (so long as the validator has functionality to recover itself). We provide a Safe account subject handler which provides email account recovery for Safe's. The third is an address hiding email subject handler which offers offchain privacy protection for accounts from guardians. +Command handlers contain functions for defining the commands in the email body, of which there are two command types - acceptance and recovery commands. The acceptance command is for the email that is displayed to the guardian when they have to accept becoming a guardian for an account. The recovery command is for the email displayed to the guardian when an account is actually being recovered. Handlers also contain helper functions to extract the account address from both command types. The acceptance and recovery templates can be written to contain any functional info, but they must contain the account address. The command is an important part of the functional info that is used to generate and verify the zkp. -Subject handlers contain functions for defining the email subjects, of which there are two subject types - acceptance and recovery subjects. The acceptance subject is for the email that is displayed to the guardian when they have to accept becoming a guardian for an account. The recovery subject is for the email displayed to the guardian when an account is actually being recovered. Handlers also contain helper functions to extract the account address from both subject types. The acceptance and recovery templates can be written to contain any functional info, but they must contain the account address. The subject is an important part of the functional info that is used to generate and verify the zkp. +Once a new command has been written and audited, the deployment bytecode of the command handler can be passed into one of the provided factories, which ensures that the deployment of a module, manager and command handler are tightly coupled. The deployment of these contracts can be attested to via the use of an [ERC-7484 resistry](https://eips.ethereum.org/EIPS/eip-7484). -Once a new subject has been written and audited, the deployment bytecode of the subject handler can be passed into one of the provided factories, which ensures that the deployment of a module, manager and subject handler are tightly coupled. The deployment of these contracts can be attested to via the use of an [ERC-7484 resistry](https://eips.ethereum.org/EIPS/eip-7484). +### Why write your own command handler? -### Why write your own subject handler? -The generic subject handler supported out of the box is sufficiently generic for recovering any modular account via the use of a recovery hash in the email subject, which is validated against when executing recovery. A modular account developer may want to provide a more specifc and human readable subject handler for their users. It's also possible to write a subject template in a non-english language to support non-english speakers. +The generic command handler supported out of the box is sufficiently generic for recovering any modular account via the use of a recovery hash in the command, which is validated against when executing recovery. A modular account developer may want to provide a more specifc and human readable command handler for their users. It's also possible to write a command template in a non-english language to support non-english speakers. It is important to re-iterate that modular accounts already get account recovery out of the box with these contracts, via the use of the unviversal email recovery module. -### EmailRecoverySubjectHandler.sol -`EmailRecoverySubjectHandler` is a generic subject handler that can be used to recovery any validator. +### EmailRecoveryCommandHandler.sol + +`EmailRecoveryCommandHandler` is a generic command handler that can be used to recovery any validator. -The acceptance subject template is: +The acceptance command template is: `Accept guardian request for {ethAddr}` -The recovery subject template is: +The recovery command template is: `Recover account {ethAddr} using recovery hash {string}` -### SafeRecoverySubjectHandler.sol +### SafeRecoveryCommandHandler.sol -`SafeRecoverySubjectHandler` is a specific subject handler that can be used to recover a Safe. It provides a good example of how to write a custom subject handler for a different account implementation. In contrast to the `EmailRecoverySubjectHandler`, the Safe subject requires additional info in order to complete the recovery request. - -The acceptance subject remains the same as the acceptance subject is already quite generic. This will be a common scenario where only the recovery subject-related functions will need changing. A scenario in which you would definitely need to update both is if you wanted to provide email recovery functionality to users who didn't speak English. In which case you could translate the required subjects into the chosen language. +`SafeRecoveryCommandHandler` is a specific command handler that can be used to recover a Safe. It provides a good example of how to write a custom command handler for a different account implementation. In contrast to the `EmailRecoveryCommandHandler`, the Safe command requires additional info in order to complete the recovery request. -The acceptance subject template is: +The acceptance command remains the same as the acceptance command is already quite generic. This will be a common scenario where only the recovery command-related functions will need changing. A scenario in which you would definitely need to update both is if you wanted to provide email recovery functionality to users who didn't speak English. In which case you could translate the required commands into the chosen language. + +The acceptance command template is: `Accept guardian request for {ethAddr}` -The recovery subject template is: +The recovery command template is: `Recover account {ethAddr} from old owner {ethAddr} to new owner {ethAddr}` -### AccountHidingRecoverySubjectHandler.sol +### AccountHidingRecoveryCommandHandler.sol -`AccountHidingRecoverySubjectHandler` is a specific subject handler that can be used to recover a Safe. This subject handler hashes the account address so that it is not revealed in recovery emails to guardians. This is important if an account does not want to easily reveal it's financial history to guardians. This handler is similar to the generic subject handler except the account address is replaced by a hash. +`AccountHidingRecoveryCommandHandler` is a specific command handler that can be used to recover a Safe. This command handler hashes the account address so that it is not revealed in recovery emails to guardians. This is important if an account does not want to easily reveal it's financial history to guardians. This handler is similar to the generic command handler except the account address is replaced by a hash. -The acceptance subject template is: +The acceptance command template is: `Accept guardian request for {string}` -The recovery subject template is: +The recovery command template is: `Recover account {string} using recovery hash {string}` -## How you can write a custom subject template +## How you can write a custom command template When you know what recovery specific information you need, you can create a new handler contract. The following functions must be implemented: -* `acceptanceSubjectTemplates()` -* `extractRecoveredAccountFromAcceptanceSubject(bytes[],uint256)` -* `validateAcceptanceSubject(uint256,bytes[])` +- `acceptanceCommandTemplates()` +- `extractRecoveredAccountFromAcceptanceCommand(bytes[],uint256)` +- `validateAcceptanceCommand(uint256,bytes[])` -* `recoverySubjectTemplates()` -* `extractRecoveredAccountFromRecoverySubject(bytes[],uint256)` -* `validateRecoverySubject(uint256,bytes[])` +- `recoveryCommandTemplates()` +- `extractRecoveredAccountFromRecoveryCommand(bytes[],uint256)` +- `validateRecoveryCommand(uint256,bytes[])` -* `parseRecoveryDataHash(uint256,bytes[])` +- `parseRecoveryDataHash(uint256,bytes[])` +### How a command is interpreted -### How a subject is interpreted +With an email command of: -With an email subject of: ```bash Recover account 0x50Bc6f1F08ff752F7F5d687F35a0fA25Ab20EF52 to new owner 0x7240b687730BE024bcfD084621f794C2e4F8408f ``` -Where the first address in the subject is the account address recovery is being executed for, and the second is the new owner address. This subject would lead to the the following subject params +Where the first address in the command is the account address recovery is being executed for, and the second is the new owner address. This command would lead to the the following command params -The subject params would be: +The command params would be: ```ts -bytes[] memory subjectParamsForRecovery = new bytes[](2); -subjectParamsForRecovery[0] = abi.encode(accountAddress); -subjectParamsForRecovery[1] = abi.encode(newOwner); +bytes[] memory commandParamsForRecovery = new bytes[](2); +commandParamsForRecovery[0] = abi.encode(accountAddress); +commandParamsForRecovery[1] = abi.encode(newOwner); ``` -### What can I add to a subject template? -A subject template defines the expected format of the message in the subject for each recovery implementation. The underlying ZK Email contracts are generic for any subject, negating some type and size constraints, so developers can write application-specific messages without writing new zk circuits. The use of different subject templates in this case allows for a flexible and extensible mechanism to define recovery messages, making it adaptable to different modular account implemtations. For recovery subjects using these contracts, the email subjects can be completely generic, but they **must** return the account address for which recovery is for. +### What can I add to a command template? + +A command template defines the expected format of the message in the command for each recovery implementation. The underlying ZK Email contracts are generic for any command, negating some type and size constraints, so developers can write application-specific messages without writing new zk circuits. The use of different command templates in this case allows for a flexible and extensible mechanism to define recovery messages, making it adaptable to different modular account implemtations. For recovery commands using these contracts, the email commands can be completely generic, but they **must** return the account address for which recovery is for. -The subject template is an array of strings, each of which has some fixed strings without space and the following variable parts. Subject variables must meet the following type constraints: -- `"{string}"`: a string. Its Solidity type is `string`. The subject string type can be used to add the solidity `bytes` type to email subjects. +The command template is an array of strings, each of which has some fixed strings without space and the following variable parts. Command variables must meet the following type constraints: + +- `"{string}"`: a string. Its Solidity type is `string`. The command string type can be used to add the solidity `bytes` type to commands. - `"{uint}"`: a decimal string of the unsigned integer. Its Solidity type is `uint256`. - `"{int}"`: a decimal string of the signed integer. Its Solidity type is `int256`. - `"{decimals}"`: a decimal string of the decimals. Its Solidity type is `uint256`. Its decimal size is fixed to 18. E.g., “2.7” ⇒ `abi.encode(2.7 * (10**18))`. - `"{ethAddr}"`: a hex string of the Ethereum address. Its Solidity type is `address`. Its value MUST satisfy the checksum of the Ethereum address. -If you are recovering an account that needs to rotate a public key which is of type `bytes` in solidity, you can use the string type for that for the subject template. To read more about the underlying ZK Email contracts that this repo uses, take a look at the [ether-email-auth](https://github.com/zkemail/ether-email-auth) repo. +If you are recovering an account that needs to rotate a public key which is of type `bytes` in solidity, you can use the string type for that for the command template. To read more about the underlying ZK Email contracts that this repo uses, take a look at the [ether-email-auth](https://github.com/zkemail/ether-email-auth) repo. ### EmailRecoveryModule.sol + A recovery module that recovers a specific validator. The target validator and target selector are passed into the module when it is deployed. This means that the module is less generic, but the module is simpler and provides less room for error when confiuring recovery. This is because the module does not have to handle permissioning multiple validators and there is less room for configuration error when installing the module, as the target validator and selector are passed in at deployment instead. @@ -198,11 +221,12 @@ The `recover()` function on the module is the key entry point where recovery is `completeRecovery()` calls `recover()` which calls executeFromExecutor to execute the account specific recovery logic. The call from the executor retains the context of the account so the `msg.sender` of the next call is the account itself. This simplifies access control in the validator being recovered as it can just do a `msg.sender` check. -When writing a custom subject handler, an account developer would likely chose to deploy a `EmailRecoveryModule` instance rather than a `UniversalEmailRecoveryModule` instance. This is because a custom subject handler would likely be specific to an validator implementation, so using the recovery module for specific validators is more appropriate than the generic recovery module. +When writing a custom command handler, an account developer would likely chose to deploy a `EmailRecoveryModule` instance rather than a `UniversalEmailRecoveryModule` instance. This is because a custom command handler would likely be specific to an validator implementation, so using the recovery module for specific validators is more appropriate than the generic recovery module. **Note:** This module is an executor and does not abide by the 4337 validation rules. The `onInstall` function breaks the validation rules and it is possible for it to be called during account deployment in the first userOp. So you cannot install this module during account deployment as onInstall will be called as part of the validation phase. Supporting executor initialization during account deployment is not mandated by ERC7579 - if required, install this module after the account has been setup. ### UniversalEmailRecoveryModule.sol + A recovery module that recovers any validator. The target validator and target selector are passed into the module when it is installed. This means that the module is generic and can be used to recover any 7579 validator. The module is slightly more complex as it has to handle permissioning multiple validators. Additionally there is a slightly higher chance of configuration error when installing the module as the target validator and selector are passed in at this stage instead of when the module is deployed. @@ -214,6 +238,7 @@ The `recover()` function on the module is the key entry point where recovery is **Note:** This module is an executor and does not abide by the 4337 validation rules. The `onInstall` function breaks the validation rules and it is possible for it to be called during account deployment in the first userOp. So you cannot install this module during account deployment as `onInstall` will be called as part of the validation phase. Supporting executor initialization during account deployment is not mandated by ERC7579 - if required, install this module after the account has been setup. ### SafeEmailRecoveryModule.sol + A recovery module that specifically recovers a Safe. The target selector is hardcoded as a constant variable, as the recovery function is stable. As with the other two modules, the `recover()` function on the module is the key entry point where recovery is executed, it is called in the same way as the other two modules from the manager. @@ -221,18 +246,65 @@ The target selector is hardcoded as a constant variable, as the recovery functio `completeRecovery()` calls `recover()` which calls `execTransactionFromModule` to execute the recovery attempt. `execTransactionFromModule` can only be called from an installed Safe module. ### EmailRecoveryFactory.sol -The factory for deploying new instances of `EmailRecoveryModule.sol` and associated subject handlers. The factory ensures there is a tight coupling between a deployed module and a subject handler. -The deployment function for this factory deploys an `EmailRecoveryModule`, which takes a target validator and function selector. The other values passed into the deployment function are the same as the `EmailRecoveryUniversalFactory`, which include deployment salts, subject handler bytecode, and a dkim registry. +The factory for deploying new instances of `EmailRecoveryModule.sol` and associated command handlers. The factory ensures there is a tight coupling between a deployed module and a command handler. + +The deployment function for this factory deploys an `EmailRecoveryModule`, which takes a target validator and function selector. The other values passed into the deployment function are the same as the `EmailRecoveryUniversalFactory`, which include deployment salts, command handler bytecode, and a dkim registry. -When deploying a new recovery module for a specific validator with a more human readable subject, modular account developers can write their own subject handler and pass the deployment bytecode of that handler into the factory. The security of each module deployment and associated contracts can then be attested to via an ERC7484 registry. +When deploying a new recovery module for a specific validator with a more human readable command, modular account developers can write their own command handler and pass the deployment bytecode of that handler into the factory. The security of each module deployment and associated contracts can then be attested to via an ERC7484 registry. ### EmailRecoveryUniversalFactory.sol -The factory for deploying new instances of `UniversalEmailRecoveryModule.sol` and associated subject handlers. -The deployment function for this factory deploys an `UniversalEmailRecoveryModule`, which takes deployment salts, subject handler bytecode, and a dkim registry as arguments. The target validator and target function selector are set when the universal module is installed. +The factory for deploying new instances of `UniversalEmailRecoveryModule.sol` and associated command handlers. -While the subject handler for `EmailRecoveryUniversalFactory` will be more stable in comparison to a subject handlers used for `EmailRecoveryModule`, developers may want to write a generic subject handler in a slightly different way, or even in a non-english lanaguage, so the bytecode is still passed in here directly. The security of each module deployment and associated contracts can then be attested to via an ERC7484 registry. +The deployment function for this factory deploys an `UniversalEmailRecoveryModule`, which takes deployment salts, command handler bytecode, and a dkim registry as arguments. The target validator and target function selector are set when the universal module is installed. + +While the command handler for `EmailRecoveryUniversalFactory` will be more stable in comparison to a command handlers used for `EmailRecoveryModule`, developers may want to write a generic command handler in a slightly different way, or even in a non-english lanaguage, so the bytecode is still passed in here directly. The security of each module deployment and associated contracts can then be attested to via an ERC7484 registry. ## Threat model + Importantly this contract offers the functonality to recover an account via email in a scenario where a private key has been lost. This contract does NOT provide an adequate mechanism to protect an account from a stolen private key by a malicious actor. This attack vector requires a holistic approach to security that takes specific implementation details of an account into consideration. For example, adding additional access control when cancelling recovery to prevent a malicious actor stopping recovery attempts, and adding spending limits to prevent account draining. Additionally, the current 7579 spec allows accounts to forcefully uninstall modules in the case of a malicious module, this means an attacker could forcefully uninstall a recovery module anyway. This is expected to be addressed in the future. This contract is designed to recover modular accounts in the case of a lost device/authentication method (private key), but does not provide adequate security for a scenario in which a malicious actor has control of the lost device/authentication method (private key). + +## How to Debug Errors + +If you get a revert error message, we recommend the following steps to debug it: + +1. Get the first 4 bytes (0x + 8 hex characters) from the error message, denoting a signature of the custom error. +2. Search those bytes in [test/unit/assertErrorSelectors.t.sol](https://github.com/zkemail/email-recovery/blob/feat/body-parsing/test/unit/assertErrorSelectors.t.sol). +3. Check the following points according to the custom error type within the line that hit. + +- `IEmailRecoveryManager.InvalidGuardianStatus.selector` (`0x5689b51a`): If you get this error when calling the `handleAcceptance` function, you might forget to call the `configureRecovery` function beforehand. + +### If You Get Only '0x' as a Return Value + +This usually means you're trying to call a contract or function that doesn't exist. Check if: + +1. The contract address is correct and deployed +2. The function you're calling is defined correctly + +You can verify contracts on block explorers or use cast commands to test contract calls. See these links for help: + +https://book.getfoundry.sh/reference/cast/cast-call +https://book.getfoundry.sh/reference/cast/cast-send + +For ZKSync, deploy libraries first and add their addresses to your Foundry or Hardhat settings. Make sure these addresses are correct. + +### If You Get an Error Message as Bytes + +The first 4 bytes are the function selector. The rest is the encoded error message. + +Check this test file for a list of function selectors: +test/unit/assertErrorSelectors.t.sol + +### Command Template Mismatch + +We have three different command handlers. Each has its own expected commands for accept and recovery actions. If you get a command-related error, check which command handler you're using. You can do this with block explorers or cast commands. + +## Support and Contact + +We prioritize the security and user experience of ZK Email. If you encounter any issues or have questions, please contact us at: + +- **Support Email**: [team@zk.email](mailto:team@zk.email) +- **Telegram groups**: [t.me/zkemail](https://t.me/zkemail) + +Our team will respond quickly to help resolve any problems. \ No newline at end of file diff --git a/foundry.toml b/foundry.toml index 6e27de20..20dfc6e4 100644 --- a/foundry.toml +++ b/foundry.toml @@ -3,24 +3,18 @@ emv_version = "paris" src = "src" out = "out" script = "script" -libs = [ - "node_modules", - "lib", -] +libs = ["node_modules", "lib"] fs_permissions = [ { access = "read", path = "out-optimized" }, { access = "read-write", path = "gas_calculations" }, ] -allow_paths = [ - "*", - "/", -] +allow_paths = ["*", "/"] ignored_warnings_from = [ "node_modules", "node_modules/@rhinestone/modulekit/src/**/*.sol", "lib", ] - +threads = 1 [rpc_endpoints] sepolia = "${BASE_SEPOLIA_RPC_URL}" @@ -36,6 +30,4 @@ number_underscore = "thousands" quote_style = "double" tab_width = 4 wrap_comments = true -ignore = [ - "./src/libraries/L2ContractHelper.sol", -] +ignore = ["./src/libraries/L2ContractHelper.sol"] diff --git a/package.json b/package.json index b706da99..ae4feb99 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@zk-email/email-recovery", - "version": "0.0.1", + "version": "0.0.12", "description": "Smart account module and related contracts to enable email recovery for validators", "license": "MIT", "author": { @@ -10,29 +10,34 @@ "scripts": { "build": "forge build", "build:optimized": "FOUNDRY_PROFILE=optimized forge build", - "build:smt": "FOUNDRY_PROFILE=smt forge build", "clean": "rm -rf artifacts broadcast cache docs out out-optimized out-svg", - "gas:report": "forge test --gas-report --mp \"./test/integration/**/*.sol\" --nmt \"test(Fuzz)?_RevertWhen_\\w{1,}?\"", - "gas:snapshot": "forge snapshot --mp \"./test/integration/**/*.sol\" --nmt \"test(Fuzz)?_RevertWhen_\\w{1,}?\"", - "gas:snapshot:optimized": "pnpm run build:optimized && FOUNDRY_PROFILE=test-optimized forge snapshot --mp \"./test/integration/**/*.sol\" --nmt \"test(Fork)?(Fuzz)?_RevertWhen_\\w{1,}?\"", - "lint": "pnpm run lint:sol && bun run prettier:check", - "lint:sol": "forge fmt --check && pnpm solhint \"{script,src,test}/**/*.sol\"", + "gas:report": "forge test --gas-report --mp \"./test/integration/**/*.sol\"", + "gas:snapshot": "forge snapshot --mp \"./test/integration/**/*.sol\"", + "gas:snapshot:optimized": "pnpm run build:optimized && FOUNDRY_PROFILE=test-optimized forge snapshot --mp \"./test/integration/**/*.sol\"", + "lint": "forge fmt --check && pnpm solhint \"{script,src,test}/**/*.sol\"", "prepack": "pnpm install", "prettier:check": "prettier --check \"**/*.{json,md,svg,yml}\"", "prettier:write": "prettier --write \"**/*.{json,md,svg,yml}\"", - "test": "forge test", + "test": " COMMAND_HANDLER_TYPE=0 forge test --match-path \"test/**/*.sol\"", + "test:email-recovery-command-handler": "COMMAND_HANDLER_TYPE=0 forge test --match-path \"test/**/*.sol\" ", + "test:address-hiding-command-handler": "COMMAND_HANDLER_TYPE=1 forge test --match-path \"test/**/*.sol\"", + "test:safe-email-recovery-command-handler": "ACCOUNT_TYPE=SAFE COMMAND_HANDLER_TYPE=0 forge test --match-path \"test/**/*.sol\" ", + "test:safe-address-hiding-command-handler": "ACCOUNT_TYPE=SAFE COMMAND_HANDLER_TYPE=1 forge test --match-path \"test/**/*.sol\"", + "test:safe-recovery-command-handler": "ACCOUNT_TYPE=SAFE COMMAND_HANDLER_TYPE=2 forge test --match-path \"test/**/*.sol\"", + "test:all": "pnpm test:email-recovery-command-handler && pnpm test:address-hiding-command-handler && pnpm test:safe-recovery-command-handler", + "test:all-safe": "pnpm test:safe-email-recovery-command-handler && pnpm test:safe-address-hiding-command-handler && pnpm test:safe-recovery-command-handler", + "test:script": "forge test --match-path script/test/**/*.sol --threads 1", "test:lite": "FOUNDRY_PROFILE=lite forge test", "test:optimized": "pnpm run build:optimized && FOUNDRY_PROFILE=test-optimized forge test" }, "dependencies": { "@matterlabs/era-contracts": "github:matter-labs/era-contracts", - "@openzeppelin/contracts-upgradeable": "v5.0.1", + "@openzeppelin/contracts-upgradeable": "5.0.1", "@rhinestone/modulekit": "github:rhinestonewtf/modulekit", - "@zk-email/contracts": "v6.0.3", - "email-wallet-sdk": "github:zkemail/email-wallet-sdk", + "@zk-email/contracts": "^6.3.1", + "@zk-email/ether-email-auth-contracts": "1.0.0", "erc7579-implementation": "github:erc7579/erc7579-implementation", - "ether-email-auth": "github:zkemail/ether-email-auth#b5694a9e0e49d07a862232f665dc4d0886c5a15f", - "solidity-stringutils": "github:Arachnid/solidity-stringutils" + "solidity-stringutils": "github:LayerZero-Labs/solidity-stringutils" }, "files": [ "src", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4149cf3a..456ad006 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,47 +10,40 @@ importers: dependencies: '@matterlabs/era-contracts': specifier: github:matter-labs/era-contracts - version: era-contracts@https://codeload.github.com/matter-labs/era-contracts/tar.gz/446d391d34bdb48255d5f8fef8a8248925fc98b9 + version: era-contracts@https://codeload.github.com/matter-labs/era-contracts/tar.gz/aafee035db892689df3f7afe4b89fd6467a39313 '@openzeppelin/contracts-upgradeable': - specifier: v5.0.1 - version: 5.0.1(@openzeppelin/contracts@5.0.2) + specifier: 5.0.1 + version: 5.0.1(@openzeppelin/contracts@5.1.0) '@rhinestone/modulekit': specifier: github:rhinestonewtf/modulekit - version: https://codeload.github.com/rhinestonewtf/modulekit/tar.gz/d2ed91a9f9c272a614aaf514afe3a5041fed1eb5(ethers@5.7.2)(hardhat@2.22.8(typescript@5.5.4))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.4))(typescript@5.5.4) + version: https://codeload.github.com/rhinestonewtf/modulekit/tar.gz/67a5c02dec798b25a3b2581d485d75e116b14af8(ethers@5.7.2)(hardhat@2.22.14(typescript@4.9.5))(lodash@4.17.21)(typechain@5.2.0(typescript@4.9.5))(typescript@4.9.5) '@zk-email/contracts': - specifier: v6.0.3 - version: 6.0.3 - email-wallet-sdk: - specifier: github:zkemail/email-wallet-sdk - version: https://codeload.github.com/zkemail/email-wallet-sdk/tar.gz/a8c200e4855a81adc7b9493afa92a898ffcb8c56(ethers@5.7.2)(hardhat@2.22.8(typescript@5.5.4))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.4)) + specifier: ^6.3.1 + version: 6.3.1 + '@zk-email/ether-email-auth-contracts': + specifier: 1.0.0 + version: 1.0.0 erc7579-implementation: specifier: github:erc7579/erc7579-implementation - version: https://codeload.github.com/erc7579/erc7579-implementation/tar.gz/b3f8bcb2df3aae3217213ffa8b7a87c1eb42ec56(ethers@5.7.2)(hardhat@2.22.8(typescript@5.5.4))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.4)) - ether-email-auth: - specifier: github:zkemail/ether-email-auth#b5694a9e0e49d07a862232f665dc4d0886c5a15f - version: '@zk-email/ether-email-auth@https://codeload.github.com/zkemail/ether-email-auth/tar.gz/b5694a9e0e49d07a862232f665dc4d0886c5a15f' + version: https://codeload.github.com/erc7579/erc7579-implementation/tar.gz/b3f8bcb2df3aae3217213ffa8b7a87c1eb42ec56(ethers@5.7.2)(hardhat@2.22.14(typescript@4.9.5))(lodash@4.17.21)(typechain@5.2.0(typescript@4.9.5)) solidity-stringutils: - specifier: github:Arachnid/solidity-stringutils - version: https://codeload.github.com/Arachnid/solidity-stringutils/tar.gz/4b2fcc43fa0426e19ce88b1f1ec16f5903a2e461 + specifier: github:LayerZero-Labs/solidity-stringutils + version: https://codeload.github.com/LayerZero-Labs/solidity-stringutils/tar.gz/eb21d6b502c2741145ab2a90f5f5b4fda9dfb218 packages: - '@babel/code-frame@7.24.7': - resolution: {integrity: sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==} + '@babel/code-frame@7.25.7': + resolution: {integrity: sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.24.7': - resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==} + '@babel/helper-validator-identifier@7.25.7': + resolution: {integrity: sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==} engines: {node: '>=6.9.0'} - '@babel/highlight@7.24.7': - resolution: {integrity: sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==} + '@babel/highlight@7.25.7': + resolution: {integrity: sha512-iYyACpW3iW8Fw+ZybQK+drQre+ns/tKpXbNESfrhNnPLIklLbXr7MYJ6gPEd0iETGLOK+SxMjVvKb/ffmk+FEw==} engines: {node: '>=6.9.0'} - '@email-wallet/contracts@https://gitpkg.now.sh/zkemail/email-wallet/packages/contracts?8ae807dfcfe617a1a3e4b5342a667f0f595b8c82': - resolution: {tarball: https://gitpkg.now.sh/zkemail/email-wallet/packages/contracts?8ae807dfcfe617a1a3e4b5342a667f0f595b8c82} - version: 1.0.0 - '@ethereumjs/rlp@4.0.1': resolution: {integrity: sha512-tqsQiBQDQdmPWE1xkkBq4rlSW5QZpLOUJ5RJh2/9fug+q9tnUhuZoVLk7s0scUIKTOzEtR72DFBXI4WiZcMpvw==} engines: {node: '>=14'} @@ -159,6 +152,12 @@ packages: peerDependencies: ethers: ^5.1.4 + '@matterlabs/zksync-contracts@0.6.1': + resolution: {integrity: sha512-+hucLw4DhGmTmQlXOTEtpboYCaOm/X2VJcWmnW4abNcOgQXEHX+mTxQrxEfPjIZT0ZE6z5FTUrOK9+RgUZwBMQ==} + peerDependencies: + '@openzeppelin/contracts': 4.6.0 + '@openzeppelin/contracts-upgradeable': 4.6.0 + '@metamask/eth-sig-util@4.0.1': resolution: {integrity: sha512-tghyZKLHZjcdlDqCA3gNZmLeR0XvOE9U1qoQO9ohyAZT6Pya+H9vkBPcsyXytmYLNgVoin7CKCmweo/R43V+tQ==} engines: {node: '>=12.0.0'} @@ -173,6 +172,10 @@ packages: resolution: {integrity: sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==} engines: {node: '>= 16'} + '@noble/hashes@1.5.0': + resolution: {integrity: sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==} + engines: {node: ^14.21.3 || >=16} + '@noble/secp256k1@1.7.1': resolution: {integrity: sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw==} @@ -192,36 +195,36 @@ packages: resolution: {tarball: https://codeload.github.com/nomad-xyz/ExcessivelySafeCall/tar.gz/81cd99ce3e69117d665d7601c330ea03b97acce0} version: 0.0.1-rc.1 - '@nomicfoundation/edr-darwin-arm64@0.5.2': - resolution: {integrity: sha512-Gm4wOPKhbDjGTIRyFA2QUAPfCXA1AHxYOKt3yLSGJkQkdy9a5WW+qtqKeEKHc/+4wpJSLtsGQfpzyIzggFfo/A==} + '@nomicfoundation/edr-darwin-arm64@0.6.4': + resolution: {integrity: sha512-QNQErISLgssV9+qia8sIjRANqtbW8snSDvjspixT/kSQ5ZSGxxctTg7x72wPSrcu8+EBEveIe5uqENIp5GH8HQ==} engines: {node: '>= 18'} - '@nomicfoundation/edr-darwin-x64@0.5.2': - resolution: {integrity: sha512-ClyABq2dFCsrYEED3/UIO0c7p4H1/4vvlswFlqUyBpOkJccr75qIYvahOSJRM62WgUFRhbSS0OJXFRwc/PwmVg==} + '@nomicfoundation/edr-darwin-x64@0.6.4': + resolution: {integrity: sha512-cjVmREiwByyc9+oGfvAh49IAw+oVJHF9WWYRD+Tm/ZlSpnEVWxrGNBak2bd/JSYjn+mZE7gmWS4SMRi4nKaLUg==} engines: {node: '>= 18'} - '@nomicfoundation/edr-linux-arm64-gnu@0.5.2': - resolution: {integrity: sha512-HWMTVk1iOabfvU2RvrKLDgtFjJZTC42CpHiw2h6rfpsgRqMahvIlx2jdjWYzFNy1jZKPTN1AStQ/91MRrg5KnA==} + '@nomicfoundation/edr-linux-arm64-gnu@0.6.4': + resolution: {integrity: sha512-96o9kRIVD6W5VkgKvUOGpWyUGInVQ5BRlME2Fa36YoNsRQMaKtmYJEU0ACosYES6ZTpYC8U5sjMulvPtVoEfOA==} engines: {node: '>= 18'} - '@nomicfoundation/edr-linux-arm64-musl@0.5.2': - resolution: {integrity: sha512-CwsQ10xFx/QAD5y3/g5alm9+jFVuhc7uYMhrZAu9UVF+KtVjeCvafj0PaVsZ8qyijjqVuVsJ8hD1x5ob7SMcGg==} + '@nomicfoundation/edr-linux-arm64-musl@0.6.4': + resolution: {integrity: sha512-+JVEW9e5plHrUfQlSgkEj/UONrIU6rADTEk+Yp9pbe+mzNkJdfJYhs5JYiLQRP4OjxH4QOrXI97bKU6FcEbt5Q==} engines: {node: '>= 18'} - '@nomicfoundation/edr-linux-x64-gnu@0.5.2': - resolution: {integrity: sha512-CWVCEdhWJ3fmUpzWHCRnC0/VLBDbqtqTGTR6yyY1Ep3S3BOrHEAvt7h5gx85r2vLcztisu2vlDq51auie4IU1A==} + '@nomicfoundation/edr-linux-x64-gnu@0.6.4': + resolution: {integrity: sha512-nzYWW+fO3EZItOeP4CrdMgDXfaGBIBkKg0Y/7ySpUxLqzut40O4Mb0/+quqLAFkacUSWMlFp8nsmypJfOH5zoA==} engines: {node: '>= 18'} - '@nomicfoundation/edr-linux-x64-musl@0.5.2': - resolution: {integrity: sha512-+aJDfwhkddy2pP5u1ISg3IZVAm0dO836tRlDTFWtvvSMQ5hRGqPcWwlsbobhDQsIxhPJyT7phL0orCg5W3WMeA==} + '@nomicfoundation/edr-linux-x64-musl@0.6.4': + resolution: {integrity: sha512-QFRoE9qSQ2boRrVeQ1HdzU+XN7NUgwZ1SIy5DQt4d7jCP+5qTNsq8LBNcqhRBOATgO63nsweNUhxX/Suj5r1Sw==} engines: {node: '>= 18'} - '@nomicfoundation/edr-win32-x64-msvc@0.5.2': - resolution: {integrity: sha512-CcvvuA3sAv7liFNPsIR/68YlH6rrybKzYttLlMr80d4GKJjwJ5OKb3YgE6FdZZnOfP19HEHhsLcE0DPLtY3r0w==} + '@nomicfoundation/edr-win32-x64-msvc@0.6.4': + resolution: {integrity: sha512-2yopjelNkkCvIjUgBGhrn153IBPLwnsDeNiq6oA0WkeM8tGmQi4td+PGi9jAriUDAkc59Yoi2q9hYA6efiY7Zw==} engines: {node: '>= 18'} - '@nomicfoundation/edr@0.5.2': - resolution: {integrity: sha512-hW/iLvUQZNTVjFyX/I40rtKvvDOqUEyIi96T28YaLfmPL+3LW2lxmYLUXEJ6MI14HzqxDqrLyhf6IbjAa2r3Dw==} + '@nomicfoundation/edr@0.6.4': + resolution: {integrity: sha512-YgrSuT3yo5ZQkbvBGqQ7hG+RDvz3YygSkddg4tb1Z0Y6pLXFzwrcEwWaJCFAVeeZxdxGfCgGMUYgRVneK+WXkw==} engines: {node: '>= 18'} '@nomicfoundation/ethereumjs-common@4.0.4': @@ -288,25 +291,19 @@ packages: peerDependencies: hardhat: ^2.0.4 - '@openzeppelin/contracts-upgradeable@4.9.6': - resolution: {integrity: sha512-m4iHazOsOCv1DgM7eD7GupTJ+NFVujRZt1wzddDPSVGpWdKq1SKkla5htKG7+IS4d2XOCtzkUNwRZ7Vq5aEUMA==} - '@openzeppelin/contracts-upgradeable@5.0.1': resolution: {integrity: sha512-MvaLoPnVcoZr/qqZP+4cl9piuR4gg0iIGgxVSZ/AL1iId3M6IdEHzz9Naw5Lirl4KKBI6ciTVnX07yL4dOMIJg==} peerDependencies: '@openzeppelin/contracts': 5.0.1 - '@openzeppelin/contracts@3.4.2-solc-0.7': - resolution: {integrity: sha512-W6QmqgkADuFcTLzHL8vVoNBtkwjvQRpYIAom7KiUNoLKghyx3FgH0GBjt8NRvigV1ZmMOBllvE1By1C+bi8WpA==} - '@openzeppelin/contracts@4.9.6': resolution: {integrity: sha512-xSmezSupL+y9VkHZJGDoCBpmnB2ogM13ccaYDWqJTfS3dbuHkgjuwDFUmaFauBCboQMGB/S5UqUl2y54X99BmA==} '@openzeppelin/contracts@5.0.1': resolution: {integrity: sha512-yQJaT5HDp9hYOOp4jTYxMsR02gdFZFXhewX5HW9Jo4fsqSVqqyIO/xTHdWDaKX5a3pv1txmf076Lziz+sO7L1w==} - '@openzeppelin/contracts@5.0.2': - resolution: {integrity: sha512-ytPc6eLGcHHnapAZ9S+5qsdomhjo6QBHTDRRBFfTxXIpsicMhVPouPgmUPebZZZGX7vt9USA+Z+0M0dSVtSUEA==} + '@openzeppelin/contracts@5.1.0': + resolution: {integrity: sha512-p1ULhl7BXzjjbha5aqst+QMLY+4/LCWADXOCsmLHRM77AqiPjnd9vvUN9sosUfhL9JGKpZ0TjEGxgvnizmWGSA==} '@pnpm/config.env-replace@1.1.0': resolution: {integrity: sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==} @@ -316,12 +313,12 @@ packages: resolution: {integrity: sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==} engines: {node: '>=12.22.0'} - '@pnpm/npm-conf@2.3.0': - resolution: {integrity: sha512-DqrO+oXGR7HCuicNy6quk6ALJSDDPKI7RZz1bP5im8mSL8J2e+9w26LdkjuAfpAjOutYUJVbnXnx4IbTQeIgfw==} + '@pnpm/npm-conf@2.3.1': + resolution: {integrity: sha512-c83qWb22rNRuB0UaVCI0uRPNRr8Z0FWnEIvT47jiHAmOIUHbBOg5XvV7pM5x+rKn9HRpjxquDbXYSXr3fAKFcw==} engines: {node: '>=12'} - '@prb/math@4.0.3': - resolution: {integrity: sha512-/RSt3VU1k2m3ox6U6kUL1MrktnAHr8vhydXu4eDtqFAms1gm3XnGpoZIPaK1lm2zdJQmKBwJ4EXALPARsuOlaA==} + '@prb/math@4.1.0': + resolution: {integrity: sha512-ef5Xrlh3BeX4xT5/Wi810dpEPq2bYPndRxgFIaKSU1F/Op/s8af03kyom+mfU7gEpvfIZ46xu8W0duiHplbBMg==} '@rhinestone/checknsignatures@https://codeload.github.com/rhinestonewtf/checknsignatures/tar.gz/7ff44ef46da1266374e6a98e6cf69d727d7c357d': resolution: {tarball: https://codeload.github.com/rhinestonewtf/checknsignatures/tar.gz/7ff44ef46da1266374e6a98e6cf69d727d7c357d} @@ -333,13 +330,17 @@ packages: '@rhinestone/erc4337-validation@0.0.1-alpha.5': resolution: {integrity: sha512-yOrYyQBrT0JfHb+rjvx4pqk8uItKxEtn7n8z3k0qbZTzkXaNS9pCUBsTxy0kp6T2SNUrbQ8I4DMSiyGqjdh2ng==} - '@rhinestone/module-bases@https://codeload.github.com/rhinestonewtf/module-bases/tar.gz/2db71722f939ed7d76315fc94c1a85bcb09ce59b': - resolution: {tarball: https://codeload.github.com/rhinestonewtf/module-bases/tar.gz/2db71722f939ed7d76315fc94c1a85bcb09ce59b} + '@rhinestone/module-bases@https://codeload.github.com/rhinestonewtf/module-bases/tar.gz/70f0833756653579be6a95ea48e49a5090e2ff48': + resolution: {tarball: https://codeload.github.com/rhinestonewtf/module-bases/tar.gz/70f0833756653579be6a95ea48e49a5090e2ff48} version: 0.0.1 - '@rhinestone/modulekit@https://codeload.github.com/rhinestonewtf/modulekit/tar.gz/d2ed91a9f9c272a614aaf514afe3a5041fed1eb5': - resolution: {tarball: https://codeload.github.com/rhinestonewtf/modulekit/tar.gz/d2ed91a9f9c272a614aaf514afe3a5041fed1eb5} - version: 0.4.10 + '@rhinestone/modulekit@https://codeload.github.com/rhinestonewtf/modulekit/tar.gz/67a5c02dec798b25a3b2581d485d75e116b14af8': + resolution: {tarball: https://codeload.github.com/rhinestonewtf/modulekit/tar.gz/67a5c02dec798b25a3b2581d485d75e116b14af8} + version: 0.4.13 + + '@rhinestone/registry@https://codeload.github.com/rhinestonewtf/registry/tar.gz/1371979a97293e0c6188afcd923784f6a718ae7d': + resolution: {tarball: https://codeload.github.com/rhinestonewtf/registry/tar.gz/1371979a97293e0c6188afcd923784f6a718ae7d} + version: 1.0.0 '@rhinestone/safe7579@https://codeload.github.com/rhinestonewtf/safe7579/tar.gz/33f110f08ed5fcab75c29d7cfb93f7f3e4da76a7': resolution: {tarball: https://codeload.github.com/rhinestonewtf/safe7579/tar.gz/33f110f08ed5fcab75c29d7cfb93f7f3e4da76a7} @@ -354,8 +355,8 @@ packages: peerDependencies: ethers: 5.4.0 - '@scure/base@1.1.7': - resolution: {integrity: sha512-PPNYBslrLNNUQ/Yad37MHYsNQtK67EhWb6WtSvNLLPo7SdVZgkUjD6Dg+5On7zNwmskf8OX7I7Nx5oN+MIWE0g==} + '@scure/base@1.1.9': + resolution: {integrity: sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==} '@scure/bip32@1.1.5': resolution: {integrity: sha512-XyNh1rB0SkEqd3tXcXMi+Xe1fvg+kUIcoRIEujP1Jgv7DqW2r9lg3Ah0NkFaCs9sTkQAQA8kw7xiRXzENi9Rtw==} @@ -421,8 +422,8 @@ packages: '@types/bn.js@4.11.6': resolution: {integrity: sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==} - '@types/bn.js@5.1.5': - resolution: {integrity: sha512-V46N0zwKRF5Q00AZ6hWtN0T8gGmDUaUzLWQvHFo5yThtVwK/VCenFY3wXVbOvNfajEpsTfQM4IN9k/d6gUVX3A==} + '@types/bn.js@5.1.6': + resolution: {integrity: sha512-Xh8vSwUeMKeYYrj3cX4lGQgFSF/N03r+tv4AiLl1SucqV+uTQpxRcnM8AkXKHwYP9ZPXOYXRr2KPXpVlIvqh9w==} '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} @@ -445,11 +446,11 @@ packages: '@types/ms@0.7.34': resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} - '@types/node@20.14.14': - resolution: {integrity: sha512-d64f00982fS9YoOgJkAMolK7MN8Iq3TDdVjchbYHdEmjth/DHowx82GnoA+tVUAN+7vxfYUgAzi+JXbKNd2SDQ==} + '@types/node@20.16.14': + resolution: {integrity: sha512-vtgGzjxLF7QT88qRHtXMzCWpAAmwonE7fwgVjFtXosUva2oSpnIEc3gNO9P7uIfOxKnii2f79/xtOnfreYtDaA==} - '@types/node@22.1.0': - resolution: {integrity: sha512-AOmuRF0R2/5j1knA3c6G3HOk523Ga+l+ZXltX8SF1+5oqcXijjfTd8fY3XRZqSihEu9XhtQnKYLmkFaoxgsJHw==} + '@types/node@22.7.8': + resolution: {integrity: sha512-a922jJy31vqR5sk+kAdIENJjHblqcZ4RmERviFsER4WJcEONqxKcjNOlk0q7OUfrF5sddT+vng070cdfMlrPLg==} '@types/pbkdf2@3.1.2': resolution: {integrity: sha512-uRwJqmiXmh9++aSu1VNEn3iIxWOhd8AHXNSdlaLfdAAdSTY9jYVeGWnzejM3dvrkbqE3/hyQkQQ29IFATEGlew==} @@ -457,44 +458,17 @@ packages: '@types/prettier@2.7.3': resolution: {integrity: sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==} - '@types/qs@6.9.15': - resolution: {integrity: sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==} + '@types/qs@6.9.16': + resolution: {integrity: sha512-7i+zxXdPD0T4cKDuxCUXJ4wHcsJLwENa6Z3dCu8cfCK743OGy5Nu1RmAGqDPsoTDINVEcdXKRvR/zre+P2Ku1A==} '@types/secp256k1@4.0.6': resolution: {integrity: sha512-hHxJU6PAEUn0TP4S/ZOzuTUvJWuZ6eIKeNKb5RBpODvSl6hp1Wrw4s7ATY50rklRCScUDpHzVA/DQdSjJ3UoYQ==} - '@uniswap/lib@4.0.1-alpha': - resolution: {integrity: sha512-f6UIliwBbRsgVLxIaBANF6w09tYqc6Y/qXdsrbEmXHyFA7ILiKrIwRFXe1yOg8M3cksgVsO9N7yuL2DdCGQKBA==} - engines: {node: '>=10'} - - '@uniswap/v2-core@1.0.1': - resolution: {integrity: sha512-MtybtkUPSyysqLY2U210NBDeCHX+ltHt3oADGdjqoThZaFRDKwM6k1Nb3F0A3hk5hwuQvytFWhrWHOEq6nVJ8Q==} - engines: {node: '>=10'} + '@zk-email/contracts@6.3.1': + resolution: {integrity: sha512-SiSSaHvn6BrT0vFFPA48r7jdoZi+2ZmRfU4QEQamtMqBlzVbs3ZmgTzgR4SFWz2/1SP+IIzDs9pfkkqf+6Wqcw==} - '@uniswap/v3-core@1.0.1': - resolution: {integrity: sha512-7pVk4hEm00j9tc71Y9+ssYpO6ytkeI0y7WE9P6UcmNzhxPePwyAxImuhVsTqWK9YFvzgtvzJHi64pBl4jUzKMQ==} - engines: {node: '>=10'} - - '@uniswap/v3-core@https://codeload.github.com/Uniswap/v3-core/tar.gz/6562c52e8f75f0c10f9deaf44861847585fc8129': - resolution: {tarball: https://codeload.github.com/Uniswap/v3-core/tar.gz/6562c52e8f75f0c10f9deaf44861847585fc8129} - version: 1.0.1-solc-0.8 - engines: {node: '>=10'} - - '@uniswap/v3-periphery@https://codeload.github.com/Uniswap/v3-periphery/tar.gz/0682387198a24c7cd63566a2c58398533860a5d1': - resolution: {tarball: https://codeload.github.com/Uniswap/v3-periphery/tar.gz/0682387198a24c7cd63566a2c58398533860a5d1} - version: 1.4.4 - engines: {node: '>=10'} - - '@zk-email/contracts@4.1.0': - resolution: {integrity: sha512-5cFgx1kpAPL0Pl5wUb2SA3qaIgYpbp5jjonuYH268BaspvyqSoFS77/UVX+4yjbOQtZmrTntaYXNaseghLLlXw==} - - '@zk-email/contracts@6.0.3': - resolution: {integrity: sha512-nPSG27431Cz5bzPlR/ltn7qa9k+Joc/6LDHUz+JeFWEP/ff9VnzK11P0ay5qdX14qpQ647varPdxGtulosUtxw==} - - '@zk-email/ether-email-auth@https://codeload.github.com/zkemail/ether-email-auth/tar.gz/b5694a9e0e49d07a862232f665dc4d0886c5a15f': - resolution: {tarball: https://codeload.github.com/zkemail/ether-email-auth/tar.gz/b5694a9e0e49d07a862232f665dc4d0886c5a15f} - version: 1.0.0 - engines: {node: '18'} + '@zk-email/ether-email-auth-contracts@1.0.0': + resolution: {integrity: sha512-xgZ8oc8fx0ddNgIkyVEt81mcDx298GCV/YfI8pQ5CaXwPBY0ketcoSj+vzw5h/+xHhqfJLEENYtKmzS0XPczpQ==} abbrev@1.0.9: resolution: {integrity: sha512-LEyx4aLEC3x6T0UguF6YILf+ntvmOaWsVfENmIW0E9H09vKlLDGelMjjSm0jkDHALj8A8quZ/HapKNigzwge+Q==} @@ -614,9 +588,6 @@ packages: base-x@3.0.10: resolution: {integrity: sha512-7d0s06rR9rYaIWHkpfLIFICM/tkSVdoPC9qYAQRpxn9DdKNWNsKC0uk++akckyLq16Tx2WIinnZ6WRriAt6njQ==} - base64-sol@1.0.1: - resolution: {integrity: sha512-ld3cCNMeXt4uJXmLZBHFGMvVpK9KsLVEhPpFRXnvSVAqABKbuNZg/+dsq3NuM+wxFLb/UrVkz7m1ciWmkMfTbg==} - bech32@1.1.4: resolution: {integrity: sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==} @@ -714,6 +685,10 @@ packages: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} + chokidar@4.0.1: + resolution: {integrity: sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==} + engines: {node: '>= 14.16.0'} + ci-info@2.0.0: resolution: {integrity: sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==} @@ -791,8 +766,8 @@ packages: death@1.1.0: resolution: {integrity: sha512-vsV6S4KVHvTGxbEcij7hkWRv0It+sGGWVOM67dQde/o5Xjnr+KmLjxWJii2uEObIrt1CcM9w0Yaovx+iOlIL+w==} - debug@4.3.6: - resolution: {integrity: sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==} + debug@4.3.7: + resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} engines: {node: '>=6.0'} peerDependencies: supports-color: '*' @@ -853,12 +828,8 @@ packages: elliptic@6.5.4: resolution: {integrity: sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==} - elliptic@6.5.6: - resolution: {integrity: sha512-mpzdtpeCLuS3BmE3pO3Cpp5bbjlOPY2Q0PgoF+Od1XZrHLYI28Xe3ossCmYCQt11FQKEYd9+PF8jymTvtWJSHQ==} - - email-wallet-sdk@https://codeload.github.com/zkemail/email-wallet-sdk/tar.gz/a8c200e4855a81adc7b9493afa92a898ffcb8c56: - resolution: {tarball: https://codeload.github.com/zkemail/email-wallet-sdk/tar.gz/a8c200e4855a81adc7b9493afa92a898ffcb8c56} - version: 0.0.1 + elliptic@6.5.7: + resolution: {integrity: sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -874,8 +845,8 @@ packages: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} engines: {node: '>=6'} - era-contracts@https://codeload.github.com/matter-labs/era-contracts/tar.gz/446d391d34bdb48255d5f8fef8a8248925fc98b9: - resolution: {tarball: https://codeload.github.com/matter-labs/era-contracts/tar.gz/446d391d34bdb48255d5f8fef8a8248925fc98b9} + era-contracts@https://codeload.github.com/matter-labs/era-contracts/tar.gz/aafee035db892689df3f7afe4b89fd6467a39313: + resolution: {tarball: https://codeload.github.com/matter-labs/era-contracts/tar.gz/aafee035db892689df3f7afe4b89fd6467a39313} version: 0.1.0 erc7579-implementation@https://codeload.github.com/erc7579/erc7579-implementation/tar.gz/b3f8bcb2df3aae3217213ffa8b7a87c1eb42ec56: @@ -893,8 +864,8 @@ packages: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} - escalade@3.1.2: - resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} escape-string-regexp@1.0.5: @@ -942,6 +913,7 @@ packages: ethereumjs-abi@0.6.8: resolution: {integrity: sha512-Tx0r/iXI6r+lRsdvkFDlut0N08jWMnKRZ6Gkq+Nmw75lZe4e6o3EkSnkaBP5NF6+m5PTGAr9JP43N3LyeoglsA==} + deprecated: This library has been deprecated and usage is discouraged. ethereumjs-util@6.2.1: resolution: {integrity: sha512-W2Ktez4L01Vexijrm5EB6w7dg4n/TgpoYU4avuT5T3Vmnw/eCRtiBrJfQYS/DCSvDIOLn2k57GcHdeBcgVxAqw==} @@ -952,6 +924,7 @@ packages: ethereumjs-wallet@1.0.2: resolution: {integrity: sha512-CCWV4RESJgRdHIvFciVQFnCHfqyhXWchTPlkfp28Qc53ufs+doi5I/cV2+xeK9+qEo25XCWfP9MiL+WEPAZfdA==} + deprecated: 'New package name format for new versions: @ethereumjs/wallet. Please update.' ethers@5.7.2: resolution: {integrity: sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg==} @@ -983,8 +956,8 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - fast-uri@3.0.1: - resolution: {integrity: sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==} + fast-uri@3.0.3: + resolution: {integrity: sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw==} fastq@1.17.1: resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} @@ -1012,8 +985,8 @@ packages: fmix@0.1.0: resolution: {integrity: sha512-Y6hyofImk9JdzU8k5INtTXX1cu8LDlePWDFU5sftm9H+zKCr5SGrVjdhkvsim646cw5zD0nADj8oHyXMZmCZ9w==} - follow-redirects@1.15.6: - resolution: {integrity: sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==} + follow-redirects@1.15.9: + resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} engines: {node: '>=4.0'} peerDependencies: debug: '*' @@ -1021,16 +994,20 @@ packages: debug: optional: true - forge-std@https://codeload.github.com/foundry-rs/forge-std/tar.gz/4695fac44b2934aaa6d7150e2eaf0256fdc566a7: - resolution: {tarball: https://codeload.github.com/foundry-rs/forge-std/tar.gz/4695fac44b2934aaa6d7150e2eaf0256fdc566a7} - version: 1.9.2 + forge-std@https://codeload.github.com/foundry-rs/forge-std/tar.gz/1eea5bae12ae557d589f9f0f0edae2faa47cb262: + resolution: {tarball: https://codeload.github.com/foundry-rs/forge-std/tar.gz/1eea5bae12ae557d589f9f0f0edae2faa47cb262} + version: 1.9.4 + + forge-std@https://codeload.github.com/foundry-rs/forge-std/tar.gz/ae570fec082bfe1c1f45b0acca4a2b4f84d345ce: + resolution: {tarball: https://codeload.github.com/foundry-rs/forge-std/tar.gz/ae570fec082bfe1c1f45b0acca4a2b4f84d345ce} + version: 1.7.6 form-data-encoder@2.1.4: resolution: {integrity: sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==} engines: {node: '>= 14.17'} - form-data@4.0.0: - resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} + form-data@4.0.1: + resolution: {integrity: sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==} engines: {node: '>= 6'} fp-ts@1.19.3: @@ -1139,8 +1116,8 @@ packages: hardhat-deploy@0.11.45: resolution: {integrity: sha512-aC8UNaq3JcORnEUIwV945iJuvBwi65tjHVDU3v6mOcqik7WAzHVCJ7cwmkkipsHrWysrB5YvGF1q9S1vIph83w==} - hardhat@2.22.8: - resolution: {integrity: sha512-hPh2feBGRswkXkoXUFW6NbxgiYtEzp/3uvVFjYROy6fA9LH8BobUyxStlyhSKj4+v1Y23ZoUBOVWL84IcLACrA==} + hardhat@2.22.14: + resolution: {integrity: sha512-sD8vHtS9l5QQVHzyPPe3auwZDJyZ0fG3Z9YENVa4oOqVEefCuHcPzdU736rei3zUKTqkX0zPIHkSMHpu02Fq1A==} hasBin: true peerDependencies: ts-node: '*' @@ -1214,8 +1191,8 @@ packages: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} - ignore@5.3.1: - resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} immutable@4.3.7: @@ -1257,8 +1234,8 @@ packages: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} - is-core-module@2.15.0: - resolution: {integrity: sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==} + is-core-module@2.15.1: + resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==} engines: {node: '>= 0.4'} is-extglob@2.1.1: @@ -1318,6 +1295,10 @@ packages: json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + json-stream-stringify@3.1.6: + resolution: {integrity: sha512-x7fpwxOkbhFCaJDJ8vb1fBY3DdSa4AlITaz+HHILQJzdPMnHEFjxPwVUi1ALIbcIxDE0PNe/0i7frnY8QnBQog==} + engines: {node: '>=7.10.1'} + jsonfile@4.0.0: resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} @@ -1381,8 +1362,8 @@ packages: match-all@1.2.6: resolution: {integrity: sha512-0EESkXiTkWzrQQntBu2uzKvLu6vVkUGz40nGPbSZuegcfE5UuSzNjLaIu76zJWuaT/2I3Z/8M06OlUOZLGwLlQ==} - mcl-wasm@1.5.0: - resolution: {integrity: sha512-+Bnefweg0PWhQ//pVAawNkZAC+TH/mMZVsxmEyHvw8Ujhwu3cxUe9WITFK74dfgPRB09Zkmf6aUFXnW23OnVUw==} + mcl-wasm@1.7.0: + resolution: {integrity: sha512-ok9uE7ekFh5+orI0dFT19KeY/y5P6ONp0dks8oo/KniyNK6mJ0zloL3+s6LiEQXW8VxQHwsfZslitL/R7MM9ew==} engines: {node: '>=14.17'} md5.js@1.3.5: @@ -1399,8 +1380,8 @@ packages: micro-ftch@0.3.1: resolution: {integrity: sha512-/0LLxhzP0tfiR5hcQebtudP56gUurs2CLkGarnCiB/OqEyUFQ6U3paQi/tgLv0hBJYt2rnr9MNpxz4fiiugstg==} - micromatch@4.0.7: - resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==} + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} mime-db@1.52.0: @@ -1447,14 +1428,11 @@ packages: mnemonist@0.38.5: resolution: {integrity: sha512-bZTFT5rrPKtPJxj8KSV0WkPyNxl72vQepqqVUAW2ARUpUSF2qXMB6jZj7hW5/k7C1rtpzqbD/IIbJwLXUjCHeg==} - mocha@10.7.0: - resolution: {integrity: sha512-v8/rBWr2VO5YkspYINnvu81inSz2y3ODJrhO175/Exzor1RcEZZkizgE2A+w/CAXXoESS8Kys5E62dOHGHzULA==} + mocha@10.7.3: + resolution: {integrity: sha512-uQWxAu44wwiACGqjbPYmjo7Lg8sFrS3dQe7PP2FQI+woptP4vZXSMcfMyFL/e1yFEeEpV4RtyTpZROOKmxis+A==} engines: {node: '>= 14.0.0'} hasBin: true - ms@2.1.2: - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} - ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -1467,6 +1445,9 @@ packages: node-addon-api@2.0.2: resolution: {integrity: sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==} + node-addon-api@5.1.0: + resolution: {integrity: sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==} + node-emoji@1.11.0: resolution: {integrity: sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==} @@ -1479,8 +1460,8 @@ packages: encoding: optional: true - node-gyp-build@4.8.1: - resolution: {integrity: sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==} + node-gyp-build@4.8.2: + resolution: {integrity: sha512-IRUxE4BVsHWXkV/SFOut4qTlagw2aM8T5/vnTsmrHJvVoKueJHRc/JaFND7QDDc61kLYUJ6qlZM3sqTSyx2dTw==} hasBin: true nofilter@1.0.4: @@ -1584,8 +1565,8 @@ packages: resolution: {integrity: sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==} engines: {node: '>=0.12'} - picocolors@1.0.1: - resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} @@ -1645,6 +1626,10 @@ packages: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} + readdirp@4.0.2: + resolution: {integrity: sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==} + engines: {node: '>= 14.16.0'} + rechoir@0.6.2: resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==} engines: {node: '>= 0.10'} @@ -1717,9 +1702,9 @@ packages: scrypt-js@3.0.1: resolution: {integrity: sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==} - secp256k1@4.0.3: - resolution: {integrity: sha512-NLZVf+ROMxwtEj3Xa562qgv2BK5e2WNmXPiOdVIPLgs6lyTzMvBq0aWTYMI5XCP9jZMVKOcqZLw/Wc4vDkuxhA==} - engines: {node: '>=10.0.0'} + secp256k1@4.0.4: + resolution: {integrity: sha512-6JfvwvjUOn8F/jUoBY2Q1v5WY5XS+rj8qSe0v8Y4ezH4InLgTEeOOPQsRll9OV429Pvo6BCHGavIyJfr3TAhsw==} + engines: {node: '>=18.0.0'} semver@5.7.2: resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} @@ -1771,9 +1756,13 @@ packages: solady@0.0.123: resolution: {integrity: sha512-F/B8OMCplGsS4FrdPrnEG0xdg8HKede5PwC+Rum8soj/LWxfKckA0p+Uwnlbgey2iI82IHvmSOCNhsdbA+lUrw==} - solady@https://codeload.github.com/vectorized/solady/tar.gz/4363564a984779b7eec3bff00ab1de3a9db4e2d5: - resolution: {tarball: https://codeload.github.com/vectorized/solady/tar.gz/4363564a984779b7eec3bff00ab1de3a9db4e2d5} - version: 0.0.236 + solady@https://codeload.github.com/vectorized/solady/tar.gz/9deb9ed36a27261a8745db5b7cd7f4cdc3b1cd4e: + resolution: {tarball: https://codeload.github.com/vectorized/solady/tar.gz/9deb9ed36a27261a8745db5b7cd7f4cdc3b1cd4e} + version: 0.0.168 + + solady@https://codeload.github.com/vectorized/solady/tar.gz/a83af63d575ed5d73e8c32830f8b50fc35591213: + resolution: {tarball: https://codeload.github.com/vectorized/solady/tar.gz/a83af63d575ed5d73e8c32830f8b50fc35591213} + version: 0.0.261 solarray@https://codeload.github.com/sablier-labs/solarray/tar.gz/6bf10cb34cdace52a3ba5fe437e78cc82df92684: resolution: {tarball: https://codeload.github.com/sablier-labs/solarray/tar.gz/6bf10cb34cdace52a3ba5fe437e78cc82df92684} @@ -1788,15 +1777,15 @@ packages: resolution: {integrity: sha512-Cu1XiJXub2q1eCr9kkJ9VPv1sGcmj3V7Zb76B0CoezDOB9bu3DxKIFFH7ggCl9fWpEPD6xBmRLfZrYijkVmujQ==} hasBin: true - solidity-coverage@0.8.12: - resolution: {integrity: sha512-8cOB1PtjnjFRqOgwFiD8DaUsYJtVJ6+YdXQtSZDrLGf8cdhhh8xzTtGzVTGeBf15kTv0v7lYPJlV/az7zLEPJw==} + solidity-coverage@0.8.13: + resolution: {integrity: sha512-RiBoI+kF94V3Rv0+iwOj3HQVSqNzA9qm/qDP1ZDXK5IX0Cvho1qiz8hAXTsAo6KOIUeP73jfscq0KlLqVxzGWA==} hasBin: true peerDependencies: hardhat: ^2.11.0 - solidity-stringutils@https://codeload.github.com/Arachnid/solidity-stringutils/tar.gz/4b2fcc43fa0426e19ce88b1f1ec16f5903a2e461: - resolution: {tarball: https://codeload.github.com/Arachnid/solidity-stringutils/tar.gz/4b2fcc43fa0426e19ce88b1f1ec16f5903a2e461} - version: 0.0.0 + solidity-stringutils@https://codeload.github.com/LayerZero-Labs/solidity-stringutils/tar.gz/eb21d6b502c2741145ab2a90f5f5b4fda9dfb218: + resolution: {tarball: https://codeload.github.com/LayerZero-Labs/solidity-stringutils/tar.gz/eb21d6b502c2741145ab2a90f5f5b4fda9dfb218} + version: 0.0.1 source-map-support@0.5.21: resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} @@ -1933,24 +1922,16 @@ packages: engines: {node: '>=4.2.0'} hasBin: true - typescript@5.5.4: - resolution: {integrity: sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==} - engines: {node: '>=14.17'} - hasBin: true - typical@2.6.1: resolution: {integrity: sha512-ofhi8kjIje6npGozTip9Fr8iecmYfEbS06i0JnIg+rh51KakryWF4+jX8lLKZVhy6N+ID45WYSFCxPOdTWCzNg==} - uglify-js@3.19.1: - resolution: {integrity: sha512-y/2wiW+ceTYR2TSSptAhfnEtpLaQ4Ups5zrjB2d3kuVxHj16j/QJwPl5PvuGy9uARb39J0+iKxcRPvtpsx4A4A==} + uglify-js@3.19.3: + resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} engines: {node: '>=0.8.0'} hasBin: true - undici-types@5.26.5: - resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} - - undici-types@6.13.0: - resolution: {integrity: sha512-xtFJHudx8S2DSoujjMd1WeWvn7KKWFRESZTMeL1RptAYERu29D6jphMjjY+vn96jvN3kVPDNxU/E13VTaXj6jg==} + undici-types@6.19.8: + resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} undici@5.28.4: resolution: {integrity: sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==} @@ -2068,38 +2049,19 @@ packages: snapshots: - '@babel/code-frame@7.24.7': + '@babel/code-frame@7.25.7': dependencies: - '@babel/highlight': 7.24.7 - picocolors: 1.0.1 + '@babel/highlight': 7.25.7 + picocolors: 1.1.1 - '@babel/helper-validator-identifier@7.24.7': {} + '@babel/helper-validator-identifier@7.25.7': {} - '@babel/highlight@7.24.7': + '@babel/highlight@7.25.7': dependencies: - '@babel/helper-validator-identifier': 7.24.7 + '@babel/helper-validator-identifier': 7.25.7 chalk: 2.4.2 js-tokens: 4.0.0 - picocolors: 1.0.1 - - '@email-wallet/contracts@https://gitpkg.now.sh/zkemail/email-wallet/packages/contracts?8ae807dfcfe617a1a3e4b5342a667f0f595b8c82(ethers@5.7.2)(hardhat@2.22.8(typescript@5.5.4))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.4))': - dependencies: - '@openzeppelin/contracts': 4.9.6 - '@openzeppelin/contracts-upgradeable': 4.9.6 - '@uniswap/v3-core': https://codeload.github.com/Uniswap/v3-core/tar.gz/6562c52e8f75f0c10f9deaf44861847585fc8129 - '@uniswap/v3-periphery': https://codeload.github.com/Uniswap/v3-periphery/tar.gz/0682387198a24c7cd63566a2c58398533860a5d1 - '@zk-email/contracts': 4.1.0 - accountabstraction: https://codeload.github.com/eth-infinitism/account-abstraction/tar.gz/7174d6d845618dbd11cee68eefa715f5263690b6(ethers@5.7.2)(hardhat@2.22.8(typescript@5.5.4))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.4)) - solady: 0.0.123 - transitivePeerDependencies: - - bufferutil - - encoding - - ethers - - hardhat - - lodash - - supports-color - - typechain - - utf-8-validate + picocolors: 1.1.1 '@ethereumjs/rlp@4.0.1': {} @@ -2370,6 +2332,11 @@ snapshots: dependencies: ethers: 5.7.2 + '@matterlabs/zksync-contracts@0.6.1(@openzeppelin/contracts-upgradeable@5.0.1(@openzeppelin/contracts@5.1.0))(@openzeppelin/contracts@5.1.0)': + dependencies: + '@openzeppelin/contracts': 5.1.0 + '@openzeppelin/contracts-upgradeable': 5.0.1(@openzeppelin/contracts@5.1.0) + '@metamask/eth-sig-util@4.0.1': dependencies: ethereumjs-abi: 0.6.8 @@ -2386,6 +2353,8 @@ snapshots: '@noble/hashes@1.4.0': {} + '@noble/hashes@1.5.0': {} + '@noble/secp256k1@1.7.1': {} '@nodelib/fs.scandir@2.1.5': @@ -2402,29 +2371,29 @@ snapshots: '@nomad-xyz/excessively-safe-call@https://codeload.github.com/nomad-xyz/ExcessivelySafeCall/tar.gz/81cd99ce3e69117d665d7601c330ea03b97acce0': {} - '@nomicfoundation/edr-darwin-arm64@0.5.2': {} + '@nomicfoundation/edr-darwin-arm64@0.6.4': {} - '@nomicfoundation/edr-darwin-x64@0.5.2': {} + '@nomicfoundation/edr-darwin-x64@0.6.4': {} - '@nomicfoundation/edr-linux-arm64-gnu@0.5.2': {} + '@nomicfoundation/edr-linux-arm64-gnu@0.6.4': {} - '@nomicfoundation/edr-linux-arm64-musl@0.5.2': {} + '@nomicfoundation/edr-linux-arm64-musl@0.6.4': {} - '@nomicfoundation/edr-linux-x64-gnu@0.5.2': {} + '@nomicfoundation/edr-linux-x64-gnu@0.6.4': {} - '@nomicfoundation/edr-linux-x64-musl@0.5.2': {} + '@nomicfoundation/edr-linux-x64-musl@0.6.4': {} - '@nomicfoundation/edr-win32-x64-msvc@0.5.2': {} + '@nomicfoundation/edr-win32-x64-msvc@0.6.4': {} - '@nomicfoundation/edr@0.5.2': + '@nomicfoundation/edr@0.6.4': dependencies: - '@nomicfoundation/edr-darwin-arm64': 0.5.2 - '@nomicfoundation/edr-darwin-x64': 0.5.2 - '@nomicfoundation/edr-linux-arm64-gnu': 0.5.2 - '@nomicfoundation/edr-linux-arm64-musl': 0.5.2 - '@nomicfoundation/edr-linux-x64-gnu': 0.5.2 - '@nomicfoundation/edr-linux-x64-musl': 0.5.2 - '@nomicfoundation/edr-win32-x64-msvc': 0.5.2 + '@nomicfoundation/edr-darwin-arm64': 0.6.4 + '@nomicfoundation/edr-darwin-x64': 0.6.4 + '@nomicfoundation/edr-linux-arm64-gnu': 0.6.4 + '@nomicfoundation/edr-linux-arm64-musl': 0.6.4 + '@nomicfoundation/edr-linux-x64-gnu': 0.6.4 + '@nomicfoundation/edr-linux-x64-musl': 0.6.4 + '@nomicfoundation/edr-win32-x64-msvc': 0.6.4 '@nomicfoundation/ethereumjs-common@4.0.4': dependencies: @@ -2477,33 +2446,29 @@ snapshots: '@nomicfoundation/solidity-analyzer-linux-x64-musl': 0.1.2 '@nomicfoundation/solidity-analyzer-win32-x64-msvc': 0.1.2 - '@nomiclabs/hardhat-etherscan@2.1.8(hardhat@2.22.8(typescript@5.5.4))': + '@nomiclabs/hardhat-etherscan@2.1.8(hardhat@2.22.14(typescript@4.9.5))': dependencies: '@ethersproject/abi': 5.7.0 '@ethersproject/address': 5.7.0 cbor: 5.2.0 - debug: 4.3.6(supports-color@8.1.1) + debug: 4.3.7(supports-color@8.1.1) fs-extra: 7.0.1 - hardhat: 2.22.8(typescript@5.5.4) + hardhat: 2.22.14(typescript@4.9.5) node-fetch: 2.7.0 semver: 6.3.1 transitivePeerDependencies: - encoding - supports-color - '@openzeppelin/contracts-upgradeable@4.9.6': {} - - '@openzeppelin/contracts-upgradeable@5.0.1(@openzeppelin/contracts@5.0.2)': + '@openzeppelin/contracts-upgradeable@5.0.1(@openzeppelin/contracts@5.1.0)': dependencies: - '@openzeppelin/contracts': 5.0.2 - - '@openzeppelin/contracts@3.4.2-solc-0.7': {} + '@openzeppelin/contracts': 5.1.0 '@openzeppelin/contracts@4.9.6': {} '@openzeppelin/contracts@5.0.1': {} - '@openzeppelin/contracts@5.0.2': {} + '@openzeppelin/contracts@5.1.0': {} '@pnpm/config.env-replace@1.1.0': {} @@ -2511,29 +2476,29 @@ snapshots: dependencies: graceful-fs: 4.2.10 - '@pnpm/npm-conf@2.3.0': + '@pnpm/npm-conf@2.3.1': dependencies: '@pnpm/config.env-replace': 1.1.0 '@pnpm/network.ca-file': 1.0.2 config-chain: 1.1.13 - '@prb/math@4.0.3': {} + '@prb/math@4.1.0': {} '@rhinestone/checknsignatures@https://codeload.github.com/rhinestonewtf/checknsignatures/tar.gz/7ff44ef46da1266374e6a98e6cf69d727d7c357d': dependencies: - forge-std: https://codeload.github.com/foundry-rs/forge-std/tar.gz/4695fac44b2934aaa6d7150e2eaf0256fdc566a7 - solady: https://codeload.github.com/vectorized/solady/tar.gz/4363564a984779b7eec3bff00ab1de3a9db4e2d5 + forge-std: https://codeload.github.com/foundry-rs/forge-std/tar.gz/1eea5bae12ae557d589f9f0f0edae2faa47cb262 + solady: https://codeload.github.com/vectorized/solady/tar.gz/a83af63d575ed5d73e8c32830f8b50fc35591213 - '@rhinestone/erc4337-validation@0.0.1-alpha.2(ethers@5.7.2)(hardhat@2.22.8(typescript@5.5.4))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.4))(typescript@5.5.4)': + '@rhinestone/erc4337-validation@0.0.1-alpha.2(ethers@5.7.2)(hardhat@2.22.14(typescript@4.9.5))(lodash@4.17.21)(typechain@5.2.0(typescript@4.9.5))(typescript@4.9.5)': dependencies: '@openzeppelin/contracts': 5.0.1 - account-abstraction: accountabstraction@https://codeload.github.com/kopy-kat/account-abstraction/tar.gz/c5887153fbfe3ed09b2637cac39873f96d676f38(ethers@5.7.2)(hardhat@2.22.8(typescript@5.5.4))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.4)) - account-abstraction-v0.6: accountabstraction@https://codeload.github.com/eth-infinitism/account-abstraction/tar.gz/7174d6d845618dbd11cee68eefa715f5263690b6(ethers@5.7.2)(hardhat@2.22.8(typescript@5.5.4))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.4)) + account-abstraction: accountabstraction@https://codeload.github.com/kopy-kat/account-abstraction/tar.gz/c5887153fbfe3ed09b2637cac39873f96d676f38(ethers@5.7.2)(hardhat@2.22.14(typescript@4.9.5))(lodash@4.17.21)(typechain@5.2.0(typescript@4.9.5)) + account-abstraction-v0.6: accountabstraction@https://codeload.github.com/eth-infinitism/account-abstraction/tar.gz/7174d6d845618dbd11cee68eefa715f5263690b6(ethers@5.7.2)(hardhat@2.22.14(typescript@4.9.5))(lodash@4.17.21)(typechain@5.2.0(typescript@4.9.5)) ds-test: https://codeload.github.com/dapphub/ds-test/tar.gz/e282159d5170298eb2455a6c05280ab5a73a4ef0 - forge-std: https://codeload.github.com/foundry-rs/forge-std/tar.gz/4695fac44b2934aaa6d7150e2eaf0256fdc566a7 + forge-std: https://codeload.github.com/foundry-rs/forge-std/tar.gz/1eea5bae12ae557d589f9f0f0edae2faa47cb262 prettier: 2.8.8 - solady: https://codeload.github.com/vectorized/solady/tar.gz/4363564a984779b7eec3bff00ab1de3a9db4e2d5 - solhint: 4.5.4(typescript@5.5.4) + solady: https://codeload.github.com/vectorized/solady/tar.gz/a83af63d575ed5d73e8c32830f8b50fc35591213 + solhint: 4.5.4(typescript@4.9.5) transitivePeerDependencies: - bufferutil - encoding @@ -2545,15 +2510,15 @@ snapshots: - typescript - utf-8-validate - '@rhinestone/erc4337-validation@0.0.1-alpha.5(ethers@5.7.2)(hardhat@2.22.8(typescript@5.5.4))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.4))': + '@rhinestone/erc4337-validation@0.0.1-alpha.5(ethers@5.7.2)(hardhat@2.22.14(typescript@4.9.5))(lodash@4.17.21)(typechain@5.2.0(typescript@4.9.5))': dependencies: '@openzeppelin/contracts': 5.0.1 - account-abstraction: accountabstraction@https://codeload.github.com/kopy-kat/account-abstraction/tar.gz/c5887153fbfe3ed09b2637cac39873f96d676f38(ethers@5.7.2)(hardhat@2.22.8(typescript@5.5.4))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.4)) - account-abstraction-v0.6: accountabstraction@https://codeload.github.com/eth-infinitism/account-abstraction/tar.gz/7174d6d845618dbd11cee68eefa715f5263690b6(ethers@5.7.2)(hardhat@2.22.8(typescript@5.5.4))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.4)) + account-abstraction: accountabstraction@https://codeload.github.com/kopy-kat/account-abstraction/tar.gz/c5887153fbfe3ed09b2637cac39873f96d676f38(ethers@5.7.2)(hardhat@2.22.14(typescript@4.9.5))(lodash@4.17.21)(typechain@5.2.0(typescript@4.9.5)) + account-abstraction-v0.6: accountabstraction@https://codeload.github.com/eth-infinitism/account-abstraction/tar.gz/7174d6d845618dbd11cee68eefa715f5263690b6(ethers@5.7.2)(hardhat@2.22.14(typescript@4.9.5))(lodash@4.17.21)(typechain@5.2.0(typescript@4.9.5)) ds-test: https://codeload.github.com/dapphub/ds-test/tar.gz/e282159d5170298eb2455a6c05280ab5a73a4ef0 - forge-std: https://codeload.github.com/foundry-rs/forge-std/tar.gz/4695fac44b2934aaa6d7150e2eaf0256fdc566a7 + forge-std: https://codeload.github.com/foundry-rs/forge-std/tar.gz/1eea5bae12ae557d589f9f0f0edae2faa47cb262 prettier: 2.8.8 - solady: https://codeload.github.com/vectorized/solady/tar.gz/4363564a984779b7eec3bff00ab1de3a9db4e2d5 + solady: https://codeload.github.com/vectorized/solady/tar.gz/a83af63d575ed5d73e8c32830f8b50fc35591213 transitivePeerDependencies: - bufferutil - encoding @@ -2564,11 +2529,11 @@ snapshots: - typechain - utf-8-validate - '@rhinestone/module-bases@https://codeload.github.com/rhinestonewtf/module-bases/tar.gz/2db71722f939ed7d76315fc94c1a85bcb09ce59b(ethers@5.7.2)(hardhat@2.22.8(typescript@5.5.4))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.4))': + '@rhinestone/module-bases@https://codeload.github.com/rhinestonewtf/module-bases/tar.gz/70f0833756653579be6a95ea48e49a5090e2ff48(ethers@5.7.2)(hardhat@2.22.14(typescript@4.9.5))(lodash@4.17.21)(typechain@5.2.0(typescript@4.9.5))': dependencies: - '@ERC4337/account-abstraction': accountabstraction@https://codeload.github.com/kopy-kat/account-abstraction/tar.gz/c5887153fbfe3ed09b2637cac39873f96d676f38(ethers@5.7.2)(hardhat@2.22.8(typescript@5.5.4))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.4)) - erc7579: erc7579-implementation@https://codeload.github.com/erc7579/erc7579-implementation/tar.gz/b3f8bcb2df3aae3217213ffa8b7a87c1eb42ec56(ethers@5.7.2)(hardhat@2.22.8(typescript@5.5.4))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.4)) - forge-std: https://codeload.github.com/foundry-rs/forge-std/tar.gz/4695fac44b2934aaa6d7150e2eaf0256fdc566a7 + '@ERC4337/account-abstraction': accountabstraction@https://codeload.github.com/kopy-kat/account-abstraction/tar.gz/c5887153fbfe3ed09b2637cac39873f96d676f38(ethers@5.7.2)(hardhat@2.22.14(typescript@4.9.5))(lodash@4.17.21)(typechain@5.2.0(typescript@4.9.5)) + erc7579: erc7579-implementation@https://codeload.github.com/erc7579/erc7579-implementation/tar.gz/b3f8bcb2df3aae3217213ffa8b7a87c1eb42ec56(ethers@5.7.2)(hardhat@2.22.14(typescript@4.9.5))(lodash@4.17.21)(typechain@5.2.0(typescript@4.9.5)) + forge-std: https://codeload.github.com/foundry-rs/forge-std/tar.gz/1eea5bae12ae557d589f9f0f0edae2faa47cb262 transitivePeerDependencies: - bufferutil - encoding @@ -2579,22 +2544,23 @@ snapshots: - typechain - utf-8-validate - '@rhinestone/modulekit@https://codeload.github.com/rhinestonewtf/modulekit/tar.gz/d2ed91a9f9c272a614aaf514afe3a5041fed1eb5(ethers@5.7.2)(hardhat@2.22.8(typescript@5.5.4))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.4))(typescript@5.5.4)': + '@rhinestone/modulekit@https://codeload.github.com/rhinestonewtf/modulekit/tar.gz/67a5c02dec798b25a3b2581d485d75e116b14af8(ethers@5.7.2)(hardhat@2.22.14(typescript@4.9.5))(lodash@4.17.21)(typechain@5.2.0(typescript@4.9.5))(typescript@4.9.5)': dependencies: - '@ERC4337/account-abstraction': accountabstraction@https://codeload.github.com/kopy-kat/account-abstraction/tar.gz/c5887153fbfe3ed09b2637cac39873f96d676f38(ethers@5.7.2)(hardhat@2.22.8(typescript@5.5.4))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.4)) - '@ERC4337/account-abstraction-v0.6': accountabstraction@https://codeload.github.com/eth-infinitism/account-abstraction/tar.gz/7174d6d845618dbd11cee68eefa715f5263690b6(ethers@5.7.2)(hardhat@2.22.8(typescript@5.5.4))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.4)) - '@prb/math': 4.0.3 - '@rhinestone/erc4337-validation': 0.0.1-alpha.5(ethers@5.7.2)(hardhat@2.22.8(typescript@5.5.4))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.4)) - '@rhinestone/module-bases': https://codeload.github.com/rhinestonewtf/module-bases/tar.gz/2db71722f939ed7d76315fc94c1a85bcb09ce59b(ethers@5.7.2)(hardhat@2.22.8(typescript@5.5.4))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.4)) - '@rhinestone/safe7579': https://codeload.github.com/rhinestonewtf/safe7579/tar.gz/33f110f08ed5fcab75c29d7cfb93f7f3e4da76a7(ethers@5.7.2)(hardhat@2.22.8(typescript@5.5.4))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.4))(typescript@5.5.4) + '@ERC4337/account-abstraction': accountabstraction@https://codeload.github.com/kopy-kat/account-abstraction/tar.gz/c5887153fbfe3ed09b2637cac39873f96d676f38(ethers@5.7.2)(hardhat@2.22.14(typescript@4.9.5))(lodash@4.17.21)(typechain@5.2.0(typescript@4.9.5)) + '@ERC4337/account-abstraction-v0.6': accountabstraction@https://codeload.github.com/eth-infinitism/account-abstraction/tar.gz/7174d6d845618dbd11cee68eefa715f5263690b6(ethers@5.7.2)(hardhat@2.22.14(typescript@4.9.5))(lodash@4.17.21)(typechain@5.2.0(typescript@4.9.5)) + '@prb/math': 4.1.0 + '@rhinestone/erc4337-validation': 0.0.1-alpha.5(ethers@5.7.2)(hardhat@2.22.14(typescript@4.9.5))(lodash@4.17.21)(typechain@5.2.0(typescript@4.9.5)) + '@rhinestone/module-bases': https://codeload.github.com/rhinestonewtf/module-bases/tar.gz/70f0833756653579be6a95ea48e49a5090e2ff48(ethers@5.7.2)(hardhat@2.22.14(typescript@4.9.5))(lodash@4.17.21)(typechain@5.2.0(typescript@4.9.5)) + '@rhinestone/registry': https://codeload.github.com/rhinestonewtf/registry/tar.gz/1371979a97293e0c6188afcd923784f6a718ae7d + '@rhinestone/safe7579': https://codeload.github.com/rhinestonewtf/safe7579/tar.gz/33f110f08ed5fcab75c29d7cfb93f7f3e4da76a7(ethers@5.7.2)(hardhat@2.22.14(typescript@4.9.5))(lodash@4.17.21)(typechain@5.2.0(typescript@4.9.5))(typescript@4.9.5) '@rhinestone/sentinellist': https://codeload.github.com/rhinestonewtf/sentinellist/tar.gz/67e42f0eb3cf355ddba5a017892f9cc28d924875 '@safe-global/safe-contracts': 1.4.1(ethers@5.7.2) '@zerodev/kernel': kernel@https://codeload.github.com/kopy-kat/kernel/tar.gz/acc457ce5169929ce3ce0f06a9540f85ccc8b25f ds-test: https://codeload.github.com/dapphub/ds-test/tar.gz/e282159d5170298eb2455a6c05280ab5a73a4ef0 - erc7579: erc7579-implementation@https://codeload.github.com/erc7579/erc7579-implementation/tar.gz/b3f8bcb2df3aae3217213ffa8b7a87c1eb42ec56(ethers@5.7.2)(hardhat@2.22.8(typescript@5.5.4))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.4)) + erc7579: erc7579-implementation@https://codeload.github.com/erc7579/erc7579-implementation/tar.gz/b3f8bcb2df3aae3217213ffa8b7a87c1eb42ec56(ethers@5.7.2)(hardhat@2.22.14(typescript@4.9.5))(lodash@4.17.21)(typechain@5.2.0(typescript@4.9.5)) excessively-safe-call: '@nomad-xyz/excessively-safe-call@https://codeload.github.com/nomad-xyz/ExcessivelySafeCall/tar.gz/81cd99ce3e69117d665d7601c330ea03b97acce0' - forge-std: https://codeload.github.com/foundry-rs/forge-std/tar.gz/4695fac44b2934aaa6d7150e2eaf0256fdc566a7 - solady: https://codeload.github.com/vectorized/solady/tar.gz/4363564a984779b7eec3bff00ab1de3a9db4e2d5 + forge-std: https://codeload.github.com/foundry-rs/forge-std/tar.gz/1eea5bae12ae557d589f9f0f0edae2faa47cb262 + solady: https://codeload.github.com/vectorized/solady/tar.gz/a83af63d575ed5d73e8c32830f8b50fc35591213 solarray: https://codeload.github.com/sablier-labs/solarray/tar.gz/6bf10cb34cdace52a3ba5fe437e78cc82df92684 transitivePeerDependencies: - bufferutil @@ -2607,19 +2573,25 @@ snapshots: - typescript - utf-8-validate - '@rhinestone/safe7579@https://codeload.github.com/rhinestonewtf/safe7579/tar.gz/33f110f08ed5fcab75c29d7cfb93f7f3e4da76a7(ethers@5.7.2)(hardhat@2.22.8(typescript@5.5.4))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.4))(typescript@5.5.4)': + '@rhinestone/registry@https://codeload.github.com/rhinestonewtf/registry/tar.gz/1371979a97293e0c6188afcd923784f6a718ae7d': dependencies: - '@ERC4337/account-abstraction': accountabstraction@https://codeload.github.com/kopy-kat/account-abstraction/tar.gz/c5887153fbfe3ed09b2637cac39873f96d676f38(ethers@5.7.2)(hardhat@2.22.8(typescript@5.5.4))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.4)) - '@ERC4337/account-abstraction-v0.6': accountabstraction@https://codeload.github.com/eth-infinitism/account-abstraction/tar.gz/7174d6d845618dbd11cee68eefa715f5263690b6(ethers@5.7.2)(hardhat@2.22.8(typescript@5.5.4))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.4)) + '@openzeppelin/contracts': 5.0.1 + forge-std: https://codeload.github.com/foundry-rs/forge-std/tar.gz/ae570fec082bfe1c1f45b0acca4a2b4f84d345ce + solady: https://codeload.github.com/vectorized/solady/tar.gz/9deb9ed36a27261a8745db5b7cd7f4cdc3b1cd4e + + '@rhinestone/safe7579@https://codeload.github.com/rhinestonewtf/safe7579/tar.gz/33f110f08ed5fcab75c29d7cfb93f7f3e4da76a7(ethers@5.7.2)(hardhat@2.22.14(typescript@4.9.5))(lodash@4.17.21)(typechain@5.2.0(typescript@4.9.5))(typescript@4.9.5)': + dependencies: + '@ERC4337/account-abstraction': accountabstraction@https://codeload.github.com/kopy-kat/account-abstraction/tar.gz/c5887153fbfe3ed09b2637cac39873f96d676f38(ethers@5.7.2)(hardhat@2.22.14(typescript@4.9.5))(lodash@4.17.21)(typechain@5.2.0(typescript@4.9.5)) + '@ERC4337/account-abstraction-v0.6': accountabstraction@https://codeload.github.com/eth-infinitism/account-abstraction/tar.gz/7174d6d845618dbd11cee68eefa715f5263690b6(ethers@5.7.2)(hardhat@2.22.14(typescript@4.9.5))(lodash@4.17.21)(typechain@5.2.0(typescript@4.9.5)) '@rhinestone/checknsignatures': https://codeload.github.com/rhinestonewtf/checknsignatures/tar.gz/7ff44ef46da1266374e6a98e6cf69d727d7c357d - '@rhinestone/erc4337-validation': 0.0.1-alpha.2(ethers@5.7.2)(hardhat@2.22.8(typescript@5.5.4))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.4))(typescript@5.5.4) - '@rhinestone/module-bases': https://codeload.github.com/rhinestonewtf/module-bases/tar.gz/2db71722f939ed7d76315fc94c1a85bcb09ce59b(ethers@5.7.2)(hardhat@2.22.8(typescript@5.5.4))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.4)) + '@rhinestone/erc4337-validation': 0.0.1-alpha.2(ethers@5.7.2)(hardhat@2.22.14(typescript@4.9.5))(lodash@4.17.21)(typechain@5.2.0(typescript@4.9.5))(typescript@4.9.5) + '@rhinestone/module-bases': https://codeload.github.com/rhinestonewtf/module-bases/tar.gz/70f0833756653579be6a95ea48e49a5090e2ff48(ethers@5.7.2)(hardhat@2.22.14(typescript@4.9.5))(lodash@4.17.21)(typechain@5.2.0(typescript@4.9.5)) '@rhinestone/sentinellist': https://codeload.github.com/rhinestonewtf/sentinellist/tar.gz/67e42f0eb3cf355ddba5a017892f9cc28d924875 '@safe-global/safe-contracts': 1.4.1(ethers@5.7.2) ds-test: https://codeload.github.com/dapphub/ds-test/tar.gz/e282159d5170298eb2455a6c05280ab5a73a4ef0 - erc7579: erc7579-implementation@https://codeload.github.com/erc7579/erc7579-implementation/tar.gz/b3f8bcb2df3aae3217213ffa8b7a87c1eb42ec56(ethers@5.7.2)(hardhat@2.22.8(typescript@5.5.4))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.4)) - forge-std: https://codeload.github.com/foundry-rs/forge-std/tar.gz/4695fac44b2934aaa6d7150e2eaf0256fdc566a7 - solady: https://codeload.github.com/vectorized/solady/tar.gz/4363564a984779b7eec3bff00ab1de3a9db4e2d5 + erc7579: erc7579-implementation@https://codeload.github.com/erc7579/erc7579-implementation/tar.gz/b3f8bcb2df3aae3217213ffa8b7a87c1eb42ec56(ethers@5.7.2)(hardhat@2.22.14(typescript@4.9.5))(lodash@4.17.21)(typechain@5.2.0(typescript@4.9.5)) + forge-std: https://codeload.github.com/foundry-rs/forge-std/tar.gz/1eea5bae12ae557d589f9f0f0edae2faa47cb262 + solady: https://codeload.github.com/vectorized/solady/tar.gz/a83af63d575ed5d73e8c32830f8b50fc35591213 solarray: https://codeload.github.com/sablier-labs/solarray/tar.gz/6bf10cb34cdace52a3ba5fe437e78cc82df92684 transitivePeerDependencies: - bufferutil @@ -2634,35 +2606,35 @@ snapshots: '@rhinestone/sentinellist@https://codeload.github.com/rhinestonewtf/sentinellist/tar.gz/67e42f0eb3cf355ddba5a017892f9cc28d924875': dependencies: - forge-std: https://codeload.github.com/foundry-rs/forge-std/tar.gz/4695fac44b2934aaa6d7150e2eaf0256fdc566a7 + forge-std: https://codeload.github.com/foundry-rs/forge-std/tar.gz/1eea5bae12ae557d589f9f0f0edae2faa47cb262 '@safe-global/safe-contracts@1.4.1(ethers@5.7.2)': dependencies: ethers: 5.7.2 - '@scure/base@1.1.7': {} + '@scure/base@1.1.9': {} '@scure/bip32@1.1.5': dependencies: '@noble/hashes': 1.2.0 '@noble/secp256k1': 1.7.1 - '@scure/base': 1.1.7 + '@scure/base': 1.1.9 '@scure/bip32@1.4.0': dependencies: '@noble/curves': 1.4.2 '@noble/hashes': 1.4.0 - '@scure/base': 1.1.7 + '@scure/base': 1.1.9 '@scure/bip39@1.1.1': dependencies: '@noble/hashes': 1.2.0 - '@scure/base': 1.1.7 + '@scure/base': 1.1.9 '@scure/bip39@1.3.0': dependencies: '@noble/hashes': 1.4.0 - '@scure/base': 1.1.7 + '@scure/base': 1.1.9 '@sentry/core@5.30.0': dependencies: @@ -2724,25 +2696,25 @@ snapshots: '@thehubbleproject/bls@0.5.1': dependencies: ethers: 5.7.2 - mcl-wasm: 1.5.0 + mcl-wasm: 1.7.0 transitivePeerDependencies: - bufferutil - utf-8-validate - '@typechain/hardhat@2.3.1(hardhat@2.22.8(typescript@5.5.4))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.4))': + '@typechain/hardhat@2.3.1(hardhat@2.22.14(typescript@4.9.5))(lodash@4.17.21)(typechain@5.2.0(typescript@4.9.5))': dependencies: fs-extra: 9.1.0 - hardhat: 2.22.8(typescript@5.5.4) + hardhat: 2.22.14(typescript@4.9.5) lodash: 4.17.21 - typechain: 5.2.0(typescript@5.5.4) + typechain: 5.2.0(typescript@4.9.5) '@types/bn.js@4.11.6': dependencies: - '@types/node': 22.1.0 + '@types/node': 22.7.8 - '@types/bn.js@5.1.5': + '@types/bn.js@5.1.6': dependencies: - '@types/node': 22.1.0 + '@types/node': 22.7.8 '@types/debug@4.1.12': dependencies: @@ -2751,7 +2723,7 @@ snapshots: '@types/glob@7.2.0': dependencies: '@types/minimatch': 5.1.2 - '@types/node': 22.1.0 + '@types/node': 22.7.8 '@types/http-cache-semantics@4.0.4': {} @@ -2763,70 +2735,57 @@ snapshots: '@types/ms@0.7.34': {} - '@types/node@20.14.14': + '@types/node@20.16.14': dependencies: - undici-types: 5.26.5 + undici-types: 6.19.8 - '@types/node@22.1.0': + '@types/node@22.7.8': dependencies: - undici-types: 6.13.0 + undici-types: 6.19.8 '@types/pbkdf2@3.1.2': dependencies: - '@types/node': 22.1.0 + '@types/node': 22.7.8 '@types/prettier@2.7.3': {} - '@types/qs@6.9.15': {} + '@types/qs@6.9.16': {} '@types/secp256k1@4.0.6': dependencies: - '@types/node': 22.1.0 - - '@uniswap/lib@4.0.1-alpha': {} - - '@uniswap/v2-core@1.0.1': {} + '@types/node': 22.7.8 - '@uniswap/v3-core@1.0.1': {} - - '@uniswap/v3-core@https://codeload.github.com/Uniswap/v3-core/tar.gz/6562c52e8f75f0c10f9deaf44861847585fc8129': {} - - '@uniswap/v3-periphery@https://codeload.github.com/Uniswap/v3-periphery/tar.gz/0682387198a24c7cd63566a2c58398533860a5d1': + '@zk-email/contracts@6.3.1': dependencies: - '@openzeppelin/contracts': 3.4.2-solc-0.7 - '@uniswap/lib': 4.0.1-alpha - '@uniswap/v2-core': 1.0.1 - '@uniswap/v3-core': 1.0.1 - base64-sol: 1.0.1 - - '@zk-email/contracts@4.1.0': - dependencies: - '@openzeppelin/contracts': 4.9.6 + '@openzeppelin/contracts': 5.1.0 + '@openzeppelin/contracts-upgradeable': 5.0.1(@openzeppelin/contracts@5.1.0) dotenv: 16.4.5 - '@zk-email/contracts@6.0.3': + '@zk-email/ether-email-auth-contracts@1.0.0': dependencies: - '@openzeppelin/contracts': 5.0.2 - dotenv: 16.4.5 - - '@zk-email/ether-email-auth@https://codeload.github.com/zkemail/ether-email-auth/tar.gz/b5694a9e0e49d07a862232f665dc4d0886c5a15f': {} + '@matterlabs/zksync-contracts': 0.6.1(@openzeppelin/contracts-upgradeable@5.0.1(@openzeppelin/contracts@5.1.0))(@openzeppelin/contracts@5.1.0) + '@openzeppelin/contracts': 5.1.0 + '@openzeppelin/contracts-upgradeable': 5.0.1(@openzeppelin/contracts@5.1.0) + '@zk-email/contracts': 6.3.1 + solady: 0.0.123 + solidity-stringutils: https://codeload.github.com/LayerZero-Labs/solidity-stringutils/tar.gz/eb21d6b502c2741145ab2a90f5f5b4fda9dfb218 abbrev@1.0.9: {} - accountabstraction@https://codeload.github.com/eth-infinitism/account-abstraction/tar.gz/6f02f5a28a20e804d0410b4b5b570dd4b076dcf9(ethers@5.7.2)(hardhat@2.22.8(typescript@5.5.4))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.4)): + accountabstraction@https://codeload.github.com/eth-infinitism/account-abstraction/tar.gz/6f02f5a28a20e804d0410b4b5b570dd4b076dcf9(ethers@5.7.2)(hardhat@2.22.14(typescript@4.9.5))(lodash@4.17.21)(typechain@5.2.0(typescript@4.9.5)): dependencies: - '@nomiclabs/hardhat-etherscan': 2.1.8(hardhat@2.22.8(typescript@5.5.4)) - '@openzeppelin/contracts': 5.0.2 + '@nomiclabs/hardhat-etherscan': 2.1.8(hardhat@2.22.14(typescript@4.9.5)) + '@openzeppelin/contracts': 5.1.0 '@thehubbleproject/bls': 0.5.1 - '@typechain/hardhat': 2.3.1(hardhat@2.22.8(typescript@5.5.4))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.4)) + '@typechain/hardhat': 2.3.1(hardhat@2.22.14(typescript@4.9.5))(lodash@4.17.21)(typechain@5.2.0(typescript@4.9.5)) '@types/debug': 4.1.12 '@types/mocha': 9.1.1 - debug: 4.3.6(supports-color@8.1.1) + debug: 4.3.7(supports-color@8.1.1) ethereumjs-util: 7.1.5 ethereumjs-wallet: 1.0.2 hardhat-deploy: 0.11.45 - hardhat-deploy-ethers: 0.3.0-beta.13(ethers@5.7.2)(hardhat@2.22.8(typescript@5.5.4)) - solidity-coverage: 0.8.12(hardhat@2.22.8(typescript@5.5.4)) + hardhat-deploy-ethers: 0.3.0-beta.13(ethers@5.7.2)(hardhat@2.22.14(typescript@4.9.5)) + solidity-coverage: 0.8.13(hardhat@2.22.14(typescript@4.9.5)) source-map-support: 0.5.21 table: 6.8.2 typescript: 4.9.5 @@ -2840,19 +2799,19 @@ snapshots: - typechain - utf-8-validate - accountabstraction@https://codeload.github.com/eth-infinitism/account-abstraction/tar.gz/7174d6d845618dbd11cee68eefa715f5263690b6(ethers@5.7.2)(hardhat@2.22.8(typescript@5.5.4))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.4)): + accountabstraction@https://codeload.github.com/eth-infinitism/account-abstraction/tar.gz/7174d6d845618dbd11cee68eefa715f5263690b6(ethers@5.7.2)(hardhat@2.22.14(typescript@4.9.5))(lodash@4.17.21)(typechain@5.2.0(typescript@4.9.5)): dependencies: '@gnosis.pm/safe-contracts': 1.3.0(ethers@5.7.2) - '@nomiclabs/hardhat-etherscan': 2.1.8(hardhat@2.22.8(typescript@5.5.4)) + '@nomiclabs/hardhat-etherscan': 2.1.8(hardhat@2.22.14(typescript@4.9.5)) '@openzeppelin/contracts': 4.9.6 '@thehubbleproject/bls': 0.5.1 - '@typechain/hardhat': 2.3.1(hardhat@2.22.8(typescript@5.5.4))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.4)) + '@typechain/hardhat': 2.3.1(hardhat@2.22.14(typescript@4.9.5))(lodash@4.17.21)(typechain@5.2.0(typescript@4.9.5)) '@types/mocha': 9.1.1 ethereumjs-util: 7.1.5 ethereumjs-wallet: 1.0.2 hardhat-deploy: 0.11.45 - hardhat-deploy-ethers: 0.3.0-beta.13(ethers@5.7.2)(hardhat@2.22.8(typescript@5.5.4)) - solidity-coverage: 0.8.12(hardhat@2.22.8(typescript@5.5.4)) + hardhat-deploy-ethers: 0.3.0-beta.13(ethers@5.7.2)(hardhat@2.22.14(typescript@4.9.5)) + solidity-coverage: 0.8.13(hardhat@2.22.14(typescript@4.9.5)) source-map-support: 0.5.21 table: 6.8.2 typescript: 4.9.5 @@ -2866,20 +2825,20 @@ snapshots: - typechain - utf-8-validate - accountabstraction@https://codeload.github.com/kopy-kat/account-abstraction/tar.gz/c5887153fbfe3ed09b2637cac39873f96d676f38(ethers@5.7.2)(hardhat@2.22.8(typescript@5.5.4))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.4)): + accountabstraction@https://codeload.github.com/kopy-kat/account-abstraction/tar.gz/c5887153fbfe3ed09b2637cac39873f96d676f38(ethers@5.7.2)(hardhat@2.22.14(typescript@4.9.5))(lodash@4.17.21)(typechain@5.2.0(typescript@4.9.5)): dependencies: - '@nomiclabs/hardhat-etherscan': 2.1.8(hardhat@2.22.8(typescript@5.5.4)) - '@openzeppelin/contracts': 5.0.2 + '@nomiclabs/hardhat-etherscan': 2.1.8(hardhat@2.22.14(typescript@4.9.5)) + '@openzeppelin/contracts': 5.1.0 '@thehubbleproject/bls': 0.5.1 - '@typechain/hardhat': 2.3.1(hardhat@2.22.8(typescript@5.5.4))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.4)) + '@typechain/hardhat': 2.3.1(hardhat@2.22.14(typescript@4.9.5))(lodash@4.17.21)(typechain@5.2.0(typescript@4.9.5)) '@types/debug': 4.1.12 '@types/mocha': 9.1.1 - debug: 4.3.6(supports-color@8.1.1) + debug: 4.3.7(supports-color@8.1.1) ethereumjs-util: 7.1.5 ethereumjs-wallet: 1.0.2 hardhat-deploy: 0.11.45 - hardhat-deploy-ethers: 0.3.0-beta.13(ethers@5.7.2)(hardhat@2.22.8(typescript@5.5.4)) - solidity-coverage: 0.8.12(hardhat@2.22.8(typescript@5.5.4)) + hardhat-deploy-ethers: 0.3.0-beta.13(ethers@5.7.2)(hardhat@2.22.14(typescript@4.9.5)) + solidity-coverage: 0.8.13(hardhat@2.22.14(typescript@4.9.5)) source-map-support: 0.5.21 table: 6.8.2 typescript: 4.9.5 @@ -2901,7 +2860,7 @@ snapshots: agent-base@6.0.2: dependencies: - debug: 4.3.6(supports-color@8.1.1) + debug: 4.3.7(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -2920,7 +2879,7 @@ snapshots: ajv@8.17.1: dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.0.1 + fast-uri: 3.0.3 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 @@ -2980,9 +2939,9 @@ snapshots: at-least-node@1.0.0: {} - axios@0.21.4(debug@4.3.6): + axios@0.21.4(debug@4.3.7): dependencies: - follow-redirects: 1.15.6(debug@4.3.6) + follow-redirects: 1.15.9(debug@4.3.7) transitivePeerDependencies: - debug @@ -2992,8 +2951,6 @@ snapshots: dependencies: safe-buffer: 5.2.1 - base64-sol@1.0.1: {} - bech32@1.1.4: {} bignumber.js@9.1.2: {} @@ -3113,6 +3070,10 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + chokidar@4.0.1: + dependencies: + readdirp: 4.0.2 + ci-info@2.0.0: {} cipher-base@1.0.4: @@ -3167,14 +3128,14 @@ snapshots: cookie@0.4.2: {} - cosmiconfig@8.3.6(typescript@5.5.4): + cosmiconfig@8.3.6(typescript@4.9.5): dependencies: import-fresh: 3.3.0 js-yaml: 4.1.0 parse-json: 5.2.0 path-type: 4.0.0 optionalDependencies: - typescript: 5.5.4 + typescript: 4.9.5 create-hash@1.2.0: dependencies: @@ -3195,9 +3156,9 @@ snapshots: death@1.1.0: {} - debug@4.3.6(supports-color@8.1.1): + debug@4.3.7(supports-color@8.1.1): dependencies: - ms: 2.1.2 + ms: 2.1.3 optionalDependencies: supports-color: 8.1.1 @@ -3247,7 +3208,7 @@ snapshots: minimalistic-assert: 1.0.1 minimalistic-crypto-utils: 1.0.1 - elliptic@6.5.6: + elliptic@6.5.7: dependencies: bn.js: 4.12.0 brorand: 1.1.0 @@ -3257,26 +3218,6 @@ snapshots: minimalistic-assert: 1.0.1 minimalistic-crypto-utils: 1.0.1 - email-wallet-sdk@https://codeload.github.com/zkemail/email-wallet-sdk/tar.gz/a8c200e4855a81adc7b9493afa92a898ffcb8c56(ethers@5.7.2)(hardhat@2.22.8(typescript@5.5.4))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.4)): - dependencies: - '@email-wallet/contracts': https://gitpkg.now.sh/zkemail/email-wallet/packages/contracts?8ae807dfcfe617a1a3e4b5342a667f0f595b8c82(ethers@5.7.2)(hardhat@2.22.8(typescript@5.5.4))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.4)) - '@openzeppelin/contracts': 4.9.6 - '@openzeppelin/contracts-upgradeable': 4.9.6 - '@uniswap/v3-core': https://codeload.github.com/Uniswap/v3-core/tar.gz/6562c52e8f75f0c10f9deaf44861847585fc8129 - '@uniswap/v3-periphery': https://codeload.github.com/Uniswap/v3-periphery/tar.gz/0682387198a24c7cd63566a2c58398533860a5d1 - '@zk-email/contracts': 4.1.0 - solady: 0.0.123 - solidity-stringutils: https://codeload.github.com/Arachnid/solidity-stringutils/tar.gz/4b2fcc43fa0426e19ce88b1f1ec16f5903a2e461 - transitivePeerDependencies: - - bufferutil - - encoding - - ethers - - hardhat - - lodash - - supports-color - - typechain - - utf-8-validate - emoji-regex@8.0.0: {} encode-utf8@1.0.3: {} @@ -3288,15 +3229,15 @@ snapshots: env-paths@2.2.1: {} - era-contracts@https://codeload.github.com/matter-labs/era-contracts/tar.gz/446d391d34bdb48255d5f8fef8a8248925fc98b9: {} + era-contracts@https://codeload.github.com/matter-labs/era-contracts/tar.gz/aafee035db892689df3f7afe4b89fd6467a39313: {} - erc7579-implementation@https://codeload.github.com/erc7579/erc7579-implementation/tar.gz/b3f8bcb2df3aae3217213ffa8b7a87c1eb42ec56(ethers@5.7.2)(hardhat@2.22.8(typescript@5.5.4))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.4)): + erc7579-implementation@https://codeload.github.com/erc7579/erc7579-implementation/tar.gz/b3f8bcb2df3aae3217213ffa8b7a87c1eb42ec56(ethers@5.7.2)(hardhat@2.22.14(typescript@4.9.5))(lodash@4.17.21)(typechain@5.2.0(typescript@4.9.5)): dependencies: '@rhinestone/sentinellist': https://codeload.github.com/rhinestonewtf/sentinellist/tar.gz/67e42f0eb3cf355ddba5a017892f9cc28d924875 - account-abstraction: accountabstraction@https://codeload.github.com/eth-infinitism/account-abstraction/tar.gz/6f02f5a28a20e804d0410b4b5b570dd4b076dcf9(ethers@5.7.2)(hardhat@2.22.8(typescript@5.5.4))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.4)) + account-abstraction: accountabstraction@https://codeload.github.com/eth-infinitism/account-abstraction/tar.gz/6f02f5a28a20e804d0410b4b5b570dd4b076dcf9(ethers@5.7.2)(hardhat@2.22.14(typescript@4.9.5))(lodash@4.17.21)(typechain@5.2.0(typescript@4.9.5)) ds-test: https://codeload.github.com/dapphub/ds-test/tar.gz/e282159d5170298eb2455a6c05280ab5a73a4ef0 - forge-std: https://codeload.github.com/foundry-rs/forge-std/tar.gz/4695fac44b2934aaa6d7150e2eaf0256fdc566a7 - solady: https://codeload.github.com/vectorized/solady/tar.gz/4363564a984779b7eec3bff00ab1de3a9db4e2d5 + forge-std: https://codeload.github.com/foundry-rs/forge-std/tar.gz/1eea5bae12ae557d589f9f0f0edae2faa47cb262 + solady: https://codeload.github.com/vectorized/solady/tar.gz/a83af63d575ed5d73e8c32830f8b50fc35591213 transitivePeerDependencies: - bufferutil - encoding @@ -3317,7 +3258,7 @@ snapshots: es-errors@1.3.0: {} - escalade@3.1.2: {} + escalade@3.2.0: {} escape-string-regexp@1.0.5: {} @@ -3342,7 +3283,7 @@ snapshots: ethereum-bloom-filters@1.2.0: dependencies: - '@noble/hashes': 1.4.0 + '@noble/hashes': 1.5.0 ethereum-cryptography@0.1.3: dependencies: @@ -3359,7 +3300,7 @@ snapshots: randombytes: 2.1.0 safe-buffer: 5.2.1 scrypt-js: 3.0.1 - secp256k1: 4.0.3 + secp256k1: 4.0.4 setimmediate: 1.0.5 ethereum-cryptography@1.2.0: @@ -3386,14 +3327,14 @@ snapshots: '@types/bn.js': 4.11.6 bn.js: 4.12.0 create-hash: 1.2.0 - elliptic: 6.5.6 + elliptic: 6.5.7 ethereum-cryptography: 0.1.3 ethjs-util: 0.1.6 rlp: 2.2.7 ethereumjs-util@7.1.5: dependencies: - '@types/bn.js': 5.1.5 + '@types/bn.js': 5.1.6 bn.js: 5.2.1 create-hash: 1.2.0 ethereum-cryptography: 0.1.3 @@ -3471,13 +3412,13 @@ snapshots: '@nodelib/fs.walk': 1.2.8 glob-parent: 5.1.2 merge2: 1.4.1 - micromatch: 4.0.7 + micromatch: 4.0.8 fast-json-stable-stringify@2.1.0: {} fast-levenshtein@2.0.6: {} - fast-uri@3.0.1: {} + fast-uri@3.0.3: {} fastq@1.17.1: dependencies: @@ -3507,15 +3448,17 @@ snapshots: dependencies: imul: 1.0.1 - follow-redirects@1.15.6(debug@4.3.6): + follow-redirects@1.15.9(debug@4.3.7): optionalDependencies: - debug: 4.3.6(supports-color@8.1.1) + debug: 4.3.7(supports-color@8.1.1) + + forge-std@https://codeload.github.com/foundry-rs/forge-std/tar.gz/1eea5bae12ae557d589f9f0f0edae2faa47cb262: {} - forge-std@https://codeload.github.com/foundry-rs/forge-std/tar.gz/4695fac44b2934aaa6d7150e2eaf0256fdc566a7: {} + forge-std@https://codeload.github.com/foundry-rs/forge-std/tar.gz/ae570fec082bfe1c1f45b0acca4a2b4f84d345ce: {} form-data-encoder@2.1.4: {} - form-data@4.0.0: + form-data@4.0.1: dependencies: asynckit: 0.4.0 combined-stream: 1.0.8 @@ -3627,7 +3570,7 @@ snapshots: dir-glob: 3.0.1 fast-glob: 3.3.2 glob: 7.2.3 - ignore: 5.3.1 + ignore: 5.3.2 merge2: 1.4.1 slash: 3.0.0 @@ -3660,12 +3603,12 @@ snapshots: source-map: 0.6.1 wordwrap: 1.0.0 optionalDependencies: - uglify-js: 3.19.1 + uglify-js: 3.19.3 - hardhat-deploy-ethers@0.3.0-beta.13(ethers@5.7.2)(hardhat@2.22.8(typescript@5.5.4)): + hardhat-deploy-ethers@0.3.0-beta.13(ethers@5.7.2)(hardhat@2.22.14(typescript@4.9.5)): dependencies: ethers: 5.7.2 - hardhat: 2.22.8(typescript@5.5.4) + hardhat: 2.22.14(typescript@4.9.5) hardhat-deploy@0.11.45: dependencies: @@ -3680,14 +3623,14 @@ snapshots: '@ethersproject/solidity': 5.7.0 '@ethersproject/transactions': 5.7.0 '@ethersproject/wallet': 5.7.0 - '@types/qs': 6.9.15 - axios: 0.21.4(debug@4.3.6) + '@types/qs': 6.9.16 + axios: 0.21.4(debug@4.3.7) chalk: 4.1.2 chokidar: 3.6.0 - debug: 4.3.6(supports-color@8.1.1) + debug: 4.3.7(supports-color@8.1.1) enquirer: 2.4.1 ethers: 5.7.2 - form-data: 4.0.0 + form-data: 4.0.1 fs-extra: 10.1.0 match-all: 1.2.6 murmur-128: 0.2.1 @@ -3698,26 +3641,26 @@ snapshots: - supports-color - utf-8-validate - hardhat@2.22.8(typescript@5.5.4): + hardhat@2.22.14(typescript@4.9.5): dependencies: '@ethersproject/abi': 5.7.0 '@metamask/eth-sig-util': 4.0.1 - '@nomicfoundation/edr': 0.5.2 + '@nomicfoundation/edr': 0.6.4 '@nomicfoundation/ethereumjs-common': 4.0.4 '@nomicfoundation/ethereumjs-tx': 5.0.4 '@nomicfoundation/ethereumjs-util': 9.0.4 '@nomicfoundation/solidity-analyzer': 0.1.2 '@sentry/node': 5.30.0 - '@types/bn.js': 5.1.5 + '@types/bn.js': 5.1.6 '@types/lru-cache': 5.1.1 adm-zip: 0.4.16 aggregate-error: 3.1.0 ansi-escapes: 4.3.2 boxen: 5.1.2 chalk: 2.4.2 - chokidar: 3.6.0 + chokidar: 4.0.1 ci-info: 2.0.0 - debug: 4.3.6(supports-color@8.1.1) + debug: 4.3.7(supports-color@8.1.1) enquirer: 2.4.1 env-paths: 2.2.1 ethereum-cryptography: 1.2.0 @@ -3728,15 +3671,16 @@ snapshots: glob: 7.2.0 immutable: 4.3.7 io-ts: 1.10.4 + json-stream-stringify: 3.1.6 keccak: 3.0.4 lodash: 4.17.21 mnemonist: 0.38.5 - mocha: 10.7.0 + mocha: 10.7.3 p-map: 4.0.0 raw-body: 2.5.2 resolve: 1.17.0 semver: 6.3.1 - solc: 0.8.26(debug@4.3.6) + solc: 0.8.26(debug@4.3.7) source-map-support: 0.5.21 stacktrace-parser: 0.1.10 tsort: 0.0.1 @@ -3744,7 +3688,7 @@ snapshots: uuid: 8.3.2 ws: 7.5.10 optionalDependencies: - typescript: 5.5.4 + typescript: 4.9.5 transitivePeerDependencies: - bufferutil - c-kzg @@ -3808,7 +3752,7 @@ snapshots: https-proxy-agent@5.0.1: dependencies: agent-base: 6.0.2 - debug: 4.3.6(supports-color@8.1.1) + debug: 4.3.7(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -3816,7 +3760,7 @@ snapshots: dependencies: safer-buffer: 2.1.2 - ignore@5.3.1: {} + ignore@5.3.2: {} immutable@4.3.7: {} @@ -3850,7 +3794,7 @@ snapshots: dependencies: binary-extensions: 2.3.0 - is-core-module@2.15.0: + is-core-module@2.15.1: dependencies: hasown: 2.0.2 @@ -3893,6 +3837,8 @@ snapshots: json-schema-traverse@1.0.0: {} + json-stream-stringify@3.1.6: {} + jsonfile@4.0.0: optionalDependencies: graceful-fs: 4.2.11 @@ -3908,7 +3854,7 @@ snapshots: keccak@3.0.4: dependencies: node-addon-api: 2.0.2 - node-gyp-build: 4.8.1 + node-gyp-build: 4.8.2 readable-stream: 3.6.2 kernel@https://codeload.github.com/kopy-kat/kernel/tar.gz/acc457ce5169929ce3ce0f06a9540f85ccc8b25f: {} @@ -3954,9 +3900,9 @@ snapshots: match-all@1.2.6: {} - mcl-wasm@1.5.0: + mcl-wasm@1.7.0: dependencies: - '@types/node': 20.14.14 + '@types/node': 20.16.14 md5.js@1.3.5: dependencies: @@ -3970,7 +3916,7 @@ snapshots: micro-ftch@0.3.1: {} - micromatch@4.0.7: + micromatch@4.0.8: dependencies: braces: 3.0.3 picomatch: 2.3.1 @@ -4009,12 +3955,12 @@ snapshots: dependencies: obliterator: 2.0.4 - mocha@10.7.0: + mocha@10.7.3: dependencies: ansi-colors: 4.1.3 browser-stdout: 1.3.1 chokidar: 3.6.0 - debug: 4.3.6(supports-color@8.1.1) + debug: 4.3.7(supports-color@8.1.1) diff: 5.2.0 escape-string-regexp: 4.0.0 find-up: 5.0.0 @@ -4032,8 +3978,6 @@ snapshots: yargs-parser: 20.2.9 yargs-unparser: 2.0.0 - ms@2.1.2: {} - ms@2.1.3: {} murmur-128@0.2.1: @@ -4046,6 +3990,8 @@ snapshots: node-addon-api@2.0.2: {} + node-addon-api@5.1.0: {} + node-emoji@1.11.0: dependencies: lodash: 4.17.21 @@ -4054,7 +4000,7 @@ snapshots: dependencies: whatwg-url: 5.0.0 - node-gyp-build@4.8.1: {} + node-gyp-build@4.8.2: {} nofilter@1.0.4: {} @@ -4127,7 +4073,7 @@ snapshots: parse-json@5.2.0: dependencies: - '@babel/code-frame': 7.24.7 + '@babel/code-frame': 7.25.7 error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 @@ -4150,7 +4096,7 @@ snapshots: safe-buffer: 5.2.1 sha.js: 2.4.11 - picocolors@1.0.1: {} + picocolors@1.1.1: {} picomatch@2.3.1: {} @@ -4202,6 +4148,8 @@ snapshots: dependencies: picomatch: 2.3.1 + readdirp@4.0.2: {} + rechoir@0.6.2: dependencies: resolve: 1.22.8 @@ -4212,7 +4160,7 @@ snapshots: registry-auth-token@5.0.2: dependencies: - '@pnpm/npm-conf': 2.3.0 + '@pnpm/npm-conf': 2.3.1 registry-url@6.0.1: dependencies: @@ -4234,7 +4182,7 @@ snapshots: resolve@1.22.8: dependencies: - is-core-module: 2.15.0 + is-core-module: 2.15.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 @@ -4280,11 +4228,11 @@ snapshots: scrypt-js@3.0.1: {} - secp256k1@4.0.3: + secp256k1@4.0.4: dependencies: - elliptic: 6.5.6 - node-addon-api: 2.0.2 - node-gyp-build: 4.8.1 + elliptic: 6.5.7 + node-addon-api: 5.1.0 + node-gyp-build: 4.8.2 semver@5.7.2: {} @@ -4337,15 +4285,17 @@ snapshots: solady@0.0.123: {} - solady@https://codeload.github.com/vectorized/solady/tar.gz/4363564a984779b7eec3bff00ab1de3a9db4e2d5: {} + solady@https://codeload.github.com/vectorized/solady/tar.gz/9deb9ed36a27261a8745db5b7cd7f4cdc3b1cd4e: {} + + solady@https://codeload.github.com/vectorized/solady/tar.gz/a83af63d575ed5d73e8c32830f8b50fc35591213: {} solarray@https://codeload.github.com/sablier-labs/solarray/tar.gz/6bf10cb34cdace52a3ba5fe437e78cc82df92684: {} - solc@0.8.26(debug@4.3.6): + solc@0.8.26(debug@4.3.7): dependencies: command-exists: 1.2.9 commander: 8.3.0 - follow-redirects: 1.15.6(debug@4.3.6) + follow-redirects: 1.15.9(debug@4.3.7) js-sha3: 0.8.0 memorystream: 0.3.1 semver: 5.7.2 @@ -4353,7 +4303,7 @@ snapshots: transitivePeerDependencies: - debug - solhint@4.5.4(typescript@5.5.4): + solhint@4.5.4(typescript@4.9.5): dependencies: '@solidity-parser/parser': 0.18.0 ajv: 6.12.6 @@ -4361,10 +4311,10 @@ snapshots: ast-parents: 0.0.1 chalk: 4.1.2 commander: 10.0.1 - cosmiconfig: 8.3.6(typescript@5.5.4) + cosmiconfig: 8.3.6(typescript@4.9.5) fast-diff: 1.3.0 glob: 8.1.0 - ignore: 5.3.1 + ignore: 5.3.2 js-yaml: 4.1.0 latest-version: 7.0.0 lodash: 4.17.21 @@ -4378,7 +4328,7 @@ snapshots: transitivePeerDependencies: - typescript - solidity-coverage@0.8.12(hardhat@2.22.8(typescript@5.5.4)): + solidity-coverage@0.8.13(hardhat@2.22.14(typescript@4.9.5)): dependencies: '@ethersproject/abi': 5.7.0 '@solidity-parser/parser': 0.18.0 @@ -4389,10 +4339,10 @@ snapshots: ghost-testrpc: 0.0.2 global-modules: 2.0.0 globby: 10.0.2 - hardhat: 2.22.8(typescript@5.5.4) + hardhat: 2.22.14(typescript@4.9.5) jsonschema: 1.4.1 lodash: 4.17.21 - mocha: 10.7.0 + mocha: 10.7.3 node-emoji: 1.11.0 pify: 4.0.1 recursive-readdir: 2.2.3 @@ -4401,7 +4351,7 @@ snapshots: shelljs: 0.8.5 web3-utils: 1.10.4 - solidity-stringutils@https://codeload.github.com/Arachnid/solidity-stringutils/tar.gz/4b2fcc43fa0426e19ce88b1f1ec16f5903a2e461: {} + solidity-stringutils@https://codeload.github.com/LayerZero-Labs/solidity-stringutils/tar.gz/eb21d6b502c2741145ab2a90f5f5b4fda9dfb218: {} source-map-support@0.5.21: dependencies: @@ -4490,9 +4440,9 @@ snapshots: tr46@0.0.3: {} - ts-essentials@7.0.3(typescript@5.5.4): + ts-essentials@7.0.3(typescript@4.9.5): dependencies: - typescript: 5.5.4 + typescript: 4.9.5 tslib@1.14.1: {} @@ -4512,34 +4462,30 @@ snapshots: type-fest@0.7.1: {} - typechain@5.2.0(typescript@5.5.4): + typechain@5.2.0(typescript@4.9.5): dependencies: '@types/prettier': 2.7.3 command-line-args: 4.0.7 - debug: 4.3.6(supports-color@8.1.1) + debug: 4.3.7(supports-color@8.1.1) fs-extra: 7.0.1 glob: 7.2.3 js-sha3: 0.8.0 lodash: 4.17.21 mkdirp: 1.0.4 prettier: 2.8.8 - ts-essentials: 7.0.3(typescript@5.5.4) - typescript: 5.5.4 + ts-essentials: 7.0.3(typescript@4.9.5) + typescript: 4.9.5 transitivePeerDependencies: - supports-color typescript@4.9.5: {} - typescript@5.5.4: {} - typical@2.6.1: {} - uglify-js@3.19.1: + uglify-js@3.19.3: optional: true - undici-types@5.26.5: {} - - undici-types@6.13.0: {} + undici-types@6.19.8: {} undici@5.28.4: dependencies: @@ -4619,7 +4565,7 @@ snapshots: yargs@16.2.0: dependencies: cliui: 7.0.4 - escalade: 3.1.2 + escalade: 3.2.0 get-caller-file: 2.0.5 require-directory: 2.1.1 string-width: 4.2.3 diff --git a/remappings.txt b/remappings.txt index 1b8a6496..e1d30364 100644 --- a/remappings.txt +++ b/remappings.txt @@ -18,7 +18,7 @@ solady/=node_modules/solady/src/ solarray/=node_modules/solarray/src/ @prb/math/=node_modules/@prb/math/src/ -ether-email-auth/=node_modules/ether-email-auth/ +@zk-email/ether-email-auth-contracts/=node_modules/@zk-email/ether-email-auth-contracts/ @zk-email/contracts/=node_modules/@zk-email/contracts/ solidity-stringutils/=node_modules/solidity-stringutils/ @matterlabs/zksync-contracts/l2/contracts/=src/libraries/ diff --git a/script/BaseDeployScript.s.sol b/script/BaseDeployScript.s.sol new file mode 100644 index 00000000..1592750a --- /dev/null +++ b/script/BaseDeployScript.s.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +/* solhint-disable no-console, gas-custom-errors */ + +import { Script } from "forge-std/Script.sol"; +import { console } from "forge-std/console.sol"; +import { Verifier } from "@zk-email/ether-email-auth-contracts/src/utils/Verifier.sol"; +import { Groth16Verifier } from "@zk-email/ether-email-auth-contracts/src/utils/Groth16Verifier.sol"; +import { UserOverrideableDKIMRegistry } from "@zk-email/contracts/UserOverrideableDKIMRegistry.sol"; +import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; + +contract BaseDeployScript is Script { + function run() public virtual { } + + /** + * Helper function to deploy a Verifier + */ + function deployVerifier(address initialOwner, uint256 salt) public returns (address) { + Verifier verifierImpl = new Verifier{ salt: bytes32(salt) }(); + console.log("Verifier implementation deployed at: %s", address(verifierImpl)); + Groth16Verifier groth16Verifier = new Groth16Verifier{ salt: bytes32(salt) }(); + ERC1967Proxy verifierProxy = new ERC1967Proxy{ salt: bytes32(salt) }( + address(verifierImpl), + abi.encodeCall(verifierImpl.initialize, (initialOwner, address(groth16Verifier))) + ); + address verifier = address(Verifier(address(verifierProxy))); + console.log("Deployed Verifier at", verifier); + return verifier; + } + + /** + * Helper function to deploy a UserOverrideableDKIMRegistry + */ + function deployUserOverrideableDKIMRegistry( + address initialOwner, + address dkimRegistrySigner, + uint256 setTimeDelay, + uint256 salt + ) + public + returns (address) + { + require(dkimRegistrySigner != address(0), "DKIM_SIGNER is required"); + UserOverrideableDKIMRegistry overrideableDkimImpl = + new UserOverrideableDKIMRegistry{ salt: bytes32(salt) }(); + console.log( + "UserOverrideableDKIMRegistry implementation deployed at: %s", + address(overrideableDkimImpl) + ); + ERC1967Proxy dkimProxy = new ERC1967Proxy{ salt: bytes32(salt) }( + address(overrideableDkimImpl), + abi.encodeCall( + overrideableDkimImpl.initialize, (initialOwner, dkimRegistrySigner, setTimeDelay) + ) + ); + address dkim = address(dkimProxy); + console.log("UseroverrideableDKIMRegistry proxy deployed at: %s", dkim); + return dkim; + } +} diff --git a/script/Compute7579RecoveryDataHash.s.sol b/script/Compute7579RecoveryDataHash.s.sol index 7cb359db..8d88e4e3 100644 --- a/script/Compute7579RecoveryDataHash.s.sol +++ b/script/Compute7579RecoveryDataHash.s.sol @@ -1,11 +1,13 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; +/* solhint-disable no-console */ + import { Script } from "forge-std/Script.sol"; import { console } from "forge-std/console.sol"; contract Compute7579RecoveryDataHash is Script { - function run() public { + function run() public view { bytes4 functionSelector = bytes4(keccak256(bytes("changeOwner(address)"))); address newOwner = vm.envAddress("NEW_OWNER"); address validator = vm.envAddress("VALIDATOR"); diff --git a/script/ComputeSafeRecoveryCalldata.s.sol b/script/ComputeSafeRecoveryCalldata.s.sol index 44e5a603..cfe35604 100644 --- a/script/ComputeSafeRecoveryCalldata.s.sol +++ b/script/ComputeSafeRecoveryCalldata.s.sol @@ -1,11 +1,13 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; +/* solhint-disable no-console */ + import { Script } from "forge-std/Script.sol"; import { console } from "forge-std/console.sol"; contract ComputeSafeRecoveryCalldataScript is Script { - function run() public { + function run() public view { address oldOwner = vm.envAddress("OLD_OWNER"); address newOwner = vm.envAddress("NEW_OWNER"); address previousOwnerInLinkedList = address(1); diff --git a/script/Deploy7579TestAccount.s.sol b/script/Deploy7579TestAccount.s.sol index 957e0952..ab5816da 100644 --- a/script/Deploy7579TestAccount.s.sol +++ b/script/Deploy7579TestAccount.s.sol @@ -1,10 +1,12 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; +/* solhint-disable no-console, gas-custom-errors, max-states-count */ + import { Script } from "forge-std/Script.sol"; import { console } from "forge-std/console.sol"; import { EmailAccountRecovery } from - "ether-email-auth/packages/contracts/src/EmailAccountRecovery.sol"; + "@zk-email/ether-email-auth-contracts/src/EmailAccountRecovery.sol"; import { IGuardianManager } from "src/interfaces/IGuardianManager.sol"; import { RhinestoneModuleKit } from "modulekit/ModuleKit.sol"; import { OwnableValidator } from "src/test/OwnableValidator.sol"; @@ -27,27 +29,29 @@ contract Deploy7579TestAccountScript is RhinestoneModuleKit, Script { using Strings for uint256; using Strings for address; - uint256 privKey; - address deployer; - MSABasic msaBasicImpl; - MSAFactory msaFactory; - Bootstrap bootstrap; - MockHook hook; - MockTarget mockTarget; + uint256 public privKey; + address public deployer; + MSABasic public msaBasicImpl; + MSAFactory public msaFactory; + Bootstrap public bootstrap; + MockHook public hook; + MockTarget public mockTarget; + + bytes32 public accountSalt; + address public validatorAddr; + address public recoveryModuleAddr; + address[] public guardians = new address[](0); + uint256[] public guardianWeights = new uint256[](0); - bytes32 accountSalt; - address validatorAddr; - address recoveryModuleAddr; - address[] guardians = new address[](0); - uint256[] guardianWeights = new uint256[](0); + address public account; + bytes public initCode; + bytes public userOpCalldata; + PackedUserOperation public userOp; + bytes32 public userOpHash; - address account; - bytes initCode; - bytes userOpCalldata; - PackedUserOperation userOp; - bytes32 userOpHash; + bytes4 public functionSelector = bytes4(keccak256(bytes("changeOwner(address)"))); - bytes4 functionSelector = bytes4(keccak256(bytes("changeOwner(address)"))); + uint256 public salt = vm.envOr("CREATE2_SALT", uint256(0)); function run() public { privKey = vm.envUint("PRIVATE_KEY"); @@ -59,45 +63,44 @@ contract Deploy7579TestAccountScript is RhinestoneModuleKit, Script { address msaBasicImplAddr = vm.envOr("MSA_BASIC_IMPL", address(0)); if (msaBasicImplAddr == address(0)) { - msaBasicImplAddr = address(new MSABasic()); + msaBasicImplAddr = address(new MSABasic{ salt: bytes32(salt) }()); console.log("Deployed MSABasic at", msaBasicImplAddr); } msaBasicImpl = MSABasic(payable(msaBasicImplAddr)); address msaFactoryAddr = vm.envOr("MSA_FACTORY", address(0)); if (msaFactoryAddr == address(0)) { - msaFactoryAddr = address(new MSAFactory(msaBasicImplAddr)); + msaFactoryAddr = address(new MSAFactory{ salt: bytes32(salt) }(msaBasicImplAddr)); console.log("Deployed MSAFactory at", msaFactoryAddr); } msaFactory = MSAFactory(msaFactoryAddr); address bootstrapAddr = vm.envOr("BOOTSTRAP", address(0)); if (bootstrapAddr == address(0)) { - bootstrapAddr = address(new Bootstrap()); + bootstrapAddr = address(new Bootstrap{ salt: bytes32(salt) }()); console.log("Deployed Bootstrap at", bootstrapAddr); } bootstrap = Bootstrap(payable(bootstrapAddr)); address hookAddr = vm.envOr("HOOK", address(0)); if (hookAddr == address(0)) { - hookAddr = address(new MockHook()); + hookAddr = address(new MockHook{ salt: bytes32(salt) }()); console.log("Deployed MockHook at", hookAddr); } hook = MockHook(hookAddr); address mockTargetAddr = vm.envOr("MOCK_TARGET", address(0)); if (mockTargetAddr == address(0)) { - mockTargetAddr = address(new MockTarget()); + mockTargetAddr = address(new MockTarget{ salt: bytes32(salt) }()); console.log("Deployed MockTarget at", mockTargetAddr); } mockTarget = MockTarget(mockTargetAddr); validatorAddr = vm.envOr("VALIDATOR", address(0)); if (validatorAddr == address(0)) { - validatorAddr = address(new OwnableValidator()); + validatorAddr = address(new OwnableValidator{ salt: bytes32(salt) }()); console.log("Deployed Ownable Validator at", validatorAddr); } - OwnableValidator validator = OwnableValidator(validatorAddr); // Create initcode to be sent to Factory BootstrapConfig[] memory validators = new BootstrapConfig[](1); @@ -223,8 +226,15 @@ contract Deploy7579TestAccountScript is RhinestoneModuleKit, Script { vm.stopBroadcast(); } - function getNonce(address account, address validator) internal returns (uint256 nonce) { + function getNonce( + address smartAccount, + address validator + ) + internal + view + returns (uint256 nonce) + { uint192 key = uint192(bytes24(bytes20(address(validator)))); - nonce = IEntryPoint(ENTRYPOINT_ADDR).getNonce(address(account), key); + nonce = IEntryPoint(ENTRYPOINT_ADDR).getNonce(address(smartAccount), key); } } diff --git a/script/DeployEmailRecoveryModule.s.sol b/script/DeployEmailRecoveryModule.s.sol index 8478225d..1032a3fb 100644 --- a/script/DeployEmailRecoveryModule.s.sol +++ b/script/DeployEmailRecoveryModule.s.sol @@ -1,83 +1,88 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { Script } from "forge-std/Script.sol"; +/* solhint-disable no-console, gas-custom-errors */ + import { console } from "forge-std/console.sol"; -import { EmailRecoverySubjectHandler } from "src/handlers/EmailRecoverySubjectHandler.sol"; -import { Verifier } from "ether-email-auth/packages/contracts/src/utils/Verifier.sol"; -import { ECDSAOwnedDKIMRegistry } from - "ether-email-auth/packages/contracts/src/utils/ECDSAOwnedDKIMRegistry.sol"; -import { EmailAuth } from "ether-email-auth/packages/contracts/src/EmailAuth.sol"; +import { EmailRecoveryCommandHandler } from "src/handlers/EmailRecoveryCommandHandler.sol"; +import { UserOverrideableDKIMRegistry } from "@zk-email/contracts/UserOverrideableDKIMRegistry.sol"; +import { EmailAuth } from "@zk-email/ether-email-auth-contracts/src/EmailAuth.sol"; import { EmailRecoveryFactory } from "src/factories/EmailRecoveryFactory.sol"; import { OwnableValidator } from "src/test/OwnableValidator.sol"; -import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import { BaseDeployScript } from "./BaseDeployScript.s.sol"; + +contract DeployEmailRecoveryModuleScript is BaseDeployScript { + address public verifier; + address public dkimRegistrySigner; + address public emailAuthImpl; + address public validatorAddr; + uint256 public minimumDelay; + address public killSwitchAuthorizer; + + address public initialOwner; + uint256 public salt; + + UserOverrideableDKIMRegistry public dkim; -contract DeployEmailRecoveryModuleScript is Script { - function run() public { + function run() public override { + super.run(); vm.startBroadcast(vm.envUint("PRIVATE_KEY")); - address verifier = vm.envOr("VERIFIER", address(0)); - address dkimRegistry = vm.envOr("DKIM_REGISTRY", address(0)); - address dkimRegistrySigner = vm.envOr("SIGNER", address(0)); - address emailAuthImpl = vm.envOr("EMAIL_AUTH_IMPL", address(0)); - address validatorAddr = vm.envOr("VALIDATOR", address(0)); + verifier = vm.envOr("VERIFIER", address(0)); + dkimRegistrySigner = vm.envOr("DKIM_SIGNER", address(0)); + emailAuthImpl = vm.envOr("EMAIL_AUTH_IMPL", address(0)); + validatorAddr = vm.envOr("VALIDATOR", address(0)); + minimumDelay = vm.envOr("MINIMUM_DELAY", uint256(0)); + killSwitchAuthorizer = vm.envAddress("KILL_SWITCH_AUTHORIZER"); - address initialOwner = vm.addr(vm.envUint("PRIVATE_KEY")); + initialOwner = vm.addr(vm.envUint("PRIVATE_KEY")); + salt = vm.envOr("CREATE2_SALT", uint256(0)); if (verifier == address(0)) { - Verifier verifierImpl = new Verifier(); - console.log("Verifier implementation deployed at: %s", address(verifierImpl)); - ERC1967Proxy verifierProxy = new ERC1967Proxy( - address(verifierImpl), abi.encodeCall(verifierImpl.initialize, (initialOwner)) - ); - verifier = address(Verifier(address(verifierProxy))); - vm.setEnv("VERIFIER", vm.toString(address(verifier))); - console.log("Deployed Verifier at", verifier); + verifier = deployVerifier(initialOwner, salt); } - if (dkimRegistry == address(0)) { - require(dkimRegistrySigner != address(0), "DKIM_REGISTRY_SIGNER is required"); - - ECDSAOwnedDKIMRegistry dkimImpl = new ECDSAOwnedDKIMRegistry(); - console.log("ECDSAOwnedDKIMRegistry implementation deployed at: %s", address(dkimImpl)); - ERC1967Proxy dkimProxy = new ERC1967Proxy( - address(dkimImpl), - abi.encodeCall(dkimImpl.initialize, (initialOwner, dkimRegistrySigner)) + // Deploy Useroverridable DKIM registry + dkim = UserOverrideableDKIMRegistry(vm.envOr("DKIM_REGISTRY", address(0))); + uint256 setTimeDelay = vm.envOr("DKIM_DELAY", uint256(0)); + if (address(dkim) == address(0)) { + dkim = UserOverrideableDKIMRegistry( + deployUserOverrideableDKIMRegistry( + initialOwner, dkimRegistrySigner, setTimeDelay, salt + ) ); - dkimRegistry = address(ECDSAOwnedDKIMRegistry(address(dkimProxy))); - vm.setEnv("ECDSA_DKIM", vm.toString(address(dkimRegistry))); - console.log("Deployed DKIM Registry at", dkimRegistry); } if (emailAuthImpl == address(0)) { - emailAuthImpl = address(new EmailAuth()); + emailAuthImpl = address(new EmailAuth{ salt: bytes32(salt) }()); console.log("Deployed Email Auth at", emailAuthImpl); } if (validatorAddr == address(0)) { - validatorAddr = address(new OwnableValidator()); + validatorAddr = address(new OwnableValidator{ salt: bytes32(salt) }()); console.log("Deployed Ownable Validator at", validatorAddr); } - EmailRecoverySubjectHandler emailRecoveryHandler = new EmailRecoverySubjectHandler(); - address _factory = vm.envOr("RECOVERY_FACTORY", address(0)); if (_factory == address(0)) { - _factory = address(new EmailRecoveryFactory(verifier, emailAuthImpl)); + _factory = + address(new EmailRecoveryFactory{ salt: bytes32(salt) }(verifier, emailAuthImpl)); console.log("Deployed Email Recovery Factory at", _factory); } { EmailRecoveryFactory factory = EmailRecoveryFactory(_factory); - (address module, address subjectHandler) = factory.deployEmailRecoveryModule( + (address module, address commandHandler) = factory.deployEmailRecoveryModule( bytes32(uint256(0)), bytes32(uint256(0)), - type(EmailRecoverySubjectHandler).creationCode, - dkimRegistry, + type(EmailRecoveryCommandHandler).creationCode, + minimumDelay, + killSwitchAuthorizer, + address(dkim), validatorAddr, bytes4(keccak256(bytes("changeOwner(address)"))) ); console.log("Deployed Email Recovery Module at", vm.toString(module)); - console.log("Deployed Email Recovery Handler at", vm.toString(subjectHandler)); + console.log("Deployed Email Recovery Handler at", vm.toString(commandHandler)); vm.stopBroadcast(); } } diff --git a/script/DeploySafeNativeRecovery.s.sol b/script/DeploySafeNativeRecovery.s.sol index 6af0e46e..13f5d5ec 100644 --- a/script/DeploySafeNativeRecovery.s.sol +++ b/script/DeploySafeNativeRecovery.s.sol @@ -1,64 +1,68 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { Script } from "forge-std/Script.sol"; +/* solhint-disable no-console, gas-custom-errors */ + import { console } from "forge-std/console.sol"; -import { Verifier } from "ether-email-auth/packages/contracts/src/utils/Verifier.sol"; -import { ECDSAOwnedDKIMRegistry } from - "ether-email-auth/packages/contracts/src/utils/ECDSAOwnedDKIMRegistry.sol"; -import { EmailAuth } from "ether-email-auth/packages/contracts/src/EmailAuth.sol"; -import { SafeRecoverySubjectHandler } from "src/handlers/SafeRecoverySubjectHandler.sol"; +import { UserOverrideableDKIMRegistry } from "@zk-email/contracts/UserOverrideableDKIMRegistry.sol"; +import { EmailAuth } from "@zk-email/ether-email-auth-contracts/src/EmailAuth.sol"; +import { SafeRecoveryCommandHandler } from "src/handlers/SafeRecoveryCommandHandler.sol"; import { SafeEmailRecoveryModule } from "src/modules/SafeEmailRecoveryModule.sol"; -import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import { BaseDeployScript } from "./BaseDeployScript.s.sol"; -contract DeploySafeNativeRecovery_Script is Script { - function run() public { +contract DeploySafeNativeRecovery_Script is BaseDeployScript { + function run() public override { + super.run(); vm.startBroadcast(vm.envUint("PRIVATE_KEY")); - address verifier = vm.envOr("VERIFIER", address(0)); - address dkimRegistry = vm.envOr("DKIM_REGISTRY", address(0)); - address dkimRegistrySigner = vm.envOr("SIGNER", address(0)); + address verifier = vm.envOr("ZK_VERIFIER", address(0)); + address dkimRegistrySigner = vm.envOr("DKIM_SIGNER", address(0)); address emailAuthImpl = vm.envOr("EMAIL_AUTH_IMPL", address(0)); - address subjectHandler = vm.envOr("SUBJECT_HANDLER", address(0)); + address commandHandler = vm.envOr("COMMAND_HANDLER", address(0)); + uint256 minimumDelay = vm.envOr("MINIMUM_DELAY", uint256(0)); + address killSwitchAuthorizer = vm.envAddress("KILL_SWITCH_AUTHORIZER"); address initialOwner = vm.addr(vm.envUint("PRIVATE_KEY")); + uint256 salt = vm.envOr("CREATE2_SALT", uint256(0)); + + console.log("verifier %s", verifier); + + UserOverrideableDKIMRegistry dkim; + if (verifier == address(0)) { - Verifier verifierImpl = new Verifier(); - console.log("Verifier implementation deployed at: %s", address(verifierImpl)); - ERC1967Proxy verifierProxy = new ERC1967Proxy( - address(verifierImpl), abi.encodeCall(verifierImpl.initialize, (initialOwner)) - ); - verifier = address(Verifier(address(verifierProxy))); - vm.setEnv("VERIFIER", vm.toString(address(verifier))); - console.log("Deployed Verifier at", verifier); + verifier = deployVerifier(initialOwner, salt); } - if (dkimRegistry == address(0)) { - require(dkimRegistrySigner != address(0), "DKIM_REGISTRY_SIGNER is required"); - - ECDSAOwnedDKIMRegistry dkimImpl = new ECDSAOwnedDKIMRegistry(); - console.log("ECDSAOwnedDKIMRegistry implementation deployed at: %s", address(dkimImpl)); - ERC1967Proxy dkimProxy = new ERC1967Proxy( - address(dkimImpl), - abi.encodeCall(dkimImpl.initialize, (initialOwner, dkimRegistrySigner)) + // Deploy Useroverridable DKIM registry + dkim = UserOverrideableDKIMRegistry(vm.envOr("DKIM_REGISTRY", address(0))); + uint256 setTimeDelay = vm.envOr("DKIM_DELAY", uint256(0)); + if (address(dkim) == address(0)) { + dkim = UserOverrideableDKIMRegistry( + deployUserOverrideableDKIMRegistry( + initialOwner, dkimRegistrySigner, setTimeDelay, salt + ) ); - dkimRegistry = address(ECDSAOwnedDKIMRegistry(address(dkimProxy))); - vm.setEnv("ECDSA_DKIM", vm.toString(address(dkimRegistry))); - console.log("Deployed DKIM Registry at", dkimRegistry); } if (emailAuthImpl == address(0)) { - emailAuthImpl = address(new EmailAuth()); + emailAuthImpl = address(new EmailAuth{ salt: bytes32(salt) }()); console.log("Deployed Email Auth at", emailAuthImpl); } - if (subjectHandler == address(0)) { - subjectHandler = address(new SafeRecoverySubjectHandler()); - console.log("Deployed Subject Handler at", subjectHandler); + if (commandHandler == address(0)) { + commandHandler = address(new SafeRecoveryCommandHandler{ salt: bytes32(salt) }()); + console.log("Deployed Command Handler at", commandHandler); } address module = address( - new SafeEmailRecoveryModule(verifier, dkimRegistry, emailAuthImpl, subjectHandler) + new SafeEmailRecoveryModule{ salt: bytes32(salt) }( + verifier, + address(dkim), + emailAuthImpl, + commandHandler, + minimumDelay, + killSwitchAuthorizer + ) ); console.log("Deployed Email Recovery Module at ", vm.toString(module)); diff --git a/script/DeploySafeNativeRecoveryWithAccountHiding.s.sol b/script/DeploySafeNativeRecoveryWithAccountHiding.s.sol new file mode 100644 index 00000000..bbcfb272 --- /dev/null +++ b/script/DeploySafeNativeRecoveryWithAccountHiding.s.sol @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +/* solhint-disable no-console, gas-custom-errors */ + +import { console } from "forge-std/console.sol"; +import { UserOverrideableDKIMRegistry } from "@zk-email/contracts/UserOverrideableDKIMRegistry.sol"; +import { EmailAuth } from "@zk-email/ether-email-auth-contracts/src/EmailAuth.sol"; +import { AccountHidingRecoveryCommandHandler } from + "src/handlers/AccountHidingRecoveryCommandHandler.sol"; +import { SafeEmailRecoveryModule } from "src/modules/SafeEmailRecoveryModule.sol"; +import { BaseDeployScript } from "./BaseDeployScript.s.sol"; + +contract DeploySafeNativeRecovery_Script is BaseDeployScript { + function run() public override { + super.run(); + vm.startBroadcast(vm.envUint("PRIVATE_KEY")); + address verifier = vm.envOr("ZK_VERIFIER", address(0)); + address dkimRegistrySigner = vm.envOr("DKIM_SIGNER", address(0)); + address emailAuthImpl = vm.envOr("EMAIL_AUTH_IMPL", address(0)); + address commandHandler = vm.envOr("COMMAND_HANDLER", address(0)); + uint256 minimumDelay = vm.envOr("MINIMUM_DELAY", uint256(0)); + address killSwitchAuthorizer = vm.envAddress("KILL_SWITCH_AUTHORIZER"); + + address initialOwner = vm.addr(vm.envUint("PRIVATE_KEY")); + + uint256 salt = vm.envOr("CREATE2_SALT", uint256(0)); + + console.log("verifier %s", verifier); + + UserOverrideableDKIMRegistry dkim; + + if (verifier == address(0)) { + verifier = deployVerifier(initialOwner, salt); + } + + // Deploy Useroverridable DKIM registry + dkim = UserOverrideableDKIMRegistry(vm.envOr("DKIM_REGISTRY", address(0))); + uint256 setTimeDelay = vm.envOr("DKIM_DELAY", uint256(0)); + if (address(dkim) == address(0)) { + dkim = UserOverrideableDKIMRegistry( + deployUserOverrideableDKIMRegistry( + initialOwner, dkimRegistrySigner, setTimeDelay, salt + ) + ); + } + + if (emailAuthImpl == address(0)) { + emailAuthImpl = address(new EmailAuth{ salt: bytes32(salt) }()); + console.log("Deployed Email Auth at", emailAuthImpl); + } + + if (commandHandler == address(0)) { + commandHandler = address(new AccountHidingRecoveryCommandHandler{ salt: bytes32(salt) }()); + console.log("Deployed Command Handler at", commandHandler); + } + + address module = address( + new SafeEmailRecoveryModule{ salt: bytes32(salt) }( + verifier, + address(dkim), + emailAuthImpl, + commandHandler, + minimumDelay, + killSwitchAuthorizer + ) + ); + + console.log("Deployed Email Recovery Module at ", vm.toString(module)); + + vm.stopBroadcast(); + } +} diff --git a/script/DeploySafeRecovery.s.sol b/script/DeploySafeRecovery.s.sol index 39414b74..9b2884d2 100644 --- a/script/DeploySafeRecovery.s.sol +++ b/script/DeploySafeRecovery.s.sol @@ -1,82 +1,85 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { Script } from "forge-std/Script.sol"; +/* solhint-disable no-console, gas-custom-errors */ + import { console } from "forge-std/console.sol"; -import { SafeRecoverySubjectHandler } from "src/handlers/SafeRecoverySubjectHandler.sol"; -import { EmailRecoveryFactory } from "src/factories/EmailRecoveryFactory.sol"; +import { SafeRecoveryCommandHandler } from "src/handlers/SafeRecoveryCommandHandler.sol"; import { EmailRecoveryUniversalFactory } from "src/factories/EmailRecoveryUniversalFactory.sol"; -import { Verifier } from "ether-email-auth/packages/contracts/src/utils/Verifier.sol"; -import { ECDSAOwnedDKIMRegistry } from - "ether-email-auth/packages/contracts/src/utils/ECDSAOwnedDKIMRegistry.sol"; -import { EmailAuth } from "ether-email-auth/packages/contracts/src/EmailAuth.sol"; +import { EmailAuth } from "@zk-email/ether-email-auth-contracts/src/EmailAuth.sol"; +import { UserOverrideableDKIMRegistry } from "@zk-email/contracts/UserOverrideableDKIMRegistry.sol"; import { Safe7579 } from "safe7579/Safe7579.sol"; import { Safe7579Launchpad } from "safe7579/Safe7579Launchpad.sol"; import { IERC7484 } from "safe7579/interfaces/IERC7484.sol"; -import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import { BaseDeployScript } from "./BaseDeployScript.s.sol"; // 1. `source .env` // 2. `forge script --chain sepolia script/DeploySafeRecovery.s.sol:DeploySafeRecovery_Script // --rpc-url $BASE_SEPOLIA_RPC_URL --broadcast -vvvv` -contract DeploySafeRecovery_Script is Script { - function run() public { +contract DeploySafeRecovery_Script is BaseDeployScript { + address public verifier; + address public dkimRegistrySigner; + address public emailAuthImpl; + uint256 public minimumDelay; + address public killSwitchAuthorizer; + + address public initialOwner; + uint256 public salt; + + UserOverrideableDKIMRegistry public dkim; + + function run() public override { + super.run(); address entryPoint = address(0x0000000071727De22E5E9d8BAf0edAc6f37da032); IERC7484 registry = IERC7484(0xe0cde9239d16bEf05e62Bbf7aA93e420f464c826); vm.startBroadcast(vm.envUint("PRIVATE_KEY")); - address verifier = vm.envOr("VERIFIER", address(0)); - address dkimRegistry = vm.envOr("DKIM_REGISTRY", address(0)); - address dkimRegistrySigner = vm.envOr("SIGNER", address(0)); - address emailAuthImpl = vm.envOr("EMAIL_AUTH_IMPL", address(0)); - - address initialOwner = vm.addr(vm.envUint("PRIVATE_KEY")); + verifier = vm.envOr("VERIFIER", address(0)); + dkimRegistrySigner = vm.envOr("DKIM_SIGNER", address(0)); + emailAuthImpl = vm.envOr("EMAIL_AUTH_IMPL", address(0)); + minimumDelay = vm.envOr("MINIMUM_DELAY", uint256(0)); + killSwitchAuthorizer = vm.envAddress("KILL_SWITCH_AUTHORIZER"); + initialOwner = vm.addr(vm.envUint("PRIVATE_KEY")); + salt = vm.envOr("CREATE2_SALT", uint256(0)); if (verifier == address(0)) { - Verifier verifierImpl = new Verifier(); - console.log("Verifier implementation deployed at: %s", address(verifierImpl)); - ERC1967Proxy verifierProxy = new ERC1967Proxy( - address(verifierImpl), abi.encodeCall(verifierImpl.initialize, (initialOwner)) - ); - verifier = address(Verifier(address(verifierProxy))); - vm.setEnv("VERIFIER", vm.toString(address(verifier))); - console.log("Deployed Verifier at", verifier); + verifier = deployVerifier(initialOwner, salt); } - if (dkimRegistry == address(0)) { - require(dkimRegistrySigner != address(0), "DKIM_REGISTRY_SIGNER is required"); - - ECDSAOwnedDKIMRegistry dkimImpl = new ECDSAOwnedDKIMRegistry(); - console.log("ECDSAOwnedDKIMRegistry implementation deployed at: %s", address(dkimImpl)); - ERC1967Proxy dkimProxy = new ERC1967Proxy( - address(dkimImpl), - abi.encodeCall(dkimImpl.initialize, (initialOwner, dkimRegistrySigner)) + // Deploy Useroverridable DKIM registry + dkim = UserOverrideableDKIMRegistry(vm.envOr("DKIM_REGISTRY", address(0))); + uint256 setTimeDelay = vm.envOr("DKIM_DELAY", uint256(0)); + if (address(dkim) == address(0)) { + dkim = UserOverrideableDKIMRegistry( + deployUserOverrideableDKIMRegistry( + initialOwner, dkimRegistrySigner, setTimeDelay, salt + ) ); - dkimRegistry = address(ECDSAOwnedDKIMRegistry(address(dkimProxy))); - vm.setEnv("ECDSA_DKIM", vm.toString(address(dkimRegistry))); - console.log("Deployed DKIM Registry at", dkimRegistry); } if (emailAuthImpl == address(0)) { - emailAuthImpl = address(new EmailAuth()); + emailAuthImpl = address(new EmailAuth{ salt: bytes32(salt) }()); console.log("Deployed Email Auth at", emailAuthImpl); } EmailRecoveryUniversalFactory factory = - new EmailRecoveryUniversalFactory(verifier, emailAuthImpl); - (address module, address subjectHandler) = factory.deployUniversalEmailRecoveryModule( - bytes32(uint256(0)), - bytes32(uint256(0)), - type(SafeRecoverySubjectHandler).creationCode, - dkimRegistry + new EmailRecoveryUniversalFactory{ salt: bytes32(salt) }(verifier, emailAuthImpl); + (address module, address commandHandler) = factory.deployUniversalEmailRecoveryModule( + bytes32(salt), + bytes32(salt), + type(SafeRecoveryCommandHandler).creationCode, + minimumDelay, + killSwitchAuthorizer, + address(dkim) ); - address safe7579 = address(new Safe7579{ salt: bytes32(uint256(0)) }()); + address safe7579 = address(new Safe7579{ salt: bytes32(salt) }()); address safe7579Launchpad = - address(new Safe7579Launchpad{ salt: bytes32(uint256(0)) }(entryPoint, registry)); + address(new Safe7579Launchpad{ salt: bytes32(salt) }(entryPoint, registry)); console.log("Deployed Email Recovery Module at ", vm.toString(module)); - console.log("Deployed Email Recovery Handler at ", vm.toString(subjectHandler)); + console.log("Deployed Email Recovery Handler at ", vm.toString(commandHandler)); console.log("Deployed Safe 7579 at ", vm.toString(safe7579)); console.log("Deployed Safe 7579 Launchpad at ", vm.toString(safe7579Launchpad)); diff --git a/script/DeploySafeRecoveryWithAccountHiding.s.sol b/script/DeploySafeRecoveryWithAccountHiding.s.sol new file mode 100644 index 00000000..30888d1b --- /dev/null +++ b/script/DeploySafeRecoveryWithAccountHiding.s.sol @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +/* solhint-disable no-console, gas-custom-errors */ + +import { console } from "forge-std/console.sol"; +import { AccountHidingRecoveryCommandHandler } from + "src/handlers/AccountHidingRecoveryCommandHandler.sol"; +import { EmailRecoveryUniversalFactory } from "src/factories/EmailRecoveryUniversalFactory.sol"; +import { EmailAuth } from "@zk-email/ether-email-auth-contracts/src/EmailAuth.sol"; + +import { Safe7579 } from "safe7579/Safe7579.sol"; +import { Safe7579Launchpad } from "safe7579/Safe7579Launchpad.sol"; +import { IERC7484 } from "safe7579/interfaces/IERC7484.sol"; +import { BaseDeployScript } from "./BaseDeployScript.s.sol"; + +// 1. `source .env` +// 2. `forge script +// script/DeploySafeRecoveryWithAccountHiding.s.sol:DeploySafeRecoveryWithAccountHiding_Script +// --rpc-url $RPC_URL --broadcast --verify --etherscan-api-key $ETHERSCAN_API_KEY -vvvv` +contract DeploySafeRecoveryWithAccountHiding_Script is BaseDeployScript { + address verifier; + address dkim; + address dkimRegistrySigner; + address emailAuthImpl; + uint256 minimumDelay; + address killSwitchAuthorizer; + + address initialOwner; + uint256 salt; + + function run() public override { + super.run(); + address entryPoint = address(0x0000000071727De22E5E9d8BAf0edAc6f37da032); + IERC7484 registry = IERC7484(0xe0cde9239d16bEf05e62Bbf7aA93e420f464c826); + + vm.startBroadcast(vm.envUint("PRIVATE_KEY")); + verifier = vm.envOr("VERIFIER", address(0)); + dkim = vm.envOr("DKIM_REGISTRY", address(0)); + dkimRegistrySigner = vm.envOr("DKIM_SIGNER", address(0)); + emailAuthImpl = vm.envOr("EMAIL_AUTH_IMPL", address(0)); + minimumDelay = vm.envOr("MINIMUM_DELAY", uint256(0)); + killSwitchAuthorizer = vm.envAddress("KILL_SWITCH_AUTHORIZER"); + + initialOwner = vm.addr(vm.envUint("PRIVATE_KEY")); + salt = vm.envOr("CREATE2_SALT", uint256(0)); + + if (verifier == address(0)) { + verifier = deployVerifier(initialOwner, salt); + } + + // Deploy Useroverridable DKIM registry + dkim = vm.envOr("DKIM_REGISTRY", address(0)); + uint256 setTimeDelay = vm.envOr("DKIM_DELAY", uint256(0)); + if (dkim == address(0)) { + dkim = deployUserOverrideableDKIMRegistry( + initialOwner, dkimRegistrySigner, setTimeDelay, salt + ); + } + + if (emailAuthImpl == address(0)) { + emailAuthImpl = address(new EmailAuth{ salt: bytes32(salt) }()); + console.log("Deployed Email Auth at", emailAuthImpl); + } + + EmailRecoveryUniversalFactory factory = + new EmailRecoveryUniversalFactory{ salt: bytes32(salt) }(verifier, emailAuthImpl); + (address module, address commandHandler) = factory.deployUniversalEmailRecoveryModule( + bytes32(uint256(0)), + bytes32(uint256(0)), + type(AccountHidingRecoveryCommandHandler).creationCode, + minimumDelay, + killSwitchAuthorizer, + dkim + ); + + address safe7579 = address(new Safe7579{ salt: bytes32(uint256(0)) }()); + address safe7579Launchpad = + address(new Safe7579Launchpad{ salt: bytes32(uint256(0)) }(entryPoint, registry)); + + console.log("Deployed Email Recovery Module at ", vm.toString(module)); + console.log("Deployed Email Recovery Handler at ", vm.toString(commandHandler)); + console.log("Deployed Safe 7579 at ", vm.toString(safe7579)); + console.log("Deployed Safe 7579 Launchpad at ", vm.toString(safe7579Launchpad)); + + vm.stopBroadcast(); + } +} diff --git a/script/DeployUniversalEmailRecoveryModule.s.sol b/script/DeployUniversalEmailRecoveryModule.s.sol index e5d620de..efc50b20 100644 --- a/script/DeployUniversalEmailRecoveryModule.s.sol +++ b/script/DeployUniversalEmailRecoveryModule.s.sol @@ -1,74 +1,69 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { Script } from "forge-std/Script.sol"; +/* solhint-disable no-console, gas-custom-errors */ + import { console } from "forge-std/console.sol"; -import { EmailRecoverySubjectHandler } from "src/handlers/EmailRecoverySubjectHandler.sol"; -import { Verifier } from "ether-email-auth/packages/contracts/src/utils/Verifier.sol"; -import { ECDSAOwnedDKIMRegistry } from - "ether-email-auth/packages/contracts/src/utils/ECDSAOwnedDKIMRegistry.sol"; -import { EmailAuth } from "ether-email-auth/packages/contracts/src/EmailAuth.sol"; +import { EmailRecoveryCommandHandler } from "src/handlers/EmailRecoveryCommandHandler.sol"; +import { UserOverrideableDKIMRegistry } from "@zk-email/contracts/UserOverrideableDKIMRegistry.sol"; +import { EmailAuth } from "@zk-email/ether-email-auth-contracts/src/EmailAuth.sol"; import { EmailRecoveryUniversalFactory } from "src/factories/EmailRecoveryUniversalFactory.sol"; -import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import { BaseDeployScript } from "./BaseDeployScript.s.sol"; -contract DeployUniversalEmailRecoveryModuleScript is Script { - function run() public { +contract DeployUniversalEmailRecoveryModuleScript is BaseDeployScript { + function run() public override { + super.run(); vm.startBroadcast(vm.envUint("PRIVATE_KEY")); address verifier = vm.envOr("VERIFIER", address(0)); - address dkimRegistry = vm.envOr("DKIM_REGISTRY", address(0)); - address dkimRegistrySigner = vm.envOr("SIGNER", address(0)); + address dkimRegistrySigner = vm.envOr("DKIM_SIGNER", address(0)); address emailAuthImpl = vm.envOr("EMAIL_AUTH_IMPL", address(0)); + uint256 minimumDelay = vm.envOr("MINIMUM_DELAY", uint256(0)); + address killSwitchAuthorizer = vm.envAddress("KILL_SWITCH_AUTHORIZER"); address initialOwner = vm.addr(vm.envUint("PRIVATE_KEY")); + uint256 salt = vm.envOr("CREATE2_SALT", uint256(0)); + UserOverrideableDKIMRegistry dkim; if (verifier == address(0)) { - Verifier verifierImpl = new Verifier(); - console.log("Verifier implementation deployed at: %s", address(verifierImpl)); - ERC1967Proxy verifierProxy = new ERC1967Proxy( - address(verifierImpl), abi.encodeCall(verifierImpl.initialize, (initialOwner)) - ); - verifier = address(Verifier(address(verifierProxy))); - vm.setEnv("VERIFIER", vm.toString(address(verifier))); - console.log("Deployed Verifier at", verifier); + verifier = deployVerifier(initialOwner, salt); } - if (dkimRegistry == address(0)) { - require(dkimRegistrySigner != address(0), "DKIM_REGISTRY_SIGNER is required"); - - ECDSAOwnedDKIMRegistry dkimImpl = new ECDSAOwnedDKIMRegistry(); - console.log("ECDSAOwnedDKIMRegistry implementation deployed at: %s", address(dkimImpl)); - ERC1967Proxy dkimProxy = new ERC1967Proxy( - address(dkimImpl), - abi.encodeCall(dkimImpl.initialize, (initialOwner, dkimRegistrySigner)) + // Deploy Useroverridable DKIM registry + dkim = UserOverrideableDKIMRegistry(vm.envOr("DKIM_REGISTRY", address(0))); + uint256 setTimeDelay = vm.envOr("DKIM_DELAY", uint256(0)); + if (address(dkim) == address(0)) { + dkim = UserOverrideableDKIMRegistry( + deployUserOverrideableDKIMRegistry( + initialOwner, dkimRegistrySigner, setTimeDelay, salt + ) ); - dkimRegistry = address(ECDSAOwnedDKIMRegistry(address(dkimProxy))); - vm.setEnv("ECDSA_DKIM", vm.toString(address(dkimRegistry))); - console.log("Deployed DKIM Registry at", dkimRegistry); } if (emailAuthImpl == address(0)) { - emailAuthImpl = address(new EmailAuth()); + emailAuthImpl = address(new EmailAuth{ salt: bytes32(salt) }()); console.log("Deployed Email Auth at", emailAuthImpl); } - EmailRecoverySubjectHandler emailRecoveryHandler = new EmailRecoverySubjectHandler(); - address _factory = vm.envOr("RECOVERY_FACTORY", address(0)); if (_factory == address(0)) { - _factory = address(new EmailRecoveryUniversalFactory(verifier, emailAuthImpl)); + _factory = address( + new EmailRecoveryUniversalFactory{ salt: bytes32(salt) }(verifier, emailAuthImpl) + ); console.log("Deployed Email Recovery Factory at", _factory); } { EmailRecoveryUniversalFactory factory = EmailRecoveryUniversalFactory(_factory); - (address module, address subjectHandler) = factory.deployUniversalEmailRecoveryModule( + (address module, address commandHandler) = factory.deployUniversalEmailRecoveryModule( bytes32(uint256(0)), bytes32(uint256(0)), - type(EmailRecoverySubjectHandler).creationCode, - dkimRegistry + type(EmailRecoveryCommandHandler).creationCode, + minimumDelay, + killSwitchAuthorizer, + address(dkim) ); console.log("Deployed Email Recovery Module at", vm.toString(module)); - console.log("Deployed Email Recovery Handler at", vm.toString(subjectHandler)); + console.log("Deployed Email Recovery Handler at", vm.toString(commandHandler)); vm.stopBroadcast(); } } diff --git a/script/test/BaseDeployTest.sol b/script/test/BaseDeployTest.sol new file mode 100644 index 00000000..1784498e --- /dev/null +++ b/script/test/BaseDeployTest.sol @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.12; + +/* solhint-disable no-console */ + +import { Test } from "forge-std/Test.sol"; +import { console2 } from "forge-std/console2.sol"; +import { Verifier } from "@zk-email/ether-email-auth-contracts/src/utils/Verifier.sol"; +import { EmailAuth } from "@zk-email/ether-email-auth-contracts/src/EmailAuth.sol"; +import { EmailRecoveryCommandHandler } from "src/handlers/EmailRecoveryCommandHandler.sol"; +import { EmailRecoveryUniversalFactory } from "src/factories/EmailRecoveryUniversalFactory.sol"; +import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import { ECDSAOwnedDKIMRegistry } from + "@zk-email/ether-email-auth-contracts/src/utils/ECDSAOwnedDKIMRegistry.sol"; +import { Groth16Verifier } from "@zk-email/ether-email-auth-contracts/src/utils/Groth16Verifier.sol"; + +abstract contract BaseDeployTest is Test { + function setUp() public virtual { + // Set environment variables + vm.setEnv( + "PRIVATE_KEY", "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" + ); + address initialOwner = vm.addr(vm.envUint("PRIVATE_KEY")); + + // Deploy Verifier and set up proxy + address verifier = deployVerifier(initialOwner); + + // Set up additional environment variables + setupEnvironmentVariables(); + + // Deploy EmailRecoveryCommandHandler + new EmailRecoveryCommandHandler(); + + // Deploy EmailRecoveryUniversalFactory and set up module + deployEmailRecoveryModule(verifier); + } + + /** + * @dev Deploys the Verifier contract and sets up its proxy. + * @param initialOwner The address of the initial owner. + * @return The address of the deployed Verifier contract. + */ + function deployVerifier(address initialOwner) internal returns (address) { + Verifier verifierImpl = new Verifier(); + Groth16Verifier groth16Verifier = new Groth16Verifier(); + ERC1967Proxy verifierProxy = new ERC1967Proxy( + address(verifierImpl), + abi.encodeCall(verifierImpl.initialize, (initialOwner, address(groth16Verifier))) + ); + address verifier = address(Verifier(address(verifierProxy))); + vm.setEnv("VERIFIER", vm.toString(address(verifierImpl))); + return verifier; + } + + /** + * @dev Sets up additional environment variables required for the deployment. + */ + function setupEnvironmentVariables() internal { + vm.setEnv("DKIM_SIGNER", vm.toString(vm.addr(5))); + address dkimRegistrySigner = vm.envOr("DKIM_SIGNER", address(0)); + + // Deploy DKIM Registry and set up proxy + address dkimRegistry = deployDKIMRegistry(dkimRegistrySigner); + vm.setEnv("DKIM_REGISTRY", vm.toString(address(dkimRegistry))); + + vm.setEnv("MINIMUM_DELAY", vm.toString(uint256(0))); + vm.setEnv("KILL_SWITCH_AUTHORIZER", vm.toString(vm.addr(1))); + + // Set EmailAuth implementation address + address emailAuthImpl = address(new EmailAuth()); + vm.setEnv("EMAIL_AUTH_IMPL", vm.toString(emailAuthImpl)); + + // Set additional environment variables + vm.setEnv("NEW_OWNER", vm.toString(vm.addr(8))); + vm.setEnv("VALIDATOR", vm.toString(vm.addr(9))); + vm.setEnv("ACCOUNT_SALT", vm.toString(bytes32(uint256(1)))); + } + + /** + * @dev Deploys the ECDSAOwnedDKIMRegistry contract and sets up its proxy. + * @param dkimRegistrySigner The address of the DKIM registry signer. + * @return The address of the deployed DKIM Registry contract. + */ + function deployDKIMRegistry(address dkimRegistrySigner) internal returns (address) { + ECDSAOwnedDKIMRegistry dkimImpl = new ECDSAOwnedDKIMRegistry(); + console2.log("ECDSAOwnedDKIMRegistry implementation deployed at: %s", address(dkimImpl)); + ERC1967Proxy dkimProxy = new ERC1967Proxy( + address(dkimImpl), + abi.encodeCall( + dkimImpl.initialize, (vm.addr(vm.envUint("PRIVATE_KEY")), dkimRegistrySigner) + ) + ); + return address(ECDSAOwnedDKIMRegistry(address(dkimProxy))); + } + + /** + * @dev Deploys the EmailRecoveryUniversalFactory and sets up the recovery module. + * @param verifier The address of the deployed Verifier contract. + */ + function deployEmailRecoveryModule(address verifier) internal { + address _factory = + address(new EmailRecoveryUniversalFactory(verifier, vm.envAddress("EMAIL_AUTH_IMPL"))); + EmailRecoveryUniversalFactory factory = EmailRecoveryUniversalFactory(_factory); + (address module,) = factory.deployUniversalEmailRecoveryModule( + bytes32(uint256(0)), + bytes32(uint256(0)), + type(EmailRecoveryCommandHandler).creationCode, + vm.envUint("MINIMUM_DELAY"), + vm.envAddress("KILL_SWITCH_AUTHORIZER"), + vm.envAddress("DKIM_REGISTRY") + ); + vm.setEnv("RECOVERY_MODULE", vm.toString(module)); + } +} diff --git a/script/test/Compute7579RecoveryDataHash.t.sol b/script/test/Compute7579RecoveryDataHash.t.sol new file mode 100644 index 00000000..a440a0a8 --- /dev/null +++ b/script/test/Compute7579RecoveryDataHash.t.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.12; + +import { Compute7579RecoveryDataHash } from "../Compute7579RecoveryDataHash.s.sol"; +import { BaseDeployTest } from "./BaseDeployTest.sol"; + +contract Compute7579RecoveryDataHashTest is BaseDeployTest { + function setUp() public override { + super.setUp(); + } + + function testRun() public { + Compute7579RecoveryDataHash target = new Compute7579RecoveryDataHash(); + target.run(); + } +} diff --git a/script/test/DeployEmailRecoveryModule.t.sol b/script/test/DeployEmailRecoveryModule.t.sol new file mode 100644 index 00000000..5bfec904 --- /dev/null +++ b/script/test/DeployEmailRecoveryModule.t.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.12; + +import { DeployEmailRecoveryModuleScript } from "../DeployEmailRecoveryModule.s.sol"; +import { BaseDeployTest } from "./BaseDeployTest.sol"; + +/** + * @title DeployEmailRecoveryModule_Test + * @dev Test contract for deploying the Email Recovery Module + */ +contract DeployEmailRecoveryModule_Test is BaseDeployTest { + /** + * @dev Sets up the test environment. + */ + function setUp() public override { + super.setUp(); + } + + /** + * @dev Tests that the standard deployment process executes correctly. + */ + function test_run() public { + DeployEmailRecoveryModuleScript target = new DeployEmailRecoveryModuleScript(); + target.run(); + } + + /** + * @dev Tests the deployment process when the VERIFIER environment variable is not set. + */ + function test_run_no_verifier() public { + vm.setEnv("VERIFIER", vm.toString(address(0))); + DeployEmailRecoveryModuleScript target = new DeployEmailRecoveryModuleScript(); + target.run(); + } + + /** + * @dev Tests the deployment process when the DKIM_REGISTRY environment variable is not set. + */ + function test_run_no_dkim_registry() public { + vm.setEnv("DKIM_REGISTRY", vm.toString(address(0))); + DeployEmailRecoveryModuleScript target = new DeployEmailRecoveryModuleScript(); + target.run(); + } +} + +/** + * @title DeployEmailRecoveryModule_TestFail + * @dev Test contract for failure scenarios when deploying the Email Recovery Module + */ +contract DeployEmailRecoveryModule_TestFail is BaseDeployTest { + /** + * @dev Sets up the test environment. + */ + function setUp() public override { + super.setUp(); + } + + /** + * @dev Tests that deployment fails when both DKIM_REGISTRY and DKIM_SIGNER environment + * variables are + * not set. + */ + function testFail_run_no_dkim_registry_no_signer() public { + vm.setEnv("DKIM_REGISTRY", vm.toString(address(0))); + vm.setEnv("DKIM_SIGNER", vm.toString(address(0))); + DeployEmailRecoveryModuleScript target = new DeployEmailRecoveryModuleScript(); + target.run(); + } +} diff --git a/script/test/DeploySafeNativeRecovery.t.sol b/script/test/DeploySafeNativeRecovery.t.sol new file mode 100644 index 00000000..9d73e998 --- /dev/null +++ b/script/test/DeploySafeNativeRecovery.t.sol @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.12; + +import { DeploySafeNativeRecovery_Script } from "../DeploySafeNativeRecovery.s.sol"; +import { BaseDeployTest } from "./BaseDeployTest.sol"; + +contract DeploySafeNativeRecovery_Test is BaseDeployTest { + /** + * @notice Tests the basic deployment and execution of the DeploySafeNativeRecovery script. + */ + function test_run() public { + // Set up the base test environment + BaseDeployTest.setUp(); + + // Instantiate the script and run it + DeploySafeNativeRecovery_Script target = new DeploySafeNativeRecovery_Script(); + target.run(); + } + + /** + * @notice Tests the deployment and execution of the DeploySafeNativeRecovery script + * without a verifier configured. + */ + function test_run_no_verifier() public { + // Set up the base test environment + BaseDeployTest.setUp(); + + // Disable the VERIFIER environment variable + vm.setEnv("ZK_VERIFIER", vm.toString(address(0))); + + // Instantiate the script and run it + DeploySafeNativeRecovery_Script target = new DeploySafeNativeRecovery_Script(); + target.run(); + } + + /** + * @notice Tests the deployment and execution of the DeploySafeNativeRecovery script + * without a DKIM registry configured. + */ + function test_run_no_dkim_registry() public { + // Set up the base test environment + BaseDeployTest.setUp(); + + // Disable the DKIM_REGISTRY environment variable + vm.setEnv("DKIM_REGISTRY", vm.toString(address(0))); + + // Instantiate the script and run it + DeploySafeNativeRecovery_Script target = new DeploySafeNativeRecovery_Script(); + target.run(); + } + + /** + * @notice Tests the deployment and execution of the DeploySafeNativeRecovery script + * without a DKIM_SIGNER configured. + */ + function test_run_no_signer() public { + // Set up the base test environment + BaseDeployTest.setUp(); + + // Disable the DKIM_SIGNER environment variable + vm.setEnv("DKIM_SIGNER", vm.toString(address(0))); + + // Instantiate the script and run it + DeploySafeNativeRecovery_Script target = new DeploySafeNativeRecovery_Script(); + target.run(); + } +} + +contract DeploySafeNativeRecovery_TestFail is BaseDeployTest { + /** + * @notice Tests that the DeploySafeNativeRecovery script fails to run + * when both DKIM registry and signer are not configured. + */ + function testFail_run_no_dkim_registry_no_signer() public { + // Set up the base test environment + BaseDeployTest.setUp(); + + // Disable the DKIM_REGISTRY and DKIM_SIGNER environment variables + vm.setEnv("DKIM_REGISTRY", vm.toString(address(0))); + vm.setEnv("DKIM_SIGNER", vm.toString(address(0))); + + // Instantiate the script and attempt to run it, expecting failure + DeploySafeNativeRecovery_Script target = new DeploySafeNativeRecovery_Script(); + target.run(); + } +} diff --git a/script/test/DeploySafeRecovery.t.sol b/script/test/DeploySafeRecovery.t.sol new file mode 100644 index 00000000..21181469 --- /dev/null +++ b/script/test/DeploySafeRecovery.t.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.12; + +import { DeploySafeRecovery_Script } from "../DeploySafeRecovery.s.sol"; +import { BaseDeployTest } from "./BaseDeployTest.sol"; + +contract DeploySafeRecovery_Test is BaseDeployTest { + /** + * @notice Tests the standard run scenario. + */ + function test_run() public { + BaseDeployTest.setUp(); + DeploySafeRecovery_Script target = new DeploySafeRecovery_Script(); + target.run(); + } + + /** + * @notice Tests the run function without a verifier set. + */ + function test_run_no_verifier() public { + BaseDeployTest.setUp(); + vm.setEnv("VERIFIER", vm.toString(address(0))); + DeploySafeRecovery_Script target = new DeploySafeRecovery_Script(); + target.run(); + } + + /** + * @notice Tests the run function without a DKIM registry. + */ + function test_run_no_dkim_registry() public { + BaseDeployTest.setUp(); + vm.setEnv("DKIM_REGISTRY", vm.toString(address(0))); + DeploySafeRecovery_Script target = new DeploySafeRecovery_Script(); + target.run(); + } +} diff --git a/script/test/DeployUniversalEmailRecoveryModule.t.sol b/script/test/DeployUniversalEmailRecoveryModule.t.sol new file mode 100644 index 00000000..150928b5 --- /dev/null +++ b/script/test/DeployUniversalEmailRecoveryModule.t.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.12; + +import { DeployUniversalEmailRecoveryModuleScript } from + "../DeployUniversalEmailRecoveryModule.s.sol"; +import { BaseDeployTest } from "./BaseDeployTest.sol"; + +/// @title DeployUniversalEmailRecoveryModule_Test +/// @notice Contains tests for deploying the Universal Email Recovery Module +contract DeployUniversalEmailRecoveryModule_Test is BaseDeployTest { + /// @notice Tests the standard deployment run + function test_run() public { + BaseDeployTest.setUp(); + DeployUniversalEmailRecoveryModuleScript target = + new DeployUniversalEmailRecoveryModuleScript(); + target.run(); + } + + /// @notice Tests the deployment run without a verifier + function test_run_no_verifier() public { + BaseDeployTest.setUp(); + vm.setEnv("VERIFIER", vm.toString(address(0))); + DeployUniversalEmailRecoveryModuleScript target = + new DeployUniversalEmailRecoveryModuleScript(); + target.run(); + } + + /// @notice Tests the deployment run without a DKIM registry + function test_run_no_dkim_registry() public { + BaseDeployTest.setUp(); + vm.setEnv("DKIM_REGISTRY", vm.toString(address(0))); + DeployUniversalEmailRecoveryModuleScript target = + new DeployUniversalEmailRecoveryModuleScript(); + target.run(); + } +} + +/// @title DeployUniversalEmailRecoveryModule_TestFail +/// @notice Contains failing tests for deploying the Universal Email Recovery Module +contract DeployUniversalEmailRecoveryModule_TestFail is BaseDeployTest { + /// @notice Tests the deployment run failure without DKIM registry and signer + function testFail_run_no_dkim_registry_no_signer() public { + BaseDeployTest.setUp(); + vm.setEnv("DKIM_REGISTRY", vm.toString(address(0))); + vm.setEnv("DKIM_SIGNER", vm.toString(address(0))); + DeployUniversalEmailRecoveryModuleScript target = + new DeployUniversalEmailRecoveryModuleScript(); + target.run(); + } +} diff --git a/src/EmailRecoveryManager.sol b/src/EmailRecoveryManager.sol index c8ba6ce5..66433238 100644 --- a/src/EmailRecoveryManager.sol +++ b/src/EmailRecoveryManager.sol @@ -2,9 +2,11 @@ pragma solidity ^0.8.25; import { EmailAccountRecovery } from - "ether-email-auth/packages/contracts/src/EmailAccountRecovery.sol"; + "@zk-email/ether-email-auth-contracts/src/EmailAccountRecovery.sol"; +import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; import { IEmailRecoveryManager } from "./interfaces/IEmailRecoveryManager.sol"; -import { IEmailRecoverySubjectHandler } from "./interfaces/IEmailRecoverySubjectHandler.sol"; +import { IEmailRecoveryCommandHandler } from "./interfaces/IEmailRecoveryCommandHandler.sol"; import { GuardianManager } from "./GuardianManager.sol"; import { GuardianStorage, GuardianStatus } from "./libraries/EnumerableGuardianMap.sol"; @@ -15,24 +17,26 @@ import { GuardianStorage, GuardianStatus } from "./libraries/EnumerableGuardianM * guardian contracts and handling email verification. * * This contract defines an implementation for email-based recovery. It is designed to - * provide the core logic for email based account recovery that can be used across different modular - * account implementations. + * provide the core logic for email-based account recovery that can be used across different + * account implementations. The core logic is agnostic to the account implementation and could be + * implemented as part of a smart account module, or on a smart account itself. * - * EmailRecoveryManager relies on a dedicated recovery module to execute a recovery attempt. This - * (EmailRecoveryManager) contract defines "what a valid recovery attempt is for an account", and - * the recovery module defines “how that recovery attempt is executed on the account”. A - * specific email subject handler is also accociated with a recovery manager. A subject handler - * defines and validates the recovery email subjects. Developers can write their own subject - * handlers to make specifc subjects for recovering modules + * EmailRecoveryManager defines "what a valid recovery attempt is for an account", and leaves + * defining "how that recovery attempt is executed on the account" to contracts implementing + * EmailRecoveryManager. A specific email command handler is also accociated with a recovery + * manager. A command handler defines and validates the recovery email commands. Developers can + * write their own command handlers to make specifc commands */ abstract contract EmailRecoveryManager is EmailAccountRecovery, GuardianManager, + Ownable, IEmailRecoveryManager { /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* CONSTANTS & STORAGE */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + using EnumerableSet for EnumerableSet.AddressSet; /** * Minimum required time window between when a recovery attempt becomes valid and when it @@ -41,9 +45,25 @@ abstract contract EmailRecoveryManager is uint256 public constant MINIMUM_RECOVERY_WINDOW = 2 days; /** - * The subject handler that returns and validates the subject templates + * The cooldown period after which a subsequent recovery attempt can be initiated by the same + * guardian */ - address public immutable subjectHandler; + uint256 public constant CANCEL_EXPIRED_RECOVERY_COOLDOWN = 1 days; + + /** + * The command handler that returns and validates the command templates + */ + address public immutable commandHandler; + + /** + * boolean flag for the kill switch being enabled or disabled + */ + bool public killSwitchEnabled; + + /** + * The minimum delay before a successful recovery attempt can be executed + */ + uint256 public immutable minimumDelay; /** * Account address to recovery config @@ -55,12 +75,22 @@ abstract contract EmailRecoveryManager is */ mapping(address account => RecoveryRequest recoveryRequest) internal recoveryRequests; + /** + * Account address to previous recovery request + */ + mapping(address account => PreviousRecoveryRequest previousRecoveryRequest) internal + previousRecoveryRequests; + constructor( address _verifier, address _dkimRegistry, address _emailAuthImpl, - address _subjectHandler - ) { + address _commandHandler, + uint256 _minimumDelay, + address _killSwitchAuthorizer + ) + Ownable(_killSwitchAuthorizer) + { if (_verifier == address(0)) { revert InvalidVerifier(); } @@ -70,13 +100,17 @@ abstract contract EmailRecoveryManager is if (_emailAuthImpl == address(0)) { revert InvalidEmailAuthImpl(); } - if (_subjectHandler == address(0)) { - revert InvalidSubjectHandler(); + if (_commandHandler == address(0)) { + revert InvalidCommandHandler(); + } + if (_killSwitchAuthorizer == address(0)) { + revert InvalidKillSwitchAuthorizer(); } verifierAddr = _verifier; dkimAddr = _dkimRegistry; emailAuthImplementationAddr = _emailAuthImpl; - subjectHandler = _subjectHandler; + commandHandler = _commandHandler; + minimumDelay = _minimumDelay; } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ @@ -95,12 +129,60 @@ abstract contract EmailRecoveryManager is /** * @notice Retrieves the recovery request details for a given account + * @dev Does not return guardianVoted as that is part of a nested mapping * @param account The address of the account for which the recovery request details are being * retrieved - * @return RecoveryRequest The recovery request details for the specified account + * @return executeAfter The timestamp from which the recovery request can be executed + * @return executeBefore The timestamp from which the recovery request becomes invalid + * @return currentWeight Total weight of all guardian approvals for the recovery request + * @return recoveryDataHash The keccak256 hash of the recovery data used to execute the recovery + * attempt + */ + function getRecoveryRequest(address account) + external + view + returns ( + uint256 executeAfter, + uint256 executeBefore, + uint256 currentWeight, + bytes32 recoveryDataHash + ) + { + return ( + recoveryRequests[account].executeAfter, + recoveryRequests[account].executeBefore, + recoveryRequests[account].currentWeight, + recoveryRequests[account].recoveryDataHash + ); + } + + /** + * @notice Retrieves the previous recovery request details for a given account + * @dev the previous recovery request is stored as this helps prevent guardians threatening the + * liveness of recovery attempts by submitting malicious recovery hashes before honest guardians + * correctly submit theirs. See `processRecovery` and `cancelExpiredRecovery` for more details + * @param account The address of the account for which the previous recovery request details are + * being retrieved + * @return PreviousRecoveryRequest The previous recovery request for the specified account + */ + function getPreviousRecoveryRequest(address account) + external + view + returns (PreviousRecoveryRequest memory) + { + return previousRecoveryRequests[account]; + } + + /** + * @notice Returns whether a guardian has voted on the current recovery request for a given + * account + * @param account The address of the account for which the recovery request is being checked + * @param guardian The address of the guardian to check voted status + * @return bool The boolean value indicating whether the guardian has voted on the recovery + * request */ - function getRecoveryRequest(address account) external view returns (RecoveryRequest memory) { - return recoveryRequests[account]; + function hasGuardianVoted(address account, address guardian) public view returns (bool) { + return recoveryRequests[account].guardianVoted.contains(guardian); } /** @@ -113,40 +195,40 @@ abstract contract EmailRecoveryManager is } /** - * @notice Returns a two-dimensional array of strings representing the subject templates for an + * @notice Returns a two-dimensional array of strings representing the command templates for an * acceptance by a new guardian. - * @dev This is retrieved from the associated subject handler. Developers can write their own - * subject handlers, this is useful for account implementations which require different data in - * the subject or if the email should be in a language that is not English. + * @dev This is retrieved from the associated command handler. Developers can write their own + * command handlers, this is useful for account implementations which require different data in + * the command or if the email should be in a language that is not English. * @return string[][] A two-dimensional array of strings, where each inner array represents a - * set of fixed strings and matchers for a subject template. + * set of fixed strings and matchers for a command template. */ - function acceptanceSubjectTemplates() public view override returns (string[][] memory) { - return IEmailRecoverySubjectHandler(subjectHandler).acceptanceSubjectTemplates(); + function acceptanceCommandTemplates() public view override returns (string[][] memory) { + return IEmailRecoveryCommandHandler(commandHandler).acceptanceCommandTemplates(); } /** - * @notice Returns a two-dimensional array of strings representing the subject templates for + * @notice Returns a two-dimensional array of strings representing the command templates for * email recovery. - * @dev This is retrieved from the associated subject handler. Developers can write their own - * subject handlers, this is useful for account implementations which require different data in - * the subject or if the email should be in a language that is not English. + * @dev This is retrieved from the associated command handler. Developers can write their own + * command handlers, this is useful for account implementations which require different data in + * the command or if the email should be in a language that is not English. * @return string[][] A two-dimensional array of strings, where each inner array represents a - * set of fixed strings and matchers for a subject template. + * set of fixed strings and matchers for a command template. */ - function recoverySubjectTemplates() public view override returns (string[][] memory) { - return IEmailRecoverySubjectHandler(subjectHandler).recoverySubjectTemplates(); + function recoveryCommandTemplates() public view override returns (string[][] memory) { + return IEmailRecoveryCommandHandler(commandHandler).recoveryCommandTemplates(); } /** - * @notice Extracts the account address to be recovered from the subject parameters of an + * @notice Extracts the account address to be recovered from the command parameters of an * acceptance email. - * @dev This is retrieved from the associated subject handler. - * @param subjectParams The subject parameters of the acceptance email. - * @param templateIdx The index of the acceptance subject template. + * @dev This is retrieved from the associated command handler. + * @param commandParams The command parameters of the acceptance email. + * @param templateIdx The index of the acceptance command template. */ - function extractRecoveredAccountFromAcceptanceSubject( - bytes[] memory subjectParams, + function extractRecoveredAccountFromAcceptanceCommand( + bytes[] memory commandParams, uint256 templateIdx ) public @@ -154,19 +236,19 @@ abstract contract EmailRecoveryManager is override returns (address) { - return IEmailRecoverySubjectHandler(subjectHandler) - .extractRecoveredAccountFromAcceptanceSubject(subjectParams, templateIdx); + return IEmailRecoveryCommandHandler(commandHandler) + .extractRecoveredAccountFromAcceptanceCommand(commandParams, templateIdx); } /** - * @notice Extracts the account address to be recovered from the subject parameters of a + * @notice Extracts the account address to be recovered from the command parameters of a * recovery email. - * @dev This is retrieved from the associated subject handler. - * @param subjectParams The subject parameters of the recovery email. - * @param templateIdx The index of the recovery subject template. + * @dev This is retrieved from the associated command handler. + * @param commandParams The command parameters of the recovery email. + * @param templateIdx The index of the recovery command template. */ - function extractRecoveredAccountFromRecoverySubject( - bytes[] memory subjectParams, + function extractRecoveredAccountFromRecoveryCommand( + bytes[] memory commandParams, uint256 templateIdx ) public @@ -174,8 +256,8 @@ abstract contract EmailRecoveryManager is override returns (address) { - return IEmailRecoverySubjectHandler(subjectHandler) - .extractRecoveredAccountFromRecoverySubject(subjectParams, templateIdx); + return IEmailRecoveryCommandHandler(commandHandler) + .extractRecoveredAccountFromRecoveryCommand(commandParams, templateIdx); } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ @@ -202,6 +284,7 @@ abstract contract EmailRecoveryManager is uint256 expiry ) internal + onlyWhenActive { address account = msg.sender; @@ -226,17 +309,19 @@ abstract contract EmailRecoveryManager is * that no recovery is in process. * @param recoveryConfig The new recovery configuration to be set for the caller's account */ - function updateRecoveryConfig( - RecoveryConfig memory recoveryConfig - ) + function updateRecoveryConfig(RecoveryConfig memory recoveryConfig) public onlyWhenNotRecovering + onlyWhenActive { address account = msg.sender; if (guardianConfigs[account].threshold == 0) { revert AccountNotConfigured(); } + if (recoveryConfig.delay < minimumDelay) { + revert DelayLessThanMinimumDelay(recoveryConfig.delay, minimumDelay); + } if (recoveryConfig.delay > recoveryConfig.expiry) { revert DelayMoreThanExpiry(recoveryConfig.delay, recoveryConfig.expiry); } @@ -263,21 +348,22 @@ abstract contract EmailRecoveryManager is * part of handleAcceptance in EmailAccountRecovery * @param guardian The address of the guardian to be accepted * @param templateIdx The index of the template used for acceptance - * @param subjectParams An array of bytes containing the subject parameters + * @param commandParams An array of bytes containing the command parameters * @param {nullifier} Unused parameter. The nullifier acts as a unique identifier for an email, * but it is not required in this implementation */ function acceptGuardian( address guardian, uint256 templateIdx, - bytes[] memory subjectParams, + bytes[] memory commandParams, bytes32 /* nullifier */ ) internal override + onlyWhenActive { - address account = IEmailRecoverySubjectHandler(subjectHandler).validateAcceptanceSubject( - templateIdx, subjectParams + address account = IEmailRecoveryCommandHandler(commandHandler).validateAcceptanceCommand( + templateIdx, commandParams ); if (recoveryRequests[account].currentWeight > 0) { @@ -289,7 +375,7 @@ abstract contract EmailRecoveryManager is } // This check ensures GuardianStatus is correct and also implicitly that the - // account in email is a valid account + // account in the email is a valid account GuardianStorage memory guardianStorage = getGuardian(account, guardian); if (guardianStorage.status != GuardianStatus.REQUESTED) { revert InvalidGuardianStatus(guardianStorage.status, GuardianStatus.REQUESTED); @@ -309,23 +395,24 @@ abstract contract EmailRecoveryManager is * @notice Processes a recovery request for a given account. This is the third core function * that must be called during the end-to-end recovery flow * @dev Called once per guardian until the threshold is reached - * @param guardian The address of the guardian initiating the recovery + * @param guardian The address of the guardian initiating/voting on the recovery request * @param templateIdx The index of the template used for the recovery request - * @param subjectParams An array of bytes containing the subject parameters + * @param commandParams An array of bytes containing the command parameters * @param {nullifier} Unused parameter. The nullifier acts as a unique identifier for an email, * but it is not required in this implementation */ function processRecovery( address guardian, uint256 templateIdx, - bytes[] memory subjectParams, + bytes[] memory commandParams, bytes32 /* nullifier */ ) internal override + onlyWhenActive { - address account = IEmailRecoverySubjectHandler(subjectHandler).validateRecoverySubject( - templateIdx, subjectParams + address account = IEmailRecoveryCommandHandler(commandHandler).validateRecoveryCommand( + templateIdx, commandParams ); if (!isActivated(account)) { @@ -340,20 +427,42 @@ abstract contract EmailRecoveryManager is } // This check ensures GuardianStatus is correct and also implicitly that the - // account in email is a valid account + // account in the email is a valid account GuardianStorage memory guardianStorage = getGuardian(account, guardian); if (guardianStorage.status != GuardianStatus.ACCEPTED) { revert InvalidGuardianStatus(guardianStorage.status, GuardianStatus.ACCEPTED); } RecoveryRequest storage recoveryRequest = recoveryRequests[account]; - bytes32 recoveryDataHash = IEmailRecoverySubjectHandler(subjectHandler) - .parseRecoveryDataHash(templateIdx, subjectParams); + bytes32 recoveryDataHash = IEmailRecoveryCommandHandler(commandHandler) + .parseRecoveryDataHash(templateIdx, commandParams); + + if (hasGuardianVoted(account, guardian)) { + revert GuardianAlreadyVoted(); + } + + // A malicious guardian can submit an invalid recovery hash that the + // other guardians do not agree with, and also re-submit the same invalid hash once + // the expired recovery attempt has been cancelled, thereby threatening the + // liveness of the recovery attempt. Adding a cooldown period in this scenario gives other + // guardians time to react before the malicious guardian adds another recovery hash + uint256 guardianCount = guardianConfigs[account].guardianCount; + bool cooldownNotExpired = + previousRecoveryRequests[account].cancelRecoveryCooldown > block.timestamp; + if ( + previousRecoveryRequests[account].previousGuardianInitiated == guardian + && cooldownNotExpired && guardianCount > 1 + ) { + revert GuardianMustWaitForCooldown(guardian); + } + // If recoveryDataHash is 0, this is the first guardian and the request is initialized if (recoveryRequest.recoveryDataHash == bytes32(0)) { recoveryRequest.recoveryDataHash = recoveryDataHash; + previousRecoveryRequests[account].previousGuardianInitiated = guardian; uint256 executeBefore = block.timestamp + recoveryConfigs[account].expiry; recoveryRequest.executeBefore = executeBefore; + emit RecoveryRequestStarted(account, guardian, executeBefore, recoveryDataHash); } if (recoveryRequest.recoveryDataHash != recoveryDataHash) { @@ -361,12 +470,13 @@ abstract contract EmailRecoveryManager is } recoveryRequest.currentWeight += guardianStorage.weight; - + recoveryRequest.guardianVoted.add(guardian); + emit GuardianVoted(account, guardian, recoveryRequest.currentWeight, guardianStorage.weight); if (recoveryRequest.currentWeight >= guardianConfig.threshold) { uint256 executeAfter = block.timestamp + recoveryConfigs[account].delay; recoveryRequest.executeAfter = executeAfter; - emit RecoveryProcessed( + emit RecoveryRequestComplete( account, guardian, executeAfter, recoveryRequest.executeBefore, recoveryDataHash ); } @@ -381,21 +491,28 @@ abstract contract EmailRecoveryManager is * core function that must be called during the end-to-end recovery flow. Can be called by * anyone. * @dev Validates the recovery request by checking the total weight, that the delay has passed, - * and the request has not expired. Triggers the recovery module to perform the recovery. This - * function deletes the recovery request but recovery config state is maintained so future - * recovery requests can be made without having to reconfigure everything + * and the request has not expired. Calls the virtual `recover()` function which triggers + * recovery. This function deletes the recovery request but recovery config state is maintained + * so future recovery requests can be made without having to reconfigure everything * @param account The address of the account for which the recovery is being completed * @param recoveryData The data that is passed to recover the validator or account. * recoveryData = abi.encode(validatorOrAccount, recoveryFunctionCalldata). Although, it is - * possible to design a recovery module using this manager without encoding the validator or - * account, depending on how the handler.parseRecoveryDataHash() and module.recover() functions + * possible to design an account/module using this manager without encoding the validator or + * account, depending on how the `handler.parseRecoveryDataHash()` and `recover()` functions * are implemented */ - function completeRecovery(address account, bytes calldata recoveryData) external override { + function completeRecovery( + address account, + bytes calldata recoveryData + ) + external + override + onlyWhenActive + { if (account == address(0)) { revert InvalidAccountAddress(); } - RecoveryRequest memory recoveryRequest = recoveryRequests[account]; + RecoveryRequest storage recoveryRequest = recoveryRequests[account]; uint256 threshold = guardianConfigs[account].threshold; if (threshold == 0) { @@ -419,7 +536,7 @@ abstract contract EmailRecoveryManager is revert InvalidRecoveryDataHash(recoveryDataHash, recoveryRequest.recoveryDataHash); } - delete recoveryRequests[account]; + clearRecoveryRequest(account); recover(account, recoveryData); @@ -427,16 +544,16 @@ abstract contract EmailRecoveryManager is } /** - * @notice Called during completeRecovery to finalize recovery. Contains recovery module - * implementation-specific logic to recover an account/module + * @notice Called during completeRecovery to finalize recovery. Contains implementation-specific + * logic to recover an account * @dev this is the only function that must be implemented by consuming contracts to use the * email recovery manager. This does not encompass other important logic such as module - * installation, that logic is specific to each module and must be implemeted separately + * installation, that logic is specific to each implementation and must be implemeted separately * @param account The address of the account for which the recovery is being completed * @param recoveryData The data that is passed to recover the validator or account. * recoveryData = abi.encode(validatorOrAccount, recoveryFunctionCalldata). Although, it is - * possible to design a recovery module using this manager without encoding the validator or - * account, depending on how the handler.parseRecoveryDataHash() and module.recover() functions + * possible to design an account/module using this manager without encoding the validator or + * account, depending on how the `handler.parseRecoveryDataHash()` and `recover()` functions * are implemented */ function recover(address account, bytes calldata recoveryData) internal virtual; @@ -449,11 +566,11 @@ abstract contract EmailRecoveryManager is * @notice Cancels the recovery request for the caller's account * @dev Deletes the current recovery request associated with the caller's account */ - function cancelRecovery() external { + function cancelRecovery() external onlyWhenActive { if (recoveryRequests[msg.sender].currentWeight == 0) { revert NoRecoveryInProcess(); } - delete recoveryRequests[msg.sender]; + clearRecoveryRequest(msg.sender); emit RecoveryCancelled(msg.sender); } @@ -463,7 +580,7 @@ abstract contract EmailRecoveryManager is * request has expired. * @param account The address of the account for which the recovery is being cancelled */ - function cancelExpiredRecovery(address account) external { + function cancelExpiredRecovery(address account) external onlyWhenActive { if (recoveryRequests[account].currentWeight == 0) { revert NoRecoveryInProcess(); } @@ -472,14 +589,17 @@ abstract contract EmailRecoveryManager is account, block.timestamp, recoveryRequests[account].executeBefore ); } - delete recoveryRequests[account]; + previousRecoveryRequests[account].cancelRecoveryCooldown = + block.timestamp + CANCEL_EXPIRED_RECOVERY_COOLDOWN; + clearRecoveryRequest(account); emit RecoveryCancelled(account); } /** * @notice Removes all state related to msg.sender. - * @dev In order to prevent unexpected behaviour when reinstalling account modules, the module - * should be deinitialized. This should include removing state accociated with an account. + * @dev A feature specifically important for smart account modules - in order to prevent + * unexpected behaviour when reinstalling account modules, the contract state should be + * deinitialized. This should include removing state accociated with an account. */ function deInitRecoveryModule() internal onlyWhenNotRecovering { address account = msg.sender; @@ -489,18 +609,46 @@ abstract contract EmailRecoveryManager is /** * @notice Removes all state related to an account. * @dev Although this function is internal, it should be used carefully as it can be called by - * anyone. In order to prevent unexpected behaviour when reinstalling account modules, the - * module should be deinitialized. This should include removing state accociated with an - * account + * anyone. A feature specifically important for smart account modules - in order to prevent + * unexpected behaviour when reinstalling account modules, the contract state should be + * deinitialized. This should include removing state accociated with an account * @param account The address of the account for which recovery is being deinitialized */ function deInitRecoveryModule(address account) internal onlyWhenNotRecovering { delete recoveryConfigs[account]; - delete recoveryRequests[account]; + clearRecoveryRequest(account); + delete previousRecoveryRequests[account]; removeAllGuardians(account); delete guardianConfigs[account]; emit RecoveryDeInitialized(account); } + + /** + * @notice Clears the recovery request for an account + * @dev Because `guardianVoted` on the `RecoveryRequest` struct is an `EnumerableSet`, we need + * to manually clear all entries. The maximum guardian count is 32, which is enforced by + * `EnumerableGuardianMap.sol`. Therefore no more than 32 values should have to be removed from + * the set + * @param account The address of the account for which the recovery request is being cleared + */ + function clearRecoveryRequest(address account) internal { + RecoveryRequest storage recoveryRequest = recoveryRequests[account]; + + address[] memory guardiansVoted = recoveryRequest.guardianVoted.values(); + uint256 voteCount = guardiansVoted.length; + for (uint256 i = 0; i < voteCount; i++) { + recoveryRequest.guardianVoted.remove(guardiansVoted[i]); + } + delete recoveryRequests[account]; + } + + /** + * @notice Toggles the kill switch on the manager + * @dev Can only be called by the kill switch authorizer + */ + function toggleKillSwitch() external onlyOwner { + killSwitchEnabled = !killSwitchEnabled; + } } diff --git a/src/EmailRecoveryManagerZkSync.sol b/src/EmailRecoveryManagerZkSync.sol new file mode 100644 index 00000000..014686dd --- /dev/null +++ b/src/EmailRecoveryManagerZkSync.sol @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { EmailRecoveryManager } from "./EmailRecoveryManager.sol"; +import { EmailAccountRecovery } from + "@zk-email/ether-email-auth-contracts/src/EmailAccountRecovery.sol"; +import { EmailAccountRecoveryZKSync } from + "@zk-email/ether-email-auth-contracts/src/EmailAccountRecoveryZKSync.sol"; + +/** + * @title EmailRecoveryManagerZkSync + * @notice Provides a mechanism for account recovery using email guardians on ZKSync networks. + * @dev The underlying EmailAccountRecoveryZkSync contract provides some base logic for deploying + * guardian contracts and handling email verification. + */ +abstract contract EmailRecoveryManagerZkSync is EmailRecoveryManager, EmailAccountRecoveryZKSync { + constructor( + address _verifier, + address _dkimRegistry, + address _emailAuthImpl, + address _commandHandler, + uint256 _minimumDelay, + address _killSwitchAuthorizer, + address _factoryAddr, + bytes32 _proxyBytecodeHash + ) + EmailRecoveryManager( + _verifier, + _dkimRegistry, + _emailAuthImpl, + _commandHandler, + _minimumDelay, + _killSwitchAuthorizer + ) + { + if (_factoryAddr == address(0)) { + revert InvalidFactory(); + } + if (_proxyBytecodeHash == bytes32(0)) { + revert InvalidProxyBytecodeHash(); + } + factoryAddr = _factoryAddr; + proxyBytecodeHash = _proxyBytecodeHash; + } + + /// @notice Computes the address for email auth contract using the CREATE2 opcode. + /// @dev This function utilizes the `ZKSyncCreate2Factory` to compute the address. The + /// computation uses a provided account address to be recovered, account salt, + /// and the hash of the encoded ERC1967Proxy creation code concatenated with the encoded email + /// auth contract implementation + /// address and the initialization call data. This ensures that the computed address is + /// deterministic and unique per account salt. + /// @param recoveredAccount The address of the account to be recovered. + /// @param accountSalt A bytes32 salt value defined as a hash of the guardian's email address + /// and an account code. This is assumed to be unique to a pair of the guardian's email address + /// and the wallet address to be recovered. + /// @return address The computed address. + function computeEmailAuthAddress( + address recoveredAccount, + bytes32 accountSalt + ) + public + view + virtual + override(EmailAccountRecovery, EmailAccountRecoveryZKSync) + returns (address) + { + return EmailAccountRecoveryZKSync.computeEmailAuthAddress(recoveredAccount, accountSalt); + } + + /// @notice Deploys a proxy contract for email authentication using the CREATE2 opcode. + /// @dev This function utilizes the `ZKSyncCreate2Factory` to deploy the proxy contract. The + /// deployment uses a provided account address to be recovered, account salt, + /// and the hash of the encoded ERC1967Proxy creation code concatenated with the encoded email + /// auth contract implementation + /// address and the initialization call data. This ensures that the deployed address is + /// deterministic and unique per account salt. + /// @param recoveredAccount The address of the account to be recovered. + /// @param accountSalt A bytes32 salt value defined as a hash of the guardian's email address + /// and an account code. This is assumed to be unique to a pair of the guardian's email address + /// and the wallet address to be recovered. + /// @return address The address of the deployed proxy contract. + function deployEmailAuthProxy( + address recoveredAccount, + bytes32 accountSalt + ) + internal + virtual + override(EmailAccountRecovery, EmailAccountRecoveryZKSync) + returns (address) + { + return EmailAccountRecoveryZKSync.deployEmailAuthProxy(recoveredAccount, accountSalt); + } +} diff --git a/src/GuardianManager.sol b/src/GuardianManager.sol index e9d769c7..1eeafa0c 100644 --- a/src/GuardianManager.sol +++ b/src/GuardianManager.sol @@ -32,12 +32,26 @@ abstract contract GuardianManager is IGuardianManager { * @notice Modifier to check recovery status. Reverts if recovery is in process for the account */ modifier onlyWhenNotRecovering() { - if (IEmailRecoveryManager(address(this)).getRecoveryRequest(msg.sender).currentWeight > 0) { + (,, uint256 currentWeight,) = + IEmailRecoveryManager(address(this)).getRecoveryRequest(msg.sender); + if (currentWeight > 0) { revert RecoveryInProcess(); } _; } + /** + * @notice Modifier to check if the kill switch has been enabled + * @dev This impacts EmailRecoveryManager & GuardianManager + */ + modifier onlyWhenActive() { + bool killSwitchEnabled = IEmailRecoveryManager(address(this)).killSwitchEnabled(); + if (killSwitchEnabled) { + revert KillSwitchEnabled(); + } + _; + } + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* GUARDIAN LOGIC */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ @@ -118,7 +132,14 @@ abstract contract GuardianManager is IGuardianManager { * @param guardian The address of the guardian to be added * @param weight The weight assigned to the guardian */ - function addGuardian(address guardian, uint256 weight) public onlyWhenNotRecovering { + function addGuardian( + address guardian, + uint256 weight + ) + public + onlyWhenNotRecovering + onlyWhenActive + { // Threshold can only be 0 at initialization. // Check ensures that setup function should be called first if (guardianConfigs[msg.sender].threshold == 0) { @@ -163,7 +184,7 @@ abstract contract GuardianManager is IGuardianManager { * no recovery is in process * @param guardian The address of the guardian to be removed */ - function removeGuardian(address guardian) external onlyWhenNotRecovering { + function removeGuardian(address guardian) external onlyWhenNotRecovering onlyWhenActive { GuardianConfig memory guardianConfig = guardianConfigs[msg.sender]; GuardianStorage memory guardianStorage = guardiansStorage[msg.sender].get(guardian); @@ -195,7 +216,7 @@ abstract contract GuardianManager is IGuardianManager { * only if no recovery is in process * @param threshold The new threshold for guardian approvals */ - function changeThreshold(uint256 threshold) external onlyWhenNotRecovering { + function changeThreshold(uint256 threshold) external onlyWhenNotRecovering onlyWhenActive { // Threshold can only be 0 at initialization. // Check ensures that setup function should be called first if (guardianConfigs[msg.sender].threshold == 0) { @@ -207,7 +228,6 @@ abstract contract GuardianManager is IGuardianManager { revert ThresholdExceedsTotalWeight(threshold, guardianConfigs[msg.sender].totalWeight); } - // Guardian weight should be at least 1 if (threshold == 0) { revert ThresholdCannotBeZero(); } @@ -250,4 +270,16 @@ abstract contract GuardianManager is IGuardianManager { function removeAllGuardians(address account) internal { guardiansStorage[account].removeAll(guardiansStorage[account].keys()); } + + /** + * @notice Gets all guardians associated with an account + * @dev Return an array containing all the keys. O(n) where n <= 32 + * + * WARNING: This operation will copy the entire storage to memory, which could + * be quite expensive. + * @param account The address of the account associated with the guardians + */ + function getAllGuardians(address account) external view returns (address[] memory) { + return guardiansStorage[account].keys(); + } } diff --git a/src/factories/EmailRecoveryFactory.sol b/src/factories/EmailRecoveryFactory.sol index 1b43bcef..55036414 100644 --- a/src/factories/EmailRecoveryFactory.sol +++ b/src/factories/EmailRecoveryFactory.sol @@ -7,7 +7,7 @@ import { EmailRecoveryModule } from "../modules/EmailRecoveryModule.sol"; /** * @title EmailRecoveryFactory * @notice This contract facilitates the deployment of email recovery modules and their associated - * subject handlers. + * command handlers. * Create2 is leveraged to ensure deterministic addresses, which assists with module * attestations */ @@ -24,7 +24,7 @@ contract EmailRecoveryFactory { event EmailRecoveryModuleDeployed( address emailRecoveryModule, - address subjectHandler, + address commandHandler, address validator, bytes4 functionSelector ); @@ -44,28 +44,32 @@ contract EmailRecoveryFactory { } /** - * @notice Deploys an email recovery module along with its subject handler - * @dev The subject handler bytecode cannot be determined ahead of time, unlike the recovery + * @notice Deploys an email recovery module along with its command handler + * @dev The command handler bytecode cannot be determined ahead of time, unlike the recovery * module, which is why it is passed in directly. In practice, this means a - * developer will write their own subject handler, and then pass the bytecode into this factory + * developer will write their own command handler, and then pass the bytecode into this factory * function. * * This deployment function deploys an `EmailRecoveryModule`, which takes a target validator and * target function selector - * @param subjectHandlerSalt Salt for the subject handler deployment + * @param commandHandlerSalt Salt for the command handler deployment * @param recoveryModuleSalt Salt for the recovery module deployment - * @param subjectHandlerBytecode Bytecode of the subject handler contract + * @param commandHandlerBytecode Bytecode of the command handler contract + * @param minimumDelay Minimum delay for recovery requests + * @param killSwitchAuthorizer Address of the kill switch authorizer * @param dkimRegistry Address of the DKIM registry * @param validator Address of the validator to be recovered * @param functionSelector Function selector for the recovery function to be called on the * target validator * @return emailRecoveryModule The deployed email recovery module - * @return subjectHandler The deployed subject handler + * @return commandHandler The deployed command handler */ function deployEmailRecoveryModule( - bytes32 subjectHandlerSalt, + bytes32 commandHandlerSalt, bytes32 recoveryModuleSalt, - bytes calldata subjectHandlerBytecode, + bytes calldata commandHandlerBytecode, + uint256 minimumDelay, + address killSwitchAuthorizer, address dkimRegistry, address validator, bytes4 functionSelector @@ -73,20 +77,27 @@ contract EmailRecoveryFactory { external returns (address, address) { - // Deploy subject handler - address subjectHandler = Create2.deploy(0, subjectHandlerSalt, subjectHandlerBytecode); + // Deploy command handler + address commandHandler = Create2.deploy(0, commandHandlerSalt, commandHandlerBytecode); // Deploy recovery module address emailRecoveryModule = address( new EmailRecoveryModule{ salt: recoveryModuleSalt }( - verifier, dkimRegistry, emailAuthImpl, subjectHandler, validator, functionSelector + verifier, + dkimRegistry, + emailAuthImpl, + commandHandler, + minimumDelay, + killSwitchAuthorizer, + validator, + functionSelector ) ); emit EmailRecoveryModuleDeployed( - emailRecoveryModule, subjectHandler, validator, functionSelector + emailRecoveryModule, commandHandler, validator, functionSelector ); - return (emailRecoveryModule, subjectHandler); + return (emailRecoveryModule, commandHandler); } } diff --git a/src/factories/EmailRecoveryUniversalFactory.sol b/src/factories/EmailRecoveryUniversalFactory.sol index eb55fc87..fd9256f9 100644 --- a/src/factories/EmailRecoveryUniversalFactory.sol +++ b/src/factories/EmailRecoveryUniversalFactory.sol @@ -5,9 +5,9 @@ import { Create2 } from "@openzeppelin/contracts/utils/Create2.sol"; import { UniversalEmailRecoveryModule } from "../modules/UniversalEmailRecoveryModule.sol"; /** - * @title EmailRecoveryFactory + * @title EmailRecoveryUniversalFactory * @notice This contract facilitates the deployment of universal email recovery modules and their - * associated subject handlers. + * associated command handlers. * Create2 is leveraged to ensure deterministic addresses, which assists with module * attestations */ @@ -22,7 +22,7 @@ contract EmailRecoveryUniversalFactory { */ address public immutable emailAuthImpl; - event UniversalEmailRecoveryModuleDeployed(address emailRecoveryModule, address subjectHandler); + event UniversalEmailRecoveryModuleDeployed(address emailRecoveryModule, address commandHandler); error InvalidVerifier(); error InvalidEmailAuthImpl(); @@ -39,46 +39,55 @@ contract EmailRecoveryUniversalFactory { } /** - * @notice Deploys a universal email recovery module along with its subject handler - * @dev The subject handler bytecode cannot be determined ahead of time, unlike the recovery + * @notice Deploys a universal email recovery module along with its command handler + * @dev The command handler bytecode cannot be determined ahead of time, unlike the recovery * module, which is why it is passed in directly. In practice, this means a - * developer will write their own subject handler, and then pass the bytecode into this factory - * function. The universal recovery module should have a relatively stable subject handler, - * however, developers may want to write a generic subject handler in a slightly different way, + * developer will write their own command handler, and then pass the bytecode into this factory + * function. The universal recovery module should have a relatively stable command handler, + * however, developers may want to write a generic command handler in a slightly different way, * or even in a non-english lanaguage, so the bytecode is still passed in here directly. * * This deployment function deploys an `UniversalEmailRecoveryModule`, which takes the - * target verifier, dkim registry, EmailAuth implementation and subject handler. The target + * target verifier, dkim registry, EmailAuth implementation and command handler. The target * validator and target function selector are set when a module is installed. This is part of * what makes the module generic for recovering any validator - * @param subjectHandlerSalt Salt for the subject handler deployment + * @param commandHandlerSalt Salt for the command handler deployment * @param recoveryModuleSalt Salt for the recovery module deployment - * @param subjectHandlerBytecode Bytecode of the subject handler contract + * @param commandHandlerBytecode Bytecode of the command handler contract + * @param minimumDelay Minimum delay for recovery requests + * @param killSwitchAuthorizer Address of the kill switch authorizer * @param dkimRegistry Address of the DKIM registry. * @return emailRecoveryModule The deployed email recovery module - * @return subjectHandler The deployed subject handler + * @return commandHandler The deployed command handler */ function deployUniversalEmailRecoveryModule( - bytes32 subjectHandlerSalt, + bytes32 commandHandlerSalt, bytes32 recoveryModuleSalt, - bytes calldata subjectHandlerBytecode, + bytes calldata commandHandlerBytecode, + uint256 minimumDelay, + address killSwitchAuthorizer, address dkimRegistry ) external returns (address, address) { - // Deploy subject handler - address subjectHandler = Create2.deploy(0, subjectHandlerSalt, subjectHandlerBytecode); + // Deploy command handler + address commandHandler = Create2.deploy(0, commandHandlerSalt, commandHandlerBytecode); // Deploy recovery module address emailRecoveryModule = address( new UniversalEmailRecoveryModule{ salt: recoveryModuleSalt }( - verifier, dkimRegistry, emailAuthImpl, subjectHandler + verifier, + dkimRegistry, + emailAuthImpl, + commandHandler, + minimumDelay, + killSwitchAuthorizer ) ); - emit UniversalEmailRecoveryModuleDeployed(emailRecoveryModule, subjectHandler); + emit UniversalEmailRecoveryModuleDeployed(emailRecoveryModule, commandHandler); - return (emailRecoveryModule, subjectHandler); + return (emailRecoveryModule, commandHandler); } } diff --git a/src/handlers/AccountHidingRecoverySubjectHandler.sol b/src/handlers/AccountHidingRecoveryCommandHandler.sol similarity index 63% rename from src/handlers/AccountHidingRecoverySubjectHandler.sol rename to src/handlers/AccountHidingRecoveryCommandHandler.sol index 7b16a261..a600dbc9 100644 --- a/src/handlers/AccountHidingRecoverySubjectHandler.sol +++ b/src/handlers/AccountHidingRecoveryCommandHandler.sol @@ -1,17 +1,17 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { IEmailRecoverySubjectHandler } from "../interfaces/IEmailRecoverySubjectHandler.sol"; -import { StringUtils } from "../libraries/StringUtils.sol"; +import { IEmailRecoveryCommandHandler } from "../interfaces/IEmailRecoveryCommandHandler.sol"; +import { StringUtils } from "@zk-email/ether-email-auth-contracts/src/libraries/StringUtils.sol"; /** - * @title AccountHidingRecoverySubjectHandler - * @notice Handler contract that defines subject templates and how to validate them - * This is the subject handler that does not expose the account address in the email subject + * @title AccountHidingRecoveryCommandHandler + * @notice Handler contract that defines command templates and how to validate them + * This command handler does not expose the account address in the email command */ -contract AccountHidingRecoverySubjectHandler is IEmailRecoverySubjectHandler { +contract AccountHidingRecoveryCommandHandler is IEmailRecoveryCommandHandler { error InvalidTemplateIndex(uint256 templateIdx, uint256 expectedTemplateIdx); - error InvalidSubjectParams(uint256 paramsLength, uint256 expectedParamsLength); + error InvalidCommandParams(uint256 paramsLength, uint256 expectedParamsLength); error InvalidAccount(); error ExistingStoredAccountHash(address account); @@ -19,12 +19,12 @@ contract AccountHidingRecoverySubjectHandler is IEmailRecoverySubjectHandler { mapping(bytes32 accountHash => address account) public accountHashes; /** - * @notice Returns a hard-coded two-dimensional array of strings representing the subject + * @notice Returns a hard-coded two-dimensional array of strings representing the command * templates for an acceptance by a new guardian. * @return string[][] A two-dimensional array of strings, where each inner array represents a - * set of fixed strings and matchers for a subject template. + * set of fixed strings and matchers for a command template. */ - function acceptanceSubjectTemplates() public pure returns (string[][] memory) { + function acceptanceCommandTemplates() public pure returns (string[][] memory) { string[][] memory templates = new string[][](1); templates[0] = new string[](5); templates[0][0] = "Accept"; @@ -36,12 +36,12 @@ contract AccountHidingRecoverySubjectHandler is IEmailRecoverySubjectHandler { } /** - * @notice Returns a hard-coded two-dimensional array of strings representing the subject + * @notice Returns a hard-coded two-dimensional array of strings representing the command * templates for email recovery. * @return string[][] A two-dimensional array of strings, where each inner array represents a - * set of fixed strings and matchers for a subject template. + * set of fixed strings and matchers for a command template. */ - function recoverySubjectTemplates() public pure returns (string[][] memory) { + function recoveryCommandTemplates() public pure returns (string[][] memory) { string[][] memory templates = new string[][](1); templates[0] = new string[](7); templates[0][0] = "Recover"; @@ -55,51 +55,51 @@ contract AccountHidingRecoverySubjectHandler is IEmailRecoverySubjectHandler { } /** - * @notice Extracts the hash of the account address to be recovered from the subject parameters - * of acceptance email and returns the corresponding address stored in the accountHashes. - * @param subjectParams The subject parameters of the acceptance email. + * @notice Extracts the hash of the account address to be recovered from the command parameters + * of the acceptance email and returns the corresponding address stored in accountHashes. + * @param commandParams The command parameters of the acceptance email. * @param {templateIdx} Unused parameter. The index of the template used for acceptance */ - function extractRecoveredAccountFromAcceptanceSubject( - bytes[] calldata subjectParams, + function extractRecoveredAccountFromAcceptanceCommand( + bytes[] calldata commandParams, uint256 /* templateIdx */ ) public view returns (address) { - bytes32 accountHash = StringUtils.hexToBytes32(abi.decode(subjectParams[0], (string))); + bytes32 accountHash = StringUtils.hexToBytes32(abi.decode(commandParams[0], (string))); return accountHashes[accountHash]; } /** - * @notice Extracts the hash of the account address to be recovered from the subject parameters - * of recovery email and returns the corresponding address stored in the accountHashes. - * @param subjectParams The subject parameters of the recovery email. + * @notice Extracts the hash of the account address to be recovered from the command parameters + * of the recovery email and returns the corresponding address stored in accountHashes. + * @param commandParams The command parameters of the recovery email. * @param {templateIdx} Unused parameter. The index of the template used for the recovery * request */ - function extractRecoveredAccountFromRecoverySubject( - bytes[] calldata subjectParams, + function extractRecoveredAccountFromRecoveryCommand( + bytes[] calldata commandParams, uint256 /* templateIdx */ ) public view returns (address) { - bytes32 accountHash = StringUtils.hexToBytes32(abi.decode(subjectParams[0], (string))); + bytes32 accountHash = StringUtils.hexToBytes32(abi.decode(commandParams[0], (string))); return accountHashes[accountHash]; } /** - * @notice Validates the subject params for an acceptance email + * @notice Validates the command params for an acceptance email * @param templateIdx The index of the template used for acceptance - * @param subjectParams The subject parameters of the acceptance email. + * @param commandParams The command parameters of the acceptance email. * @return accountInEmail The account address in the acceptance email */ - function validateAcceptanceSubject( + function validateAcceptanceCommand( uint256 templateIdx, - bytes[] calldata subjectParams + bytes[] calldata commandParams ) external view @@ -108,27 +108,27 @@ contract AccountHidingRecoverySubjectHandler is IEmailRecoverySubjectHandler { if (templateIdx != 0) { revert InvalidTemplateIndex(templateIdx, 0); } - if (subjectParams.length != 1) { - revert InvalidSubjectParams(subjectParams.length, 1); + if (commandParams.length != 1) { + revert InvalidCommandParams(commandParams.length, 1); } // The GuardianStatus check in acceptGuardian implicitly // validates the account, so no need to re-validate here address accountInEmail = - extractRecoveredAccountFromAcceptanceSubject(subjectParams, templateIdx); + extractRecoveredAccountFromAcceptanceCommand(commandParams, templateIdx); return accountInEmail; } /** - * @notice Validates the subject params for an acceptance email + * @notice Validates the command params for an acceptance email * @param templateIdx The index of the template used for the recovery request - * @param subjectParams The subject parameters of the recovery email + * @param commandParams The command parameters of the recovery email * @return accountInEmail The account address in the recovery email */ - function validateRecoverySubject( + function validateRecoveryCommand( uint256 templateIdx, - bytes[] calldata subjectParams + bytes[] calldata commandParams ) public view @@ -137,13 +137,13 @@ contract AccountHidingRecoverySubjectHandler is IEmailRecoverySubjectHandler { if (templateIdx != 0) { revert InvalidTemplateIndex(templateIdx, 0); } - if (subjectParams.length != 2) { - revert InvalidSubjectParams(subjectParams.length, 2); + if (commandParams.length != 2) { + revert InvalidCommandParams(commandParams.length, 2); } address accountInEmail = - extractRecoveredAccountFromRecoverySubject(subjectParams, templateIdx); - string memory recoveryDataHashInEmail = abi.decode(subjectParams[1], (string)); + extractRecoveredAccountFromRecoveryCommand(commandParams, templateIdx); + string memory recoveryDataHashInEmail = abi.decode(commandParams[1], (string)); if (accountInEmail == address(0)) { revert InvalidAccount(); @@ -155,16 +155,16 @@ contract AccountHidingRecoverySubjectHandler is IEmailRecoverySubjectHandler { } /** - * @notice parses the recovery data hash from the subject params. The data hash is + * @notice Parses the recovery data hash from the command params. The data hash is * verified against later when recovery is executed - * @dev recoveryDataHash = abi.encode(validator, recoveryFunctionCalldata) + * @dev recoveryDataHash = keccak256(abi.encode(validatorOrAccount, recoveryFunctionCalldata)) * @param templateIdx The index of the template used for the recovery request - * @param subjectParams The subject parameters of the recovery email + * @param commandParams The command parameters of the recovery email * @return recoveryDataHash The keccak256 hash of the recovery data */ function parseRecoveryDataHash( uint256 templateIdx, - bytes[] calldata subjectParams + bytes[] calldata commandParams ) external pure @@ -173,7 +173,7 @@ contract AccountHidingRecoverySubjectHandler is IEmailRecoverySubjectHandler { if (templateIdx != 0) { revert InvalidTemplateIndex(templateIdx, 0); } - return StringUtils.hexToBytes32(abi.decode(subjectParams[1], (string))); + return StringUtils.hexToBytes32(abi.decode(commandParams[1], (string))); } /** diff --git a/src/handlers/EmailRecoverySubjectHandler.sol b/src/handlers/EmailRecoveryCommandHandler.sol similarity index 60% rename from src/handlers/EmailRecoverySubjectHandler.sol rename to src/handlers/EmailRecoveryCommandHandler.sol index bb812687..e49e296e 100644 --- a/src/handlers/EmailRecoverySubjectHandler.sol +++ b/src/handlers/EmailRecoveryCommandHandler.sol @@ -1,26 +1,26 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { IEmailRecoverySubjectHandler } from "../interfaces/IEmailRecoverySubjectHandler.sol"; -import { StringUtils } from "../libraries/StringUtils.sol"; +import { IEmailRecoveryCommandHandler } from "../interfaces/IEmailRecoveryCommandHandler.sol"; +import { StringUtils } from "@zk-email/ether-email-auth-contracts/src/libraries/StringUtils.sol"; /** - * @title EmailRecoverySubjectHandler - * @notice Handler contract that defines subject templates and how to validate them - * This is the default subject handler that will work with any validator. + * @title EmailRecoveryCommandHandler + * @notice Handler contract that defines command templates and how to validate them + * This is the default command handler that will work with any validator. */ -contract EmailRecoverySubjectHandler is IEmailRecoverySubjectHandler { +contract EmailRecoveryCommandHandler is IEmailRecoveryCommandHandler { error InvalidTemplateIndex(uint256 templateIdx, uint256 expectedTemplateIdx); - error InvalidSubjectParams(uint256 paramsLength, uint256 expectedParamsLength); + error InvalidCommandParams(uint256 paramsLength, uint256 expectedParamsLength); error InvalidAccount(); /** - * @notice Returns a hard-coded two-dimensional array of strings representing the subject + * @notice Returns a hard-coded two-dimensional array of strings representing the command * templates for an acceptance by a new guardian. * @return string[][] A two-dimensional array of strings, where each inner array represents a - * set of fixed strings and matchers for a subject template. + * set of fixed strings and matchers for a command template. */ - function acceptanceSubjectTemplates() public pure returns (string[][] memory) { + function acceptanceCommandTemplates() public pure returns (string[][] memory) { string[][] memory templates = new string[][](1); templates[0] = new string[](5); templates[0][0] = "Accept"; @@ -32,12 +32,12 @@ contract EmailRecoverySubjectHandler is IEmailRecoverySubjectHandler { } /** - * @notice Returns a hard-coded two-dimensional array of strings representing the subject + * @notice Returns a hard-coded two-dimensional array of strings representing the command * templates for email recovery. * @return string[][] A two-dimensional array of strings, where each inner array represents a - * set of fixed strings and matchers for a subject template. + * set of fixed strings and matchers for a command template. */ - function recoverySubjectTemplates() public pure returns (string[][] memory) { + function recoveryCommandTemplates() public pure returns (string[][] memory) { string[][] memory templates = new string[][](1); templates[0] = new string[](7); templates[0][0] = "Recover"; @@ -51,49 +51,49 @@ contract EmailRecoverySubjectHandler is IEmailRecoverySubjectHandler { } /** - * @notice Extracts the account address to be recovered from the subject parameters of an + * @notice Extracts the account address to be recovered from the command parameters of an * acceptance email. - * @param subjectParams The subject parameters of the acceptance email. + * @param commandParams The command parameters of the acceptance email. * @param {templateIdx} Unused parameter. The index of the template used for acceptance */ - function extractRecoveredAccountFromAcceptanceSubject( - bytes[] calldata subjectParams, + function extractRecoveredAccountFromAcceptanceCommand( + bytes[] calldata commandParams, uint256 /* templateIdx */ ) public pure returns (address) { - return abi.decode(subjectParams[0], (address)); + return abi.decode(commandParams[0], (address)); } /** - * @notice Extracts the account address to be recovered from the subject parameters of a + * @notice Extracts the account address to be recovered from the command parameters of a * recovery email. - * @param subjectParams The subject parameters of the recovery email. + * @param commandParams The command parameters of the recovery email. * @param {templateIdx} Unused parameter. The index of the template used for the recovery * request */ - function extractRecoveredAccountFromRecoverySubject( - bytes[] calldata subjectParams, + function extractRecoveredAccountFromRecoveryCommand( + bytes[] calldata commandParams, uint256 /* templateIdx */ ) public pure returns (address) { - return abi.decode(subjectParams[0], (address)); + return abi.decode(commandParams[0], (address)); } /** - * @notice Validates the subject params for an acceptance email + * @notice Validates the command params for an acceptance email * @param templateIdx The index of the template used for acceptance - * @param subjectParams The subject parameters of the acceptance email. + * @param commandParams The command parameters of the acceptance email. * @return accountInEmail The account address in the acceptance email */ - function validateAcceptanceSubject( + function validateAcceptanceCommand( uint256 templateIdx, - bytes[] calldata subjectParams + bytes[] calldata commandParams ) external pure @@ -102,26 +102,26 @@ contract EmailRecoverySubjectHandler is IEmailRecoverySubjectHandler { if (templateIdx != 0) { revert InvalidTemplateIndex(templateIdx, 0); } - if (subjectParams.length != 1) { - revert InvalidSubjectParams(subjectParams.length, 1); + if (commandParams.length != 1) { + revert InvalidCommandParams(commandParams.length, 1); } // The GuardianStatus check in acceptGuardian implicitly // validates the account, so no need to re-validate here - address accountInEmail = abi.decode(subjectParams[0], (address)); + address accountInEmail = abi.decode(commandParams[0], (address)); return accountInEmail; } /** - * @notice Validates the subject params for an acceptance email + * @notice Validates the command params for a recovery email * @param templateIdx The index of the template used for the recovery request - * @param subjectParams The subject parameters of the recovery email + * @param commandParams The command parameters of the recovery email * @return accountInEmail The account address in the recovery email */ - function validateRecoverySubject( + function validateRecoveryCommand( uint256 templateIdx, - bytes[] calldata subjectParams + bytes[] calldata commandParams ) public pure @@ -130,12 +130,12 @@ contract EmailRecoverySubjectHandler is IEmailRecoverySubjectHandler { if (templateIdx != 0) { revert InvalidTemplateIndex(templateIdx, 0); } - if (subjectParams.length != 2) { - revert InvalidSubjectParams(subjectParams.length, 2); + if (commandParams.length != 2) { + revert InvalidCommandParams(commandParams.length, 2); } - address accountInEmail = abi.decode(subjectParams[0], (address)); - string memory recoveryDataHashInEmail = abi.decode(subjectParams[1], (string)); + address accountInEmail = abi.decode(commandParams[0], (address)); + string memory recoveryDataHashInEmail = abi.decode(commandParams[1], (string)); if (accountInEmail == address(0)) { revert InvalidAccount(); @@ -147,16 +147,16 @@ contract EmailRecoverySubjectHandler is IEmailRecoverySubjectHandler { } /** - * @notice parses the recovery data hash from the subject params. The data hash is + * @notice parses the recovery data hash from the command params. The data hash is * verified against later when recovery is executed - * @dev recoveryDataHash = abi.encode(validator, recoveryFunctionCalldata) + * @dev recoveryDataHash = keccak256(abi.encode(validatorOrAccount, recoveryFunctionCalldata)) * @param templateIdx The index of the template used for the recovery request - * @param subjectParams The subject parameters of the recovery email + * @param commandParams The command parameters of the recovery email * @return recoveryDataHash The keccak256 hash of the recovery data */ function parseRecoveryDataHash( uint256 templateIdx, - bytes[] calldata subjectParams + bytes[] calldata commandParams ) external pure @@ -165,6 +165,6 @@ contract EmailRecoverySubjectHandler is IEmailRecoverySubjectHandler { if (templateIdx != 0) { revert InvalidTemplateIndex(templateIdx, 0); } - return StringUtils.hexToBytes32(abi.decode(subjectParams[1], (string))); + return StringUtils.hexToBytes32(abi.decode(commandParams[1], (string))); } } diff --git a/src/handlers/SafeRecoverySubjectHandler.sol b/src/handlers/SafeRecoveryCommandHandler.sol similarity index 71% rename from src/handlers/SafeRecoverySubjectHandler.sol rename to src/handlers/SafeRecoveryCommandHandler.sol index 177b7495..52fc4456 100644 --- a/src/handlers/SafeRecoverySubjectHandler.sol +++ b/src/handlers/SafeRecoveryCommandHandler.sol @@ -1,32 +1,32 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { IEmailRecoverySubjectHandler } from "../interfaces/IEmailRecoverySubjectHandler.sol"; +import { IEmailRecoveryCommandHandler } from "../interfaces/IEmailRecoveryCommandHandler.sol"; import { ISafe } from "../interfaces/ISafe.sol"; /** - * @title SafeRecoverySubjectHandler - * @notice Handler contract that defines subject templates and how to validate them - * This is a custom subject handler that will work with Safes and defines custom validation. + * @title SafeRecoveryCommandHandler + * @notice Handler contract that defines command templates and how to validate them + * This is a custom command handler that will work with Safes and defines custom validation. */ -contract SafeRecoverySubjectHandler is IEmailRecoverySubjectHandler { +contract SafeRecoveryCommandHandler is IEmailRecoveryCommandHandler { /* * The function selector for rotating an owner on a Safe */ bytes4 public constant selector = bytes4(keccak256(bytes("swapOwner(address,address,address)"))); error InvalidTemplateIndex(uint256 templateIdx, uint256 expectedTemplateIdx); - error InvalidSubjectParams(uint256 paramsLength, uint256 expectedParamsLength); + error InvalidCommandParams(uint256 paramsLength, uint256 expectedParamsLength); error InvalidOldOwner(address oldOwner); error InvalidNewOwner(address newOwner); /** - * @notice Returns a hard-coded two-dimensional array of strings representing the subject + * @notice Returns a hard-coded two-dimensional array of strings representing the command * templates for an acceptance by a new guardian. * @return string[][] A two-dimensional array of strings, where each inner array represents a - * set of fixed strings and matchers for a subject template. + * set of fixed strings and matchers for a command template. */ - function acceptanceSubjectTemplates() public pure returns (string[][] memory) { + function acceptanceCommandTemplates() public pure returns (string[][] memory) { string[][] memory templates = new string[][](1); templates[0] = new string[](5); templates[0][0] = "Accept"; @@ -38,12 +38,12 @@ contract SafeRecoverySubjectHandler is IEmailRecoverySubjectHandler { } /** - * @notice Returns a hard-coded two-dimensional array of strings representing the subject + * @notice Returns a hard-coded two-dimensional array of strings representing the command * templates for email recovery. * @return string[][] A two-dimensional array of strings, where each inner array represents a - * set of fixed strings and matchers for a subject template. + * set of fixed strings and matchers for a command template. */ - function recoverySubjectTemplates() public pure returns (string[][] memory) { + function recoveryCommandTemplates() public pure returns (string[][] memory) { string[][] memory templates = new string[][](1); templates[0] = new string[](11); templates[0][0] = "Recover"; @@ -61,48 +61,49 @@ contract SafeRecoverySubjectHandler is IEmailRecoverySubjectHandler { } /** - * @notice Extracts the account address to be recovered from the subject parameters of an + * @notice Extracts the account address to be recovered from the command parameters of an * acceptance email. - * @param subjectParams The subject parameters of the acceptance email. + * @param commandParams The command parameters of the acceptance email. * @param {templateIdx} Unused parameter. The index of the template used for acceptance */ - function extractRecoveredAccountFromAcceptanceSubject( - bytes[] calldata subjectParams, + function extractRecoveredAccountFromAcceptanceCommand( + bytes[] calldata commandParams, uint256 /* templateIdx */ ) public pure returns (address) { - return abi.decode(subjectParams[0], (address)); + return abi.decode(commandParams[0], (address)); } /** - * @notice Extracts the account address to be recovered from the subject parameters of a + * @notice Extracts the account address to be recovered from the command parameters of a * recovery email. - * @param subjectParams The subject parameters of the recovery email. + * @param commandParams The command parameters of the recovery email. * @param {templateIdx} Unused parameter. The index of the template used for the recovery + * request */ - function extractRecoveredAccountFromRecoverySubject( - bytes[] calldata subjectParams, + function extractRecoveredAccountFromRecoveryCommand( + bytes[] calldata commandParams, uint256 /* templateIdx */ ) public pure returns (address) { - return abi.decode(subjectParams[0], (address)); + return abi.decode(commandParams[0], (address)); } /** - * @notice Validates the subject params for an acceptance email + * @notice Validates the command params for an acceptance email * @param templateIdx The index of the template used for acceptance - * @param subjectParams The subject parameters of the acceptance email + * @param commandParams The command parameters of the acceptance email * @return accountInEmail The account address in the acceptance email */ - function validateAcceptanceSubject( + function validateAcceptanceCommand( uint256 templateIdx, - bytes[] calldata subjectParams + bytes[] calldata commandParams ) external pure @@ -111,26 +112,26 @@ contract SafeRecoverySubjectHandler is IEmailRecoverySubjectHandler { if (templateIdx != 0) { revert InvalidTemplateIndex(templateIdx, 0); } - if (subjectParams.length != 1) { - revert InvalidSubjectParams(subjectParams.length, 1); + if (commandParams.length != 1) { + revert InvalidCommandParams(commandParams.length, 1); } // The GuardianStatus check in acceptGuardian implicitly // validates the account, so no need to re-validate here - address accountInEmail = abi.decode(subjectParams[0], (address)); + address accountInEmail = abi.decode(commandParams[0], (address)); return accountInEmail; } /** - * @notice Validates the subject params for an acceptance email + * @notice Validates the command params for an acceptance email * @param templateIdx The index of the template used for the recovery request - * @param subjectParams The subject parameters of the recovery email + * @param commandParams The command parameters of the recovery email * @return accountInEmail The account address in the recovery email */ - function validateRecoverySubject( + function validateRecoveryCommand( uint256 templateIdx, - bytes[] calldata subjectParams + bytes[] calldata commandParams ) public view @@ -139,13 +140,13 @@ contract SafeRecoverySubjectHandler is IEmailRecoverySubjectHandler { if (templateIdx != 0) { revert InvalidTemplateIndex(templateIdx, 0); } - if (subjectParams.length != 3) { - revert InvalidSubjectParams(subjectParams.length, 3); + if (commandParams.length != 3) { + revert InvalidCommandParams(commandParams.length, 3); } - address accountInEmail = abi.decode(subjectParams[0], (address)); - address oldOwnerInEmail = abi.decode(subjectParams[1], (address)); - address newOwnerInEmail = abi.decode(subjectParams[2], (address)); + address accountInEmail = abi.decode(commandParams[0], (address)); + address oldOwnerInEmail = abi.decode(commandParams[1], (address)); + address newOwnerInEmail = abi.decode(commandParams[2], (address)); bool isOldAddressOwner = ISafe(accountInEmail).isOwner(oldOwnerInEmail); if (!isOldAddressOwner) { @@ -161,18 +162,18 @@ contract SafeRecoverySubjectHandler is IEmailRecoverySubjectHandler { } /** - * @notice parses the recovery data hash from the subject params. The data hash is + * @notice parses the recovery data hash from the command params. The data hash is * verified against later when recovery is executed * @dev recoveryDataHash = keccak256(abi.encode(safeAccount, recoveryFunctionCalldata)). In the * context of recovery for a Safe, the first encoded value is the Safe account address. Normally, * this would be the validator address * @param templateIdx The index of the template used for the recovery request - * @param subjectParams The subject parameters of the recovery email + * @param commandParams The command parameters of the recovery email * @return recoveryDataHash The keccak256 hash of the recovery data */ function parseRecoveryDataHash( uint256 templateIdx, - bytes[] calldata subjectParams + bytes[] calldata commandParams ) external view @@ -182,9 +183,9 @@ contract SafeRecoverySubjectHandler is IEmailRecoverySubjectHandler { revert InvalidTemplateIndex(templateIdx, 0); } - address accountInEmail = abi.decode(subjectParams[0], (address)); - address oldOwnerInEmail = abi.decode(subjectParams[1], (address)); - address newOwnerInEmail = abi.decode(subjectParams[2], (address)); + address accountInEmail = abi.decode(commandParams[0], (address)); + address oldOwnerInEmail = abi.decode(commandParams[1], (address)); + address newOwnerInEmail = abi.decode(commandParams[2], (address)); address previousOwnerInLinkedList = getPreviousOwnerInLinkedList(accountInEmail, oldOwnerInEmail); diff --git a/src/interfaces/IEmailRecoverySubjectHandler.sol b/src/interfaces/IEmailRecoveryCommandHandler.sol similarity index 52% rename from src/interfaces/IEmailRecoverySubjectHandler.sol rename to src/interfaces/IEmailRecoveryCommandHandler.sol index cbfe7104..2622f911 100644 --- a/src/interfaces/IEmailRecoverySubjectHandler.sol +++ b/src/interfaces/IEmailRecoveryCommandHandler.sol @@ -1,37 +1,37 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -interface IEmailRecoverySubjectHandler { - function acceptanceSubjectTemplates() external pure returns (string[][] memory); - function recoverySubjectTemplates() external pure returns (string[][] memory); +interface IEmailRecoveryCommandHandler { + function acceptanceCommandTemplates() external pure returns (string[][] memory); + function recoveryCommandTemplates() external pure returns (string[][] memory); - function extractRecoveredAccountFromAcceptanceSubject( - bytes[] memory subjectParams, + function extractRecoveredAccountFromAcceptanceCommand( + bytes[] memory commandParams, uint256 templateIdx ) external view returns (address); - function extractRecoveredAccountFromRecoverySubject( - bytes[] memory subjectParams, + function extractRecoveredAccountFromRecoveryCommand( + bytes[] memory commandParams, uint256 templateIdx ) external view returns (address); - function validateAcceptanceSubject( + function validateAcceptanceCommand( uint256 templateIdx, - bytes[] memory subjectParams + bytes[] memory commandParams ) external view returns (address); - function validateRecoverySubject( + function validateRecoveryCommand( uint256 templateIdx, - bytes[] memory subjectParams + bytes[] memory commandParams ) external view @@ -39,7 +39,7 @@ interface IEmailRecoverySubjectHandler { function parseRecoveryDataHash( uint256 templateIdx, - bytes[] memory subjectParams + bytes[] memory commandParams ) external view diff --git a/src/interfaces/IEmailRecoveryManager.sol b/src/interfaces/IEmailRecoveryManager.sol index 00c0bff4..1da5cc26 100644 --- a/src/interfaces/IEmailRecoveryManager.sol +++ b/src/interfaces/IEmailRecoveryManager.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; +import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import { GuardianStatus } from "../libraries/EnumerableGuardianMap.sol"; interface IEmailRecoveryManager { @@ -14,17 +15,34 @@ interface IEmailRecoveryManager { */ struct RecoveryConfig { uint256 delay; // the time from when the threshold for a recovery request has passed (when - // the attempt is successful), until the recovery request can be executed - uint256 expiry; // the time from when recovery is started until the recovery request becomes - // invalid. The recovery expiry encourages the timely execution of successful recovery - // attempts, and reduces the risk of unauthorized access through stale or outdated - // requests. After the recovery expiry has passed, anyone can cancel the recovery - // request + // the attempt is successful), until the recovery request can be executed. The delay can + // be used to give the account owner time to react in case a malicious recovery + // attempt is started by a guardian + uint256 expiry; // the time from when a recovery request is started until the recovery + // request becomes invalid. The recovery expiry encourages the timely execution of + // successful recovery attempts, and reduces the risk of unauthorized access through + // stale or outdated requests. After the recovery expiry has passed, anyone can cancel + // the recovery request + } + + struct PreviousRecoveryRequest { + address previousGuardianInitiated; // the address of the guardian who initiated the previous + // recovery request. Used to prevent a malicious guardian threatening the liveness of + // the recovery attempt. For example, a guardian could initiate a recovery request with + // a recovery data hash for calldata that recovers the account to their own + // private key. Recording the previous guardian to initiate the request can be used + // in combination with a cooldown to stop the guardian blocking recovery with an + // invalid hash which is replaced by another invalid recovery hash after the request + // is cancelled + uint256 cancelRecoveryCooldown; // Used in conjunction with previousGuardianInitiated to + // stop a guardian blocking subsequent recovery requests with an invalid hash each time. + // Other guardians can react in time before the cooldown expires to start a valid + // recovery request with a valid hash } /** - * A struct representing the values required for a recovery request - * The request state should be maintained over a single recovery attempts unless + * A struct representing the values required for a recovery request. + * The request state should be maintained over a single recovery attempt unless * explicitly modified. It should be deleted after a recovery attempt has been processed */ struct RecoveryRequest { @@ -32,7 +50,9 @@ interface IEmailRecoveryManager { uint256 executeBefore; // the timestamp from which the recovery request becomes invalid uint256 currentWeight; // total weight of all guardian approvals for the recovery request bytes32 recoveryDataHash; // the keccak256 hash of the recovery data used to execute the - // recovery attempt. + // recovery attempt + EnumerableSet.AddressSet guardianVoted; // the set of guardians who have voted for the + // recovery request. Must be looped through manually to delete each value } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ @@ -44,7 +64,19 @@ interface IEmailRecoveryManager { ); event RecoveryConfigUpdated(address indexed account, uint256 delay, uint256 expiry); event GuardianAccepted(address indexed account, address indexed guardian); - event RecoveryProcessed( + event RecoveryRequestStarted( + address indexed account, + address indexed guardian, + uint256 executeBefore, + bytes32 recoveryDataHash + ); + event GuardianVoted( + address indexed account, + address indexed guardian, + uint256 currentWeight, + uint256 guardianWeight + ); + event RecoveryRequestComplete( address indexed account, address indexed guardian, uint256 executeAfter, @@ -62,15 +94,21 @@ interface IEmailRecoveryManager { error InvalidVerifier(); error InvalidDkimRegistry(); error InvalidEmailAuthImpl(); - error InvalidSubjectHandler(); + error InvalidCommandHandler(); + error InvalidKillSwitchAuthorizer(); + error InvalidFactory(); + error InvalidProxyBytecodeHash(); error SetupAlreadyCalled(); error AccountNotConfigured(); + error DelayLessThanMinimumDelay(uint256 delay, uint256 minimumDelay); error DelayMoreThanExpiry(uint256 delay, uint256 expiry); error RecoveryWindowTooShort(uint256 recoveryWindow); error ThresholdExceedsAcceptedWeight(uint256 threshold, uint256 acceptedWeight); error InvalidGuardianStatus( GuardianStatus guardianStatus, GuardianStatus expectedGuardianStatus ); + error GuardianAlreadyVoted(); + error GuardianMustWaitForCooldown(address guardian); error InvalidAccountAddress(); error NoRecoveryConfigured(); error NotEnoughApprovals(uint256 currentWeight, uint256 threshold); @@ -85,11 +123,32 @@ interface IEmailRecoveryManager { /* FUNCTIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + function killSwitchEnabled() external returns (bool); + function getRecoveryConfig(address account) external view returns (RecoveryConfig memory); - function getRecoveryRequest(address account) external view returns (RecoveryRequest memory); + function getRecoveryRequest(address account) + external + view + returns ( + uint256 executeAfter, + uint256 executeBefore, + uint256 currentWeight, + bytes32 recoveryDataHash + ); function updateRecoveryConfig(RecoveryConfig calldata recoveryConfig) external; + function getPreviousRecoveryRequest(address account) + external + view + returns (PreviousRecoveryRequest memory); + + function hasGuardianVoted(address account, address guardian) external view returns (bool); + function cancelRecovery() external; + + function cancelExpiredRecovery(address account) external; + + function toggleKillSwitch() external; } diff --git a/src/interfaces/IGuardianManager.sol b/src/interfaces/IGuardianManager.sol index dbdee7b8..25a02ae5 100644 --- a/src/interfaces/IGuardianManager.sol +++ b/src/interfaces/IGuardianManager.sol @@ -1,11 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { - EnumerableGuardianMap, - GuardianStorage, - GuardianStatus -} from "../libraries/EnumerableGuardianMap.sol"; +import { GuardianStorage, GuardianStatus } from "../libraries/EnumerableGuardianMap.sol"; interface IGuardianManager { /** @@ -32,6 +28,7 @@ interface IGuardianManager { event ChangedThreshold(address indexed account, uint256 threshold); error RecoveryInProcess(); + error KillSwitchEnabled(); error IncorrectNumberOfWeights(uint256 guardianCount, uint256 weightCount); error ThresholdCannotBeZero(); error InvalidGuardianAddress(address guardian); @@ -57,4 +54,6 @@ interface IGuardianManager { function removeGuardian(address guardian) external; function changeThreshold(uint256 threshold) external; + + function getAllGuardians(address account) external returns (address[] memory); } diff --git a/src/libraries/StringUtils.sol b/src/libraries/StringUtils.sol deleted file mode 100644 index 87799f33..00000000 --- a/src/libraries/StringUtils.sol +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.13; - -import { strings } from "solidity-stringutils/src/strings.sol"; - -// Extracted from https://github.com/zkemail/email-wallet-sdk/blob/main/src/helpers/StringUtils.sol -library StringUtils { - using strings for *; - - function hexToBytes32(string calldata hexStr) public pure returns (bytes32 result) { - require(hexStr.toSlice().startsWith("0x".toSlice()), "invalid hex prefix"); - hexStr = hexStr[2:]; - require(bytes(hexStr).length == 64, "invalid hex string length"); - uint256[] memory ints = hex2Ints(hexStr); - uint256 sum = 0; - for (uint256 i = 0; i < 32; i++) { - sum = (256 * sum + ints[i]); - } - return bytes32(sum); - } - - function hex2Ints(string memory hexStr) private pure returns (uint256[] memory) { - uint256[] memory result = new uint256[](bytes(hexStr).length / 2); - for (uint256 i = 0; i < result.length; i++) { - result[i] = - 16 * hexChar2Int(bytes(hexStr)[2 * i]) + hexChar2Int(bytes(hexStr)[2 * i + 1]); - } - return result; - } - - function hexChar2Int(bytes1 char) private pure returns (uint256) { - uint8 charInt = uint8(char); - if (charInt >= 48 && charInt <= 57) { - return charInt - 48; - } else if (charInt >= 97 && charInt <= 102) { - return charInt - 87; - } else { - require(false, "invalid hex char"); - } - return 0; - } -} diff --git a/src/modules/EmailRecoveryModule.sol b/src/modules/EmailRecoveryModule.sol index 9dbcbec9..075e0e31 100644 --- a/src/modules/EmailRecoveryModule.sol +++ b/src/modules/EmailRecoveryModule.sol @@ -43,11 +43,20 @@ contract EmailRecoveryModule is EmailRecoveryManager, ERC7579ExecutorBase, IEmai address verifier, address dkimRegistry, address emailAuthImpl, - address subjectHandler, + address commandHandler, + uint256 minimumDelay, + address killSwitchAuthorizer, address _validator, bytes4 _selector ) - EmailRecoveryManager(verifier, dkimRegistry, emailAuthImpl, subjectHandler) + EmailRecoveryManager( + verifier, + dkimRegistry, + emailAuthImpl, + commandHandler, + minimumDelay, + killSwitchAuthorizer + ) { if (_validator == address(0)) { revert InvalidValidator(_validator); @@ -78,10 +87,10 @@ contract EmailRecoveryModule is EmailRecoveryManager, ERC7579ExecutorBase, IEmai /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /** - * Initializes the module with the threshold and guardians + * @notice Initializes the module with the threshold, guardians and other configuration * @dev You cannot install this module during account deployment as it breaks the 4337 * validation rules. ERC7579 does not mandate that executors abide by the validation rules - * during account setup - if required, install this module after the account has been setup. The + * during account setup - if required, install this module after the account has been setup. The * data is encoded as follows: abi.encode(isInstalledContext, guardians, weights, threshold, * delay, expiry) * @param data encoded data for recovery configuration @@ -108,7 +117,7 @@ contract EmailRecoveryModule is EmailRecoveryManager, ERC7579ExecutorBase, IEmai } /** - * Handles the uninstallation of the module and clears the recovery configuration + * @notice Handles the uninstallation of the module and clears the recovery configuration * @param {data} Unused parameter. */ function onUninstall(bytes calldata /* data */ ) external { @@ -116,23 +125,24 @@ contract EmailRecoveryModule is EmailRecoveryManager, ERC7579ExecutorBase, IEmai } /** - * Check if the module is initialized + * @notice Check if the module is initialized * @param account The smart account to check - * @return true if the module is initialized, false otherwise + * @return bool True if the module is initialized, false otherwise */ function isInitialized(address account) external view returns (bool) { return getGuardianConfig(account).threshold != 0; } /** - * Check if a recovery request can be initiated based on guardian acceptance + * @notice Check if a recovery request can be initiated based on guardian acceptance * @param account The smart account to check - * @return true if the recovery request can be started, false otherwise + * @return bool True if the recovery request can be started, false otherwise */ function canStartRecoveryRequest(address account) external view returns (bool) { GuardianConfig memory guardianConfig = getGuardianConfig(account); - return guardianConfig.acceptedWeight >= guardianConfig.threshold; + return guardianConfig.threshold > 0 + && guardianConfig.acceptedWeight >= guardianConfig.threshold; } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ @@ -168,25 +178,25 @@ contract EmailRecoveryModule is EmailRecoveryManager, ERC7579ExecutorBase, IEmai /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /** - * Returns the name of the module - * @return name of the module + * @notice Returns the name of the module + * @return string name of the module */ function name() external pure returns (string memory) { return "ZKEmail.EmailRecoveryModule"; } /** - * Returns the version of the module - * @return version of the module + * @notice Returns the version of the module + * @return string version of the module */ function version() external pure returns (string memory) { return "1.0.0"; } /** - * Returns the type of the module + * @notice Returns the type of the module * @param typeID type of the module - * @return true if the type is a module type, false otherwise + * @return bool true if the type is a module type, false otherwise */ function isModuleType(uint256 typeID) external pure returns (bool) { return typeID == TYPE_EXECUTOR; diff --git a/src/modules/SafeEmailRecoveryModule.sol b/src/modules/SafeEmailRecoveryModule.sol index da14a01d..c868d49a 100644 --- a/src/modules/SafeEmailRecoveryModule.sol +++ b/src/modules/SafeEmailRecoveryModule.sol @@ -30,13 +30,22 @@ contract SafeEmailRecoveryModule is EmailRecoveryManager { address verifier, address dkimRegistry, address emailAuthImpl, - address subjectHandler + address commandHandler, + uint256 minimumDelay, + address killSwitchAuthorizer ) - EmailRecoveryManager(verifier, dkimRegistry, emailAuthImpl, subjectHandler) + EmailRecoveryManager( + verifier, + dkimRegistry, + emailAuthImpl, + commandHandler, + minimumDelay, + killSwitchAuthorizer + ) { } /** - * @notice Configures recovery for the caller's account + * @notice Initializes the module with the threshold, guardians and other configuration * @dev This function ensures that the module is installed before configuring recovery. Calls * internal configureRecovery function * @param guardians An array of guardian addresses @@ -61,14 +70,15 @@ contract SafeEmailRecoveryModule is EmailRecoveryManager { } /** - * Check if a recovery request can be initiated based on guardian acceptance + * @notice Check if a recovery request can be initiated based on guardian acceptance * @param account The smart account to check * @return true if the recovery request can be started, false otherwise */ function canStartRecoveryRequest(address account) external view returns (bool) { GuardianConfig memory guardianConfig = getGuardianConfig(account); - return guardianConfig.acceptedWeight >= guardianConfig.threshold; + return guardianConfig.threshold > 0 + && guardianConfig.acceptedWeight >= guardianConfig.threshold; } /** diff --git a/src/modules/UniversalEmailRecoveryModule.sol b/src/modules/UniversalEmailRecoveryModule.sol index b13fa588..d4c11e57 100644 --- a/src/modules/UniversalEmailRecoveryModule.sol +++ b/src/modules/UniversalEmailRecoveryModule.sol @@ -65,8 +65,9 @@ contract UniversalEmailRecoveryModule is allowedSelectors; /** - * @notice Modifier to check whether the selector is safe. Reverts if the selector is for - * "onInstall" or "onUninstall" + * @notice Modifier to check whether the selector is safe + * @dev Reverts if the selector is for "onInstall" or "onUninstall", or if the selector is not + * on a whitelist if the validator is equal to msg.sender */ modifier withoutUnsafeSelector(address validator, bytes4 selector) { if (validator == msg.sender) { @@ -102,9 +103,18 @@ contract UniversalEmailRecoveryModule is address verifier, address dkimRegistry, address emailAuthImpl, - address subjectHandler + address commandHandler, + uint256 minimumDelay, + address killSwitchAuthorizer ) - EmailRecoveryManager(verifier, dkimRegistry, emailAuthImpl, subjectHandler) + EmailRecoveryManager( + verifier, + dkimRegistry, + emailAuthImpl, + commandHandler, + minimumDelay, + killSwitchAuthorizer + ) { } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ @@ -112,7 +122,7 @@ contract UniversalEmailRecoveryModule is /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /** - * Initializes the module with the threshold and guardians + * @notice Initializes the module with the threshold, guardians and other configuration * @dev You cannot install this module during account deployment as it breaks the 4337 * validation rules. ERC7579 does not mandate that executors abide by the validation rules * during account setup - if required, install this module after the account has been setup. The @@ -212,7 +222,7 @@ contract UniversalEmailRecoveryModule is } /** - * Handles the uninstallation of the module and clears the recovery configuration + * @notice Handles the uninstallation of the module and clears the recovery configuration * @param {data} Unused parameter. */ function onUninstall(bytes calldata /* data */ ) external { @@ -229,19 +239,19 @@ contract UniversalEmailRecoveryModule is } /** - * Check if the module is initialized + * @notice Check if the module is initialized * @param account The smart account to check - * @return true if the module is initialized, false otherwise + * @return bool True if the module is initialized, false otherwise */ function isInitialized(address account) public view returns (bool) { return getGuardianConfig(account).threshold != 0; } /** - * Check if a recovery request can be initiated based on guardian acceptance + * @notice Check if a recovery request can be initiated based on guardian acceptance * @param account The smart account to check * @param validator The validator to check - * @return true if the recovery request can be started, false otherwise + * @return bool True if the recovery request can be started, false otherwise */ function canStartRecoveryRequest( address account, @@ -253,7 +263,8 @@ contract UniversalEmailRecoveryModule is { GuardianConfig memory guardianConfig = getGuardianConfig(account); - return guardianConfig.acceptedWeight >= guardianConfig.threshold + return guardianConfig.threshold > 0 + && guardianConfig.acceptedWeight >= guardianConfig.threshold && validators[account].contains(validator); } @@ -327,25 +338,25 @@ contract UniversalEmailRecoveryModule is /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /** - * Returns the name of the module - * @return name of the module + * @notice Returns the name of the module + * @return string name of the module */ function name() external pure returns (string memory) { return "ZKEmail.UniversalEmailRecoveryModule"; } /** - * Returns the version of the module - * @return version of the module + * @notice Returns the version of the module + * @return string version of the module */ function version() external pure returns (string memory) { return "1.0.0"; } /** - * Returns the type of the module + * @notice Returns the type of the module * @param typeID type of the module - * @return true if the type is a module type, false otherwise + * @return bool true if the type is a module type, false otherwise */ function isModuleType(uint256 typeID) external pure returns (bool) { return typeID == TYPE_EXECUTOR; diff --git a/src/test/MockGroth16Verifier.sol b/src/test/MockGroth16Verifier.sol index 64268364..93b2d98f 100644 --- a/src/test/MockGroth16Verifier.sol +++ b/src/test/MockGroth16Verifier.sol @@ -1,21 +1,24 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.25; -struct EmailProof { - string domainName; // Domain name of the sender's email - bytes32 publicKeyHash; // Hash of the DKIM public key used in email/proof - uint256 timestamp; // Timestamp of the email - string maskedSubject; // Masked subject of the email - bytes32 emailNullifier; // Nullifier of the email to prevent its reuse. - bytes32 accountSalt; // Create2 salt of the account - bool isCodeExist; // Check if the account code is exist - bytes proof; // ZK Proof of Email -} +import { + IVerifier, + EmailProof +} from "@zk-email/ether-email-auth-contracts/src/interfaces/IVerifier.sol"; /** * @notice Mock snarkjs Groth16 Solidity verifier */ -contract MockGroth16Verifier { +contract MockGroth16Verifier is IVerifier { + uint256 public constant DOMAIN_FIELDS = 9; + uint256 public constant DOMAIN_BYTES = 255; + uint256 public constant COMMAND_FIELDS = 20; + uint256 public constant COMMAND_BYTES = 605; + + function commandBytes() external pure returns (uint256) { + return COMMAND_BYTES; + } + function verifyEmailProof(EmailProof memory proof) public pure returns (bool) { proof; diff --git a/src/test/OwnableValidator.sol b/src/test/OwnableValidator.sol index eb5933e5..8f78521a 100644 --- a/src/test/OwnableValidator.sol +++ b/src/test/OwnableValidator.sol @@ -7,6 +7,9 @@ import { PackedUserOperation } from "modulekit/external/ERC4337.sol"; import { SignatureCheckerLib } from "solady/utils/SignatureCheckerLib.sol"; import { ECDSA } from "solady/utils/ECDSA.sol"; +/** + * @notice NOT FOR USE IN PRODUCTION + */ contract OwnableValidator is ERC7579ValidatorBase { using SignatureCheckerLib for address; @@ -32,7 +35,6 @@ contract OwnableValidator is ERC7579ValidatorBase { */ function onUninstall(bytes calldata) external override { delete owners[msg.sender]; - // delete authorized[msg.sender][authorizedAccount]; } function isInitialized(address smartAccount) external view returns (bool) { @@ -74,6 +76,19 @@ contract OwnableValidator is ERC7579ValidatorBase { : EIP1271_FAILED; } + function validateSignatureWithData( + bytes32 hash, + bytes calldata signature, + bytes calldata data + ) + external + view + override + returns (bool) + { + return false; + } + function changeOwner(address newOwner) external { owners[msg.sender] = newOwner; } diff --git a/test/Base.t.sol b/test/Base.t.sol new file mode 100644 index 00000000..e70483e1 --- /dev/null +++ b/test/Base.t.sol @@ -0,0 +1,528 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { Test } from "forge-std/Test.sol"; +import { RhinestoneModuleKit, AccountInstance } from "modulekit/ModuleKit.sol"; +import { + EmailAuth, + EmailAuthMsg, + EmailProof +} from "@zk-email/ether-email-auth-contracts/src/EmailAuth.sol"; +import { CommandUtils } from "@zk-email/ether-email-auth-contracts/src/libraries/CommandUtils.sol"; +import { UserOverrideableDKIMRegistry } from "@zk-email/contracts/UserOverrideableDKIMRegistry.sol"; +import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; + +import { MockGroth16Verifier } from "src/test/MockGroth16Verifier.sol"; +import { OwnableValidator } from "src/test/OwnableValidator.sol"; +import { EmailRecoveryCommandHandler } from "src/handlers/EmailRecoveryCommandHandler.sol"; +import { AccountHidingRecoveryCommandHandler } from + "src/handlers/AccountHidingRecoveryCommandHandler.sol"; +import { SafeRecoveryCommandHandler } from "src/handlers/SafeRecoveryCommandHandler.sol"; + +/* solhint-disable gas-custom-errors, custom-errors, reason-string, max-states-count */ + +interface IEmailRecoveryModule { + function computeAcceptanceTemplateId(uint256 templateIdx) external pure returns (uint256); + + function computeRecoveryTemplateId(uint256 templateIdx) external pure returns (uint256); + + function handleAcceptance(EmailAuthMsg memory emailAuthMsg, uint256 templateIdx) external; + + function handleRecovery(EmailAuthMsg memory emailAuthMsg, uint256 templateIdx) external; + + function completeRecovery(address account, bytes memory completeCalldata) external; +} + +enum CommandHandlerType { + EmailRecoveryCommandHandler, + AccountHidingRecoveryCommandHandler, + SafeRecoveryCommandHandler +} + +abstract contract BaseTest is RhinestoneModuleKit, Test { + using Strings for uint256; + + // ZK Email contracts and variables + address public zkEmailDeployer; + UserOverrideableDKIMRegistry public dkimRegistry; + MockGroth16Verifier public verifier; + EmailAuth public emailAuthImpl; + + OwnableValidator public validator; + address public validatorAddress; + + // public account and owners + address public owner1; + address public owner2; + address public owner3; + address public newOwner1; + address public newOwner2; + address public newOwner3; + AccountInstance public instance1; + AccountInstance public instance2; + AccountInstance public instance3; + address public accountAddress1; + address public accountAddress2; + address public accountAddress3; + address public killSwitchAuthorizer; + + // public Account salts + bytes32 public accountSalt1; + bytes32 public accountSalt2; + bytes32 public accountSalt3; + + // public recovery config + address[] public guardians1; + address[] public guardians2; + address[] public guardians3; + uint256[] public guardianWeights; + uint256 public totalWeight; + uint256 public delay; + uint256 public expiry; + uint256 public threshold; + uint256 public templateIdx; + bytes public isInstalledContext; + + string public selector = "12345"; + string public domainName = "gmail.com"; + bytes32 public publicKeyHash = + 0x0ea9c777dc7110e5a9e89b13f0cfc540e3845ba120b2b6dc24024d61488d4788; + uint256 public minimumDelay = 12 hours; + + bytes4 public functionSelector; + bytes public recoveryCalldata; + bytes public recoveryData; + bytes32 public recoveryDataHash; + + uint256 public nullifierCount; + + function setUp() public virtual { + init(); + + // create owners + owner1 = vm.createWallet("owner1").addr; + owner2 = vm.createWallet("owner2").addr; + owner3 = vm.createWallet("owner3").addr; + newOwner1 = vm.createWallet("newOwner1").addr; + newOwner2 = vm.createWallet("newOwner2").addr; + newOwner3 = vm.createWallet("newOwner3").addr; + + // Deploy and fund the accounts + instance1 = makeAccountInstance("account1"); + instance2 = makeAccountInstance("account2"); + instance3 = makeAccountInstance("account3"); + accountAddress1 = instance1.account; + accountAddress2 = instance2.account; + accountAddress3 = instance3.account; + vm.deal(address(instance1.account), 10 ether); + vm.deal(address(instance2.account), 10 ether); + vm.deal(address(instance3.account), 10 ether); + + accountSalt1 = keccak256(abi.encode("account salt 1")); + accountSalt2 = keccak256(abi.encode("account salt 2")); + accountSalt3 = keccak256(abi.encode("account salt 3")); + + zkEmailDeployer = vm.addr(1); + killSwitchAuthorizer = vm.addr(2); + + vm.startPrank(zkEmailDeployer); + uint256 setTimeDelay = 0; + UserOverrideableDKIMRegistry overrideableDkimImpl = new UserOverrideableDKIMRegistry(); + ERC1967Proxy dkimProxy = new ERC1967Proxy( + address(overrideableDkimImpl), + abi.encodeCall( + overrideableDkimImpl.initialize, (zkEmailDeployer, zkEmailDeployer, setTimeDelay) + ) + ); + dkimRegistry = UserOverrideableDKIMRegistry(address(dkimProxy)); + + dkimRegistry.setDKIMPublicKeyHash(domainName, publicKeyHash, zkEmailDeployer, new bytes(0)); + + verifier = new MockGroth16Verifier(); + emailAuthImpl = new EmailAuth(); + vm.stopPrank(); + + // Deploy validator to be recovered + validator = new OwnableValidator(); + validatorAddress = address(validator); + + bytes memory handlerBytecode = getHandlerBytecode(); + setRecoveryData(); + deployModule(handlerBytecode); + + // Compute guardian addresses + guardians1 = new address[](3); + guardians1[0] = computeEmailAuthAddress(instance1.account, accountSalt1); + guardians1[1] = computeEmailAuthAddress(instance1.account, accountSalt2); + guardians1[2] = computeEmailAuthAddress(instance1.account, accountSalt3); + guardians2 = new address[](3); + guardians2[0] = computeEmailAuthAddress(instance2.account, accountSalt1); + guardians2[1] = computeEmailAuthAddress(instance2.account, accountSalt2); + guardians2[2] = computeEmailAuthAddress(instance2.account, accountSalt3); + guardians3 = new address[](3); + guardians3[0] = computeEmailAuthAddress(instance3.account, accountSalt1); + guardians3[1] = computeEmailAuthAddress(instance3.account, accountSalt2); + guardians3[2] = computeEmailAuthAddress(instance3.account, accountSalt3); + + // Set recovery config variables + guardianWeights = new uint256[](3); + guardianWeights[0] = 1; + guardianWeights[1] = 2; + guardianWeights[2] = 1; + totalWeight = 4; + delay = 1 days; + expiry = 2 weeks; + threshold = 3; + templateIdx = 0; + isInstalledContext = bytes("0"); + } + + /** + * Return if current account type is safe or not + */ + function isAccountTypeSafe() public view returns (bool) { + string memory currentAccountType = vm.envOr("ACCOUNT_TYPE", string("")); + if (Strings.equal(currentAccountType, "SAFE")) { + return true; + } else { + return false; + } + } + + /** + * Skip the test if the account type is not safe + */ + function skipIfNotSafeAccountType() public { + if (isAccountTypeSafe()) { + vm.skip(false); + } else { + vm.skip(true); + } + } + + /** + * Returns the commmand handler type + */ + function getCommandHandlerType() public view returns (CommandHandlerType) { + return CommandHandlerType(vm.envOr("COMMAND_HANDLER_TYPE", uint256(0))); + } + + /** + * Return the command handler bytecode based on the command handler type + */ + function getHandlerBytecode() public view returns (bytes memory) { + CommandHandlerType commandHandlerType = getCommandHandlerType(); + + if (commandHandlerType == CommandHandlerType.EmailRecoveryCommandHandler) { + return type(EmailRecoveryCommandHandler).creationCode; + } + if (commandHandlerType == CommandHandlerType.AccountHidingRecoveryCommandHandler) { + return type(AccountHidingRecoveryCommandHandler).creationCode; + } + if (commandHandlerType == CommandHandlerType.SafeRecoveryCommandHandler) { + return type(SafeRecoveryCommandHandler).creationCode; + } + + revert("Invalid command handler type"); + } + + /** + * Skip the test if command handler type is not the expected type + */ + function skipIfNotCommandHandlerType(CommandHandlerType commandHandlerType) public { + if (getCommandHandlerType() == commandHandlerType) { + vm.skip(false); + } else { + vm.skip(true); + } + } + + /** + * Skip the test if command handler type is the expected type + */ + function skipIfCommandHandlerType(CommandHandlerType commandHandlerType) public { + if (getCommandHandlerType() == commandHandlerType) { + vm.skip(true); + } else { + vm.skip(false); + } + } + + function setRecoveryData() public virtual; + + function deployModule(bytes memory handlerBytecode) public virtual; + + function computeEmailAuthAddress( + address account, + bytes32 accountSalt + ) + public + view + virtual + returns (address); + + function generateMockEmailProof( + string memory command, + bytes32 nullifier, + bytes32 accountSalt + ) + public + view + returns (EmailProof memory) + { + EmailProof memory emailProof; + emailProof.domainName = "gmail.com"; + emailProof.publicKeyHash = bytes32( + vm.parseUint( + "6632353713085157925504008443078919716322386156160602218536961028046468237192" + ) + ); + emailProof.timestamp = block.timestamp; + emailProof.maskedCommand = command; + emailProof.emailNullifier = nullifier; + emailProof.accountSalt = accountSalt; + emailProof.isCodeExist = true; + emailProof.proof = bytes("0"); + + return emailProof; + } + + function acceptGuardian( + address account, + address guardian, + address emailRecoveryModule + ) + public + { + EmailAuthMsg memory emailAuthMsg = + getAcceptanceEmailAuthMessage(account, guardian, emailRecoveryModule); + IEmailRecoveryModule(emailRecoveryModule).handleAcceptance(emailAuthMsg, templateIdx); + } + + // WithAccountSalt variation - used for creating incorrect recovery setups + function acceptGuardianWithAccountSalt( + address account, + address guardian, + address emailRecoveryModule, + bytes32 optionalAccountSalt + ) + public + { + EmailAuthMsg memory emailAuthMsg = getAcceptanceEmailAuthMessageWithAccountSalt( + account, guardian, emailRecoveryModule, optionalAccountSalt + ); + IEmailRecoveryModule(emailRecoveryModule).handleAcceptance(emailAuthMsg, templateIdx); + } + + function getAcceptanceEmailAuthMessage( + address account, + address guardian, + address emailRecoveryModule + ) + public + returns (EmailAuthMsg memory) + { + return getAcceptanceEmailAuthMessageWithAccountSalt( + account, guardian, emailRecoveryModule, bytes32(0) + ); + } + + // WithAccountSalt variation - used for creating incorrect recovery setups + function getAcceptanceEmailAuthMessageWithAccountSalt( + address account, + address guardian, + address emailRecoveryModule, + bytes32 optionalAccountSalt + ) + public + virtual + returns (EmailAuthMsg memory) + { + string memory command; + bytes[] memory commandParamsForAcceptance = new bytes[](1); + if (getCommandHandlerType() == CommandHandlerType.AccountHidingRecoveryCommandHandler) { + bytes32 accountHash = keccak256(abi.encodePacked(account)); + string memory accountHashString = uint256(accountHash).toHexString(32); + command = string.concat("Accept guardian request for ", accountHashString); + commandParamsForAcceptance[0] = abi.encode(accountHashString); + } else { + string memory accountString = CommandUtils.addressToChecksumHexString(account); + command = string.concat("Accept guardian request for ", accountString); + commandParamsForAcceptance[0] = abi.encode(account); + } + + bytes32 nullifier = generateNewNullifier(); + + bytes32 accountSalt; + if (optionalAccountSalt == bytes32(0)) { + accountSalt = getAccountSaltForGuardian(account, guardian); + } else { + accountSalt = optionalAccountSalt; + } + + EmailProof memory emailProof = generateMockEmailProof(command, nullifier, accountSalt); + return EmailAuthMsg({ + templateId: IEmailRecoveryModule(emailRecoveryModule).computeAcceptanceTemplateId( + templateIdx + ), + commandParams: commandParamsForAcceptance, + skippedCommandPrefix: 0, + proof: emailProof + }); + } + + function handleRecovery( + address account, + address guardian, + bytes32 _recoveryDataHash, + address emailRecoveryModule + ) + public + { + EmailAuthMsg memory emailAuthMsg = + getRecoveryEmailAuthMessage(account, guardian, _recoveryDataHash, emailRecoveryModule); + IEmailRecoveryModule(emailRecoveryModule).handleRecovery(emailAuthMsg, templateIdx); + } + + // WithAccountSalt variation - used for creating incorrect recovery setups + function handleRecoveryWithAccountSalt( + address account, + address guardian, + bytes32 _recoveryDataHash, + address emailRecoveryModule, + bytes32 optionalAccountSalt + ) + public + { + EmailAuthMsg memory emailAuthMsg = getRecoveryEmailAuthMessageWithAccountSalt( + account, guardian, _recoveryDataHash, emailRecoveryModule, optionalAccountSalt + ); + IEmailRecoveryModule(emailRecoveryModule).handleRecovery(emailAuthMsg, templateIdx); + } + + function getRecoveryEmailAuthMessage( + address account, + address guardian, + bytes32 _recoveryDataHash, + address emailRecoveryModule + ) + public + returns (EmailAuthMsg memory) + { + return getRecoveryEmailAuthMessageWithAccountSalt( + account, guardian, _recoveryDataHash, emailRecoveryModule, bytes32(0) + ); + } + + // WithAccountSalt variation - used for creating incorrect recovery setups + function getRecoveryEmailAuthMessageWithAccountSalt( + address account, + address guardian, + bytes32 _recoveryDataHash, + address emailRecoveryModule, + bytes32 optionalAccountSalt + ) + public + returns (EmailAuthMsg memory) + { + string memory command; + bytes[] memory commandParamsForRecovery = new bytes[](2); + + if (getCommandHandlerType() == CommandHandlerType.AccountHidingRecoveryCommandHandler) { + bytes32 accountHash = keccak256(abi.encodePacked(account)); + string memory accountHashString = uint256(accountHash).toHexString(32); + string memory recoveryDataHashString = uint256(_recoveryDataHash).toHexString(32); + string memory commandPart1 = string.concat("Recover account ", accountHashString); + string memory commandPart2 = + string.concat(" using recovery hash ", recoveryDataHashString); + command = string.concat(commandPart1, commandPart2); + + commandParamsForRecovery = new bytes[](2); + commandParamsForRecovery[0] = abi.encode(accountHashString); + commandParamsForRecovery[1] = abi.encode(recoveryDataHashString); + } + if (getCommandHandlerType() == CommandHandlerType.EmailRecoveryCommandHandler) { + string memory accountString = CommandUtils.addressToChecksumHexString(account); + string memory recoveryDataHashString = uint256(_recoveryDataHash).toHexString(32); + string memory commandPart1 = string.concat("Recover account ", accountString); + string memory commandPart2 = + string.concat(" using recovery hash ", recoveryDataHashString); + command = string.concat(commandPart1, commandPart2); + + commandParamsForRecovery = new bytes[](2); + commandParamsForRecovery[0] = abi.encode(account); + commandParamsForRecovery[1] = abi.encode(recoveryDataHashString); + } + if (getCommandHandlerType() == CommandHandlerType.SafeRecoveryCommandHandler) { + string memory accountString = CommandUtils.addressToChecksumHexString(account); + string memory oldOwnerString = CommandUtils.addressToChecksumHexString(owner1); + string memory newOwnerString = CommandUtils.addressToChecksumHexString(newOwner1); + command = string.concat( + "Recover account ", + accountString, + " from old owner ", + oldOwnerString, + " to new owner ", + newOwnerString + ); + + commandParamsForRecovery = new bytes[](3); + commandParamsForRecovery[0] = abi.encode(accountAddress1); + commandParamsForRecovery[1] = abi.encode(owner1); + commandParamsForRecovery[2] = abi.encode(newOwner1); + } + + bytes32 nullifier = generateNewNullifier(); + + bytes32 accountSalt; + if (optionalAccountSalt == bytes32(0)) { + accountSalt = getAccountSaltForGuardian(account, guardian); + } else { + accountSalt = optionalAccountSalt; + } + + EmailProof memory emailProof = generateMockEmailProof(command, nullifier, accountSalt); + return EmailAuthMsg({ + templateId: IEmailRecoveryModule(emailRecoveryModule).computeRecoveryTemplateId(templateIdx), + commandParams: commandParamsForRecovery, + skippedCommandPrefix: 0, + proof: emailProof + }); + } + + function getAccountSaltForGuardian( + address account, + address guardian + ) + public + view + returns (bytes32) + { + address[] memory guardians; + if (account == instance1.account) { + guardians = guardians1; + } else if (account == instance2.account) { + guardians = guardians2; + } else if (account == instance3.account) { + guardians = guardians3; + } else { + revert("getAccountSaltForGuardian - Invalid account address"); + } + if (guardian == guardians[0]) { + return accountSalt1; + } + if (guardian == guardians[1]) { + return accountSalt2; + } + if (guardian == guardians[2]) { + return accountSalt3; + } + + revert("getAccountSaltForGuardian - Invalid guardian address"); + } + + function generateNewNullifier() public returns (bytes32) { + return keccak256(abi.encode(nullifierCount++)); + } +} diff --git a/test/integration/EmailRecoveryManager/EmailRecoveryManager.integration.t.sol b/test/integration/EmailRecoveryManager/EmailRecoveryManager.integration.t.sol index 31d02d3c..6d269633 100644 --- a/test/integration/EmailRecoveryManager/EmailRecoveryManager.integration.t.sol +++ b/test/integration/EmailRecoveryManager/EmailRecoveryManager.integration.t.sol @@ -1,16 +1,16 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; import { ModuleKitHelpers, ModuleKitUserOp } from "modulekit/ModuleKit.sol"; -import { MODULE_TYPE_EXECUTOR, MODULE_TYPE_VALIDATOR } from "modulekit/external/ERC7579.sol"; -import { EmailAuthMsg } from "ether-email-auth/packages/contracts/src/EmailAuth.sol"; +import { MODULE_TYPE_EXECUTOR } from "modulekit/external/ERC7579.sol"; +import { EmailAuthMsg } from "@zk-email/ether-email-auth-contracts/src/EmailAuth.sol"; +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; import { IEmailRecoveryManager } from "src/interfaces/IEmailRecoveryManager.sol"; import { IGuardianManager } from "src/interfaces/IGuardianManager.sol"; import { GuardianStorage, GuardianStatus } from "src/libraries/EnumerableGuardianMap.sol"; -import { OwnableValidator } from "src/test/OwnableValidator.sol"; +import { CommandHandlerType } from "../../Base.t.sol"; import { OwnableValidatorRecovery_UniversalEmailRecoveryModule_Base } from "../OwnableValidatorRecovery/UniversalEmailRecoveryModule/UniversalEmailRecoveryModuleBase.t.sol"; @@ -25,35 +25,44 @@ contract EmailRecoveryManager_Integration_Test is } function test_RevertWhen_HandleAcceptanceCalled_BeforeConfigureRecovery() public { - vm.prank(accountAddress1); - instance1.uninstallModule(MODULE_TYPE_EXECUTOR, recoveryModuleAddress, ""); - vm.stopPrank(); + skipIfCommandHandlerType(CommandHandlerType.SafeRecoveryCommandHandler); + + instance1.uninstallModule(MODULE_TYPE_EXECUTOR, emailRecoveryModuleAddress, ""); - EmailAuthMsg memory emailAuthMsg = - getAcceptanceEmailAuthMessage(accountAddress1, guardians1[0]); + EmailAuthMsg memory emailAuthMsg = getAcceptanceEmailAuthMessage( + accountAddress1, guardians1[0], emailRecoveryModuleAddress + ); vm.expectRevert(IEmailRecoveryManager.RecoveryIsNotActivated.selector); emailRecoveryModule.handleAcceptance(emailAuthMsg, templateIdx); } function test_RevertWhen_HandleRecoveryCalled_BeforeTimeStampChanged() public { - acceptGuardian(accountAddress1, guardians1[0]); + skipIfCommandHandlerType(CommandHandlerType.SafeRecoveryCommandHandler); - EmailAuthMsg memory emailAuthMsg = - getRecoveryEmailAuthMessage(accountAddress1, guardians1[0], recoveryDataHash1); + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + + EmailAuthMsg memory emailAuthMsg = getRecoveryEmailAuthMessage( + accountAddress1, guardians1[0], recoveryDataHash1, emailRecoveryModuleAddress + ); vm.expectRevert("invalid timestamp"); emailRecoveryModule.handleRecovery(emailAuthMsg, templateIdx); } function test_RevertWhen_HandleAcceptanceCalled_DuringRecovery() public { - acceptGuardian(accountAddress1, guardians1[0]); - acceptGuardian(accountAddress1, guardians1[1]); + skipIfCommandHandlerType(CommandHandlerType.SafeRecoveryCommandHandler); + + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); vm.warp(12 seconds); - handleRecovery(accountAddress1, guardians1[0], recoveryDataHash1); + handleRecovery( + accountAddress1, guardians1[0], recoveryDataHash1, emailRecoveryModuleAddress + ); - EmailAuthMsg memory emailAuthMsg = - getAcceptanceEmailAuthMessage(accountAddress1, guardians1[1]); + EmailAuthMsg memory emailAuthMsg = getAcceptanceEmailAuthMessage( + accountAddress1, guardians1[1], emailRecoveryModuleAddress + ); vm.expectRevert(IGuardianManager.RecoveryInProcess.selector); emailRecoveryModule.handleAcceptance(emailAuthMsg, templateIdx); @@ -63,29 +72,42 @@ contract EmailRecoveryManager_Integration_Test is ) public { - acceptGuardian(accountAddress1, guardians1[0]); - acceptGuardian(accountAddress1, guardians1[1]); + skipIfCommandHandlerType(CommandHandlerType.SafeRecoveryCommandHandler); + + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); vm.warp(12 seconds); - handleRecovery(accountAddress1, guardians1[0], recoveryDataHash1); - handleRecovery(accountAddress1, guardians1[1], recoveryDataHash1); + handleRecovery( + accountAddress1, guardians1[0], recoveryDataHash1, emailRecoveryModuleAddress + ); + handleRecovery( + accountAddress1, guardians1[1], recoveryDataHash1, emailRecoveryModuleAddress + ); - EmailAuthMsg memory emailAuthMsg = - getAcceptanceEmailAuthMessage(accountAddress1, guardians1[2]); + EmailAuthMsg memory emailAuthMsg = getAcceptanceEmailAuthMessage( + accountAddress1, guardians1[2], emailRecoveryModuleAddress + ); vm.expectRevert(IGuardianManager.RecoveryInProcess.selector); emailRecoveryModule.handleAcceptance(emailAuthMsg, templateIdx); } function test_HandleNewAcceptanceSucceeds_AfterCompleteRecovery() public { - acceptGuardian(accountAddress1, guardians1[0]); - acceptGuardian(accountAddress1, guardians1[1]); + skipIfCommandHandlerType(CommandHandlerType.SafeRecoveryCommandHandler); + + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); vm.warp(12 seconds); - handleRecovery(accountAddress1, guardians1[0], recoveryDataHash1); - handleRecovery(accountAddress1, guardians1[1], recoveryDataHash1); + handleRecovery( + accountAddress1, guardians1[0], recoveryDataHash1, emailRecoveryModuleAddress + ); + handleRecovery( + accountAddress1, guardians1[1], recoveryDataHash1, emailRecoveryModuleAddress + ); vm.warp(block.timestamp + delay); emailRecoveryModule.completeRecovery(accountAddress1, recoveryData1); - acceptGuardian(accountAddress1, guardians1[2]); + acceptGuardian(accountAddress1, guardians1[2], emailRecoveryModuleAddress); GuardianStorage memory guardianStorage = emailRecoveryModule.getGuardian(accountAddress1, guardians1[2]); @@ -94,20 +116,24 @@ contract EmailRecoveryManager_Integration_Test is } function test_RevertWhen_HandleRecoveryCalled_BeforeConfigureRecovery() public { - vm.prank(accountAddress1); - instance1.uninstallModule(MODULE_TYPE_EXECUTOR, recoveryModuleAddress, ""); - vm.stopPrank(); + skipIfCommandHandlerType(CommandHandlerType.SafeRecoveryCommandHandler); + + instance1.uninstallModule(MODULE_TYPE_EXECUTOR, emailRecoveryModuleAddress, ""); - EmailAuthMsg memory emailAuthMsg = - getRecoveryEmailAuthMessage(accountAddress1, guardians1[0], recoveryDataHash1); + EmailAuthMsg memory emailAuthMsg = getRecoveryEmailAuthMessage( + accountAddress1, guardians1[0], recoveryDataHash1, emailRecoveryModuleAddress + ); vm.expectRevert(); emailRecoveryModule.handleRecovery(emailAuthMsg, templateIdx); } function test_RevertWhen_HandleRecoveryCalled_BeforeHandleAcceptance() public { - EmailAuthMsg memory emailAuthMsg = - getRecoveryEmailAuthMessage(accountAddress1, guardians1[0], recoveryDataHash1); + skipIfCommandHandlerType(CommandHandlerType.SafeRecoveryCommandHandler); + + EmailAuthMsg memory emailAuthMsg = getRecoveryEmailAuthMessage( + accountAddress1, guardians1[0], recoveryDataHash1, emailRecoveryModuleAddress + ); vm.expectRevert("guardian is not deployed"); emailRecoveryModule.handleRecovery(emailAuthMsg, templateIdx); @@ -116,13 +142,18 @@ contract EmailRecoveryManager_Integration_Test is function test_RevertWhen_HandleRecoveryCalled_DuringRecoveryWithoutGuardianBeingDeployed() public { - acceptGuardian(accountAddress1, guardians1[0]); - acceptGuardian(accountAddress1, guardians1[1]); + skipIfCommandHandlerType(CommandHandlerType.SafeRecoveryCommandHandler); + + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); vm.warp(12 seconds); - handleRecovery(accountAddress1, guardians1[0], recoveryDataHash1); + handleRecovery( + accountAddress1, guardians1[0], recoveryDataHash1, emailRecoveryModuleAddress + ); - EmailAuthMsg memory emailAuthMsg = - getRecoveryEmailAuthMessage(accountAddress1, guardians1[2], recoveryDataHash1); + EmailAuthMsg memory emailAuthMsg = getRecoveryEmailAuthMessage( + accountAddress1, guardians1[2], recoveryDataHash1, emailRecoveryModuleAddress + ); vm.expectRevert("guardian is not deployed"); emailRecoveryModule.handleRecovery(emailAuthMsg, templateIdx); @@ -131,51 +162,66 @@ contract EmailRecoveryManager_Integration_Test is function test_RevertWhen_HandleRecoveryCalled_AfterRecoveryProcessedButBeforeCompleteRecovery() public { - acceptGuardian(accountAddress1, guardians1[0]); - acceptGuardian(accountAddress1, guardians1[1]); + skipIfCommandHandlerType(CommandHandlerType.SafeRecoveryCommandHandler); + + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); vm.warp(12 seconds); - handleRecovery(accountAddress1, guardians1[0], recoveryDataHash1); - handleRecovery(accountAddress1, guardians1[1], recoveryDataHash1); + handleRecovery( + accountAddress1, guardians1[0], recoveryDataHash1, emailRecoveryModuleAddress + ); + handleRecovery( + accountAddress1, guardians1[1], recoveryDataHash1, emailRecoveryModuleAddress + ); - EmailAuthMsg memory emailAuthMsg = - getRecoveryEmailAuthMessage(accountAddress1, guardians1[2], recoveryDataHash1); + EmailAuthMsg memory emailAuthMsg = getRecoveryEmailAuthMessage( + accountAddress1, guardians1[2], recoveryDataHash1, emailRecoveryModuleAddress + ); vm.expectRevert("guardian is not deployed"); emailRecoveryModule.handleRecovery(emailAuthMsg, templateIdx); } function test_HandleRecoveryCalled_AfterCompleteRecoveryStartsNewRecoveryRequest() public { - acceptGuardian(accountAddress1, guardians1[0]); - acceptGuardian(accountAddress1, guardians1[1]); - acceptGuardian(accountAddress1, guardians1[2]); + skipIfCommandHandlerType(CommandHandlerType.SafeRecoveryCommandHandler); + + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[2], emailRecoveryModuleAddress); vm.warp(12 seconds); - handleRecovery(accountAddress1, guardians1[0], recoveryDataHash1); - handleRecovery(accountAddress1, guardians1[1], recoveryDataHash1); + handleRecovery( + accountAddress1, guardians1[0], recoveryDataHash1, emailRecoveryModuleAddress + ); + handleRecovery( + accountAddress1, guardians1[1], recoveryDataHash1, emailRecoveryModuleAddress + ); vm.warp(block.timestamp + delay); emailRecoveryModule.completeRecovery(accountAddress1, recoveryData1); - IEmailRecoveryManager.RecoveryRequest memory recoveryRequest = - emailRecoveryModule.getRecoveryRequest(accountAddress1); - assertEq(recoveryRequest.currentWeight, 0); + (,, uint256 currentWeight,) = emailRecoveryModule.getRecoveryRequest(accountAddress1); + assertEq(currentWeight, 0); - EmailAuthMsg memory emailAuthMsg = - getRecoveryEmailAuthMessage(accountAddress1, guardians1[2], recoveryDataHash1); + EmailAuthMsg memory emailAuthMsg = getRecoveryEmailAuthMessage( + accountAddress1, guardians1[2], recoveryDataHash1, emailRecoveryModuleAddress + ); emailRecoveryModule.handleRecovery(emailAuthMsg, templateIdx); - recoveryRequest = emailRecoveryModule.getRecoveryRequest(accountAddress1); - assertEq(recoveryRequest.currentWeight, 1); + (,, currentWeight,) = emailRecoveryModule.getRecoveryRequest(accountAddress1); + assertEq(currentWeight, 1); } function test_RevertWhen_CompleteRecoveryCalled_BeforeConfigureRecovery() public { - vm.prank(accountAddress1); - instance1.uninstallModule(MODULE_TYPE_EXECUTOR, recoveryModuleAddress, ""); - vm.stopPrank(); + skipIfCommandHandlerType(CommandHandlerType.SafeRecoveryCommandHandler); + + instance1.uninstallModule(MODULE_TYPE_EXECUTOR, emailRecoveryModuleAddress, ""); vm.expectRevert(IEmailRecoveryManager.NoRecoveryConfigured.selector); emailRecoveryModule.completeRecovery(accountAddress1, recoveryData1); } function test_RevertWhen_CompleteRecoveryCalled_BeforeHandleAcceptance() public { + skipIfCommandHandlerType(CommandHandlerType.SafeRecoveryCommandHandler); + vm.expectRevert( abi.encodeWithSelector(IEmailRecoveryManager.NotEnoughApprovals.selector, 0, threshold) ); @@ -183,7 +229,9 @@ contract EmailRecoveryManager_Integration_Test is } function test_RevertWhen_CompleteRecoveryCalled_BeforeProcessRecovery() public { - acceptGuardian(accountAddress1, guardians1[0]); + skipIfCommandHandlerType(CommandHandlerType.SafeRecoveryCommandHandler); + + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); vm.expectRevert( abi.encodeWithSelector(IEmailRecoveryManager.NotEnoughApprovals.selector, 0, threshold) @@ -192,45 +240,56 @@ contract EmailRecoveryManager_Integration_Test is } function test_TryRecoverFunctionsWhenModuleNotInstalled() public { - EmailAuthMsg memory emailAuthMsg = - getAcceptanceEmailAuthMessage(accountAddress1, guardians1[0]); + skipIfCommandHandlerType(CommandHandlerType.SafeRecoveryCommandHandler); + + EmailAuthMsg memory emailAuthMsg = getAcceptanceEmailAuthMessage( + accountAddress1, guardians1[0], emailRecoveryModuleAddress + ); emailRecoveryModule.handleAcceptance(emailAuthMsg, templateIdx); - emailAuthMsg = getAcceptanceEmailAuthMessage(accountAddress1, guardians1[1]); + emailAuthMsg = getAcceptanceEmailAuthMessage( + accountAddress1, guardians1[1], emailRecoveryModuleAddress + ); emailRecoveryModule.handleAcceptance(emailAuthMsg, templateIdx); - vm.prank(accountAddress1); - instance1.uninstallModule(MODULE_TYPE_EXECUTOR, recoveryModuleAddress, ""); - vm.stopPrank(); + instance1.uninstallModule(MODULE_TYPE_EXECUTOR, emailRecoveryModuleAddress, ""); vm.warp(12 seconds); - emailAuthMsg = - getRecoveryEmailAuthMessage(accountAddress1, guardians1[0], recoveryDataHash1); + emailAuthMsg = getRecoveryEmailAuthMessage( + accountAddress1, guardians1[0], recoveryDataHash1, emailRecoveryModuleAddress + ); vm.expectRevert(IEmailRecoveryManager.RecoveryIsNotActivated.selector); emailRecoveryModule.handleRecovery(emailAuthMsg, templateIdx); - emailAuthMsg = - getRecoveryEmailAuthMessage(accountAddress1, guardians1[1], recoveryDataHash1); + emailAuthMsg = getRecoveryEmailAuthMessage( + accountAddress1, guardians1[1], recoveryDataHash1, emailRecoveryModuleAddress + ); vm.expectRevert(IEmailRecoveryManager.RecoveryIsNotActivated.selector); emailRecoveryModule.handleRecovery(emailAuthMsg, templateIdx); } function test_TryCompleteRecoveryWhenModuleNotInstalled() public { - vm.prank(accountAddress1); - instance1.uninstallModule(MODULE_TYPE_EXECUTOR, recoveryModuleAddress, ""); - vm.stopPrank(); + skipIfCommandHandlerType(CommandHandlerType.SafeRecoveryCommandHandler); + + instance1.uninstallModule(MODULE_TYPE_EXECUTOR, emailRecoveryModuleAddress, ""); vm.expectRevert(IEmailRecoveryManager.NoRecoveryConfigured.selector); emailRecoveryModule.completeRecovery(accountAddress1, recoveryData1); } function test_StaleRecoveryRequest() public { - acceptGuardian(accountAddress1, guardians1[0]); - acceptGuardian(accountAddress1, guardians1[1]); + skipIfCommandHandlerType(CommandHandlerType.SafeRecoveryCommandHandler); + + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); vm.warp(12 seconds); - handleRecovery(accountAddress1, guardians1[0], recoveryDataHash1); - handleRecovery(accountAddress1, guardians1[1], recoveryDataHash1); + handleRecovery( + accountAddress1, guardians1[0], recoveryDataHash1, emailRecoveryModuleAddress + ); + handleRecovery( + accountAddress1, guardians1[1], recoveryDataHash1, emailRecoveryModuleAddress + ); uint256 executeAfter = block.timestamp + expiry; vm.warp(10 weeks); @@ -247,20 +306,39 @@ contract EmailRecoveryManager_Integration_Test is emailRecoveryModule.cancelRecovery(); vm.stopPrank(); - IEmailRecoveryManager.RecoveryRequest memory recoveryRequest = - emailRecoveryModule.getRecoveryRequest(accountAddress1); - assertEq(recoveryRequest.executeAfter, 0); - assertEq(recoveryRequest.executeBefore, 0); - assertEq(recoveryRequest.currentWeight, 0); - assertEq(recoveryRequest.recoveryDataHash, bytes32(0)); + ( + uint256 _executeAfter, + uint256 executeBefore, + uint256 currentWeight, + bytes32 recoveryDataHash + ) = emailRecoveryModule.getRecoveryRequest(accountAddress1); + bool hasGuardian1Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[0]); + bool hasGuardian2Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[1]); + bool hasGuardian3Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[2]); + assertEq(_executeAfter, 0); + assertEq(executeBefore, 0); + assertEq(currentWeight, 0); + assertEq(recoveryDataHash, bytes32(0)); + assertEq(hasGuardian1Voted, false); + assertEq(hasGuardian2Voted, false); + assertEq(hasGuardian3Voted, false); } function test_CancelExpiredRecoveryRequest() public { - acceptGuardian(accountAddress1, guardians1[0]); - acceptGuardian(accountAddress1, guardians1[1]); + skipIfCommandHandlerType(CommandHandlerType.SafeRecoveryCommandHandler); + + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); vm.warp(12 seconds); - handleRecovery(accountAddress1, guardians1[0], recoveryDataHash1); - handleRecovery(accountAddress1, guardians1[1], recoveryDataHash1); + handleRecovery( + accountAddress1, guardians1[0], recoveryDataHash1, emailRecoveryModuleAddress + ); + handleRecovery( + accountAddress1, guardians1[1], recoveryDataHash1, emailRecoveryModuleAddress + ); uint256 executeAfter = block.timestamp + expiry; vm.warp(executeAfter); @@ -276,20 +354,39 @@ contract EmailRecoveryManager_Integration_Test is emailRecoveryModule.cancelExpiredRecovery(accountAddress1); vm.stopPrank(); - IEmailRecoveryManager.RecoveryRequest memory recoveryRequest = - emailRecoveryModule.getRecoveryRequest(accountAddress1); - assertEq(recoveryRequest.executeAfter, 0); - assertEq(recoveryRequest.executeBefore, 0); - assertEq(recoveryRequest.currentWeight, 0); - assertEq(recoveryRequest.recoveryDataHash, bytes32(0)); + ( + uint256 _executeAfter, + uint256 executeBefore, + uint256 currentWeight, + bytes32 recoveryDataHash + ) = emailRecoveryModule.getRecoveryRequest(accountAddress1); + bool hasGuardian1Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[0]); + bool hasGuardian2Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[1]); + bool hasGuardian3Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[2]); + assertEq(_executeAfter, 0); + assertEq(executeBefore, 0); + assertEq(currentWeight, 0); + assertEq(recoveryDataHash, bytes32(0)); + assertEq(hasGuardian1Voted, false); + assertEq(hasGuardian2Voted, false); + assertEq(hasGuardian3Voted, false); } function test_CannotComplete_CancelledExpiredRecoveryRequest() public { - acceptGuardian(accountAddress1, guardians1[0]); - acceptGuardian(accountAddress1, guardians1[1]); + skipIfCommandHandlerType(CommandHandlerType.SafeRecoveryCommandHandler); + + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); vm.warp(12 seconds); - handleRecovery(accountAddress1, guardians1[0], recoveryDataHash1); - handleRecovery(accountAddress1, guardians1[1], recoveryDataHash1); + handleRecovery( + accountAddress1, guardians1[0], recoveryDataHash1, emailRecoveryModuleAddress + ); + handleRecovery( + accountAddress1, guardians1[1], recoveryDataHash1, emailRecoveryModuleAddress + ); uint256 executeAfter = block.timestamp + expiry; vm.warp(executeAfter); @@ -298,20 +395,87 @@ contract EmailRecoveryManager_Integration_Test is emailRecoveryModule.cancelExpiredRecovery(accountAddress1); vm.stopPrank(); - IEmailRecoveryManager.RecoveryRequest memory recoveryRequest = - emailRecoveryModule.getRecoveryRequest(accountAddress1); - assertEq(recoveryRequest.executeAfter, 0); - assertEq(recoveryRequest.executeBefore, 0); - assertEq(recoveryRequest.currentWeight, 0); - assertEq(recoveryRequest.recoveryDataHash, bytes32(0)); + ( + uint256 _executeAfter, + uint256 executeBefore, + uint256 currentWeight, + bytes32 recoveryDataHash + ) = emailRecoveryModule.getRecoveryRequest(accountAddress1); + bool hasGuardian1Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[0]); + bool hasGuardian2Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[1]); + bool hasGuardian3Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[2]); + assertEq(_executeAfter, 0); + assertEq(executeBefore, 0); + assertEq(currentWeight, 0); + assertEq(recoveryDataHash, bytes32(0)); + assertEq(hasGuardian1Voted, false); + assertEq(hasGuardian2Voted, false); + assertEq(hasGuardian3Voted, false); vm.expectRevert( abi.encodeWithSelector( - IEmailRecoveryManager.NotEnoughApprovals.selector, - recoveryRequest.currentWeight, - threshold + IEmailRecoveryManager.NotEnoughApprovals.selector, currentWeight, threshold ) ); emailRecoveryModule.completeRecovery(accountAddress1, recoveryData1); } + + function test_Ownable_renounceOwnership() public { + vm.prank(killSwitchAuthorizer); + emailRecoveryModule.toggleKillSwitch(); + vm.stopPrank(); + assertTrue(emailRecoveryModule.killSwitchEnabled()); + + // renounce ownership + vm.prank(killSwitchAuthorizer); + emailRecoveryModule.renounceOwnership(); + vm.stopPrank(); + + address owner = emailRecoveryModule.owner(); + assertEq(owner, address(0)); + + vm.prank(killSwitchAuthorizer); + vm.expectRevert( + abi.encodeWithSelector( + Ownable.OwnableUnauthorizedAccount.selector, killSwitchAuthorizer + ) + ); + emailRecoveryModule.toggleKillSwitch(); + vm.stopPrank(); + } + + function test_Ownable_transferOwnership() public { + address newOwner = vm.addr(99); + + vm.prank(killSwitchAuthorizer); + emailRecoveryModule.toggleKillSwitch(); + vm.stopPrank(); + assertTrue(emailRecoveryModule.killSwitchEnabled()); + + // transfer ownership + vm.prank(killSwitchAuthorizer); + emailRecoveryModule.transferOwnership(newOwner); + vm.stopPrank(); + + address owner = emailRecoveryModule.owner(); + assertEq(owner, newOwner); + + vm.prank(newOwner); + emailRecoveryModule.toggleKillSwitch(); + vm.stopPrank(); + assertFalse(emailRecoveryModule.killSwitchEnabled()); + + vm.prank(killSwitchAuthorizer); + vm.expectRevert( + abi.encodeWithSelector( + Ownable.OwnableUnauthorizedAccount.selector, killSwitchAuthorizer + ) + ); + emailRecoveryModule.toggleKillSwitch(); + vm.stopPrank(); + assertFalse(emailRecoveryModule.killSwitchEnabled()); + } } diff --git a/test/integration/IntegrationBase.t.sol b/test/integration/IntegrationBase.t.sol deleted file mode 100644 index 9858c9d7..00000000 --- a/test/integration/IntegrationBase.t.sol +++ /dev/null @@ -1,117 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.25; - -import { Test } from "forge-std/Test.sol"; -import { console2 } from "forge-std/console2.sol"; - -import { RhinestoneModuleKit, AccountInstance } from "modulekit/ModuleKit.sol"; -import { ECDSAOwnedDKIMRegistry } from - "ether-email-auth/packages/contracts/src/utils/ECDSAOwnedDKIMRegistry.sol"; -import { EmailAuth } from "ether-email-auth/packages/contracts/src/EmailAuth.sol"; -import { ECDSA } from "solady/utils/ECDSA.sol"; - -import { MockGroth16Verifier } from "src/test/MockGroth16Verifier.sol"; -import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; - -abstract contract IntegrationBase is RhinestoneModuleKit, Test { - // ZK Email contracts and variables - address zkEmailDeployer = vm.addr(1); - ECDSAOwnedDKIMRegistry dkimRegistry; - MockGroth16Verifier verifier; - EmailAuth emailAuthImpl; - - // account and owners - AccountInstance instance1; - AccountInstance instance2; - AccountInstance instance3; - address accountAddress1; - address accountAddress2; - address accountAddress3; - address owner1; - address owner2; - address owner3; - address newOwner1; - address newOwner2; - address newOwner3; - - // recovery config - address[] guardians1; - address[] guardians2; - address[] guardians3; - uint256[] guardianWeights; - uint256 totalWeight; - uint256 delay; - uint256 expiry; - uint256 threshold; - uint256 templateIdx; - - // Account salts - bytes32 accountSalt1; - bytes32 accountSalt2; - bytes32 accountSalt3; - - string selector = "12345"; - string domainName = "gmail.com"; - bytes32 publicKeyHash = 0x0ea9c777dc7110e5a9e89b13f0cfc540e3845ba120b2b6dc24024d61488d4788; - - function setUp() public virtual { - init(); - - // Create ZK Email contracts - vm.startPrank(zkEmailDeployer); - { - ECDSAOwnedDKIMRegistry dkimImpl = new ECDSAOwnedDKIMRegistry(); - ERC1967Proxy dkimProxy = new ERC1967Proxy( - address(dkimImpl), - abi.encodeCall(dkimImpl.initialize, (zkEmailDeployer, zkEmailDeployer)) - ); - dkimRegistry = ECDSAOwnedDKIMRegistry(address(dkimProxy)); - } - - string memory signedMsg = dkimRegistry.computeSignedMsg( - dkimRegistry.SET_PREFIX(), selector, domainName, publicKeyHash - ); - bytes32 digest = ECDSA.toEthSignedMessageHash(bytes(signedMsg)); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(1, digest); - bytes memory signature = abi.encodePacked(r, s, v); - dkimRegistry.setDKIMPublicKeyHash(selector, domainName, publicKeyHash, signature); - - verifier = new MockGroth16Verifier(); - emailAuthImpl = new EmailAuth(); - vm.stopPrank(); - - // create owners - owner1 = vm.createWallet("owner1").addr; - owner2 = vm.createWallet("owner2").addr; - owner3 = vm.createWallet("owner3").addr; - newOwner1 = vm.createWallet("newOwner1").addr; - newOwner2 = vm.createWallet("newOwner2").addr; - newOwner3 = vm.createWallet("newOwner3").addr; - - // Deploy and fund the accounts - instance1 = makeAccountInstance("account1"); - instance2 = makeAccountInstance("account2"); - instance3 = makeAccountInstance("account3"); - accountAddress1 = instance1.account; - accountAddress2 = instance2.account; - accountAddress3 = instance3.account; - vm.deal(address(instance1.account), 10 ether); - vm.deal(address(instance2.account), 10 ether); - vm.deal(address(instance3.account), 10 ether); - - accountSalt1 = keccak256(abi.encode("account salt 1")); - accountSalt2 = keccak256(abi.encode("account salt 2")); - accountSalt3 = keccak256(abi.encode("account salt 3")); - - // Set recovery config variables - guardianWeights = new uint256[](3); - guardianWeights[0] = 1; - guardianWeights[1] = 2; - guardianWeights[2] = 1; - totalWeight = 4; - delay = 1 seconds; - expiry = 2 weeks; - threshold = 3; - templateIdx = 0; - } -} diff --git a/test/integration/OwnableValidatorRecovery/EmailRecoveryModule/EmailRecoveryModule.t.sol b/test/integration/OwnableValidatorRecovery/EmailRecoveryModule/EmailRecoveryModule.t.sol index 1a548c4e..b41fd66b 100644 --- a/test/integration/OwnableValidatorRecovery/EmailRecoveryModule/EmailRecoveryModule.t.sol +++ b/test/integration/OwnableValidatorRecovery/EmailRecoveryModule/EmailRecoveryModule.t.sol @@ -1,18 +1,18 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; -import { ModuleKitHelpers, ModuleKitUserOp } from "modulekit/ModuleKit.sol"; -import { MODULE_TYPE_EXECUTOR, MODULE_TYPE_VALIDATOR } from "modulekit/external/ERC7579.sol"; -import { EmailAuthMsg } from "ether-email-auth/packages/contracts/src/EmailAuth.sol"; -import { SubjectUtils } from "ether-email-auth/packages/contracts/src/libraries/SubjectUtils.sol"; +import { ModuleKitHelpers, AccountInstance } from "modulekit/ModuleKit.sol"; +import { MODULE_TYPE_EXECUTOR } from "modulekit/external/ERC7579.sol"; +import { EmailAuthMsg } from "@zk-email/ether-email-auth-contracts/src/EmailAuth.sol"; +import { EmailAccountRecovery } from + "@zk-email/ether-email-auth-contracts/src/EmailAccountRecovery.sol"; import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; import { IEmailRecoveryManager } from "src/interfaces/IEmailRecoveryManager.sol"; import { IGuardianManager } from "src/interfaces/IGuardianManager.sol"; import { GuardianStorage, GuardianStatus } from "src/libraries/EnumerableGuardianMap.sol"; -import { OwnableValidator } from "src/test/OwnableValidator.sol"; +import { CommandHandlerType } from "../../../Base.t.sol"; import { OwnableValidatorRecovery_EmailRecoveryModule_Base } from "./EmailRecoveryModuleBase.t.sol"; contract OwnableValidatorRecovery_EmailRecoveryModule_Integration_Test is @@ -30,11 +30,11 @@ contract OwnableValidatorRecovery_EmailRecoveryModule_Integration_Test is ) internal { - acceptGuardian(account, guardians[0]); - acceptGuardian(account, guardians[1]); + acceptGuardian(account, guardians[0], emailRecoveryModuleAddress); + acceptGuardian(account, guardians[1], emailRecoveryModuleAddress); vm.warp(block.timestamp + 12 seconds); - handleRecovery(account, guardians[0], recoveryDataHash); - handleRecovery(account, guardians[1], recoveryDataHash); + handleRecovery(account, guardians[0], recoveryDataHash, emailRecoveryModuleAddress); + handleRecovery(account, guardians[1], recoveryDataHash, emailRecoveryModuleAddress); vm.warp(block.timestamp + delay); emailRecoveryModule.completeRecovery(account, recoveryData); } @@ -44,15 +44,17 @@ contract OwnableValidatorRecovery_EmailRecoveryModule_Integration_Test is } function test_Recover_RotatesOwnerSuccessfully() public { + skipIfCommandHandlerType(CommandHandlerType.SafeRecoveryCommandHandler); + // Accept guardian 1 - acceptGuardian(accountAddress1, guardians1[0]); + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); GuardianStorage memory guardianStorage1 = emailRecoveryModule.getGuardian(accountAddress1, guardians1[0]); assertEq(uint256(guardianStorage1.status), uint256(GuardianStatus.ACCEPTED)); assertEq(guardianStorage1.weight, uint256(1)); // Accept guardian 2 - acceptGuardian(accountAddress1, guardians1[1]); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); GuardianStorage memory guardianStorage2 = emailRecoveryModule.getGuardian(accountAddress1, guardians1[1]); assertEq(uint256(guardianStorage2.status), uint256(GuardianStatus.ACCEPTED)); @@ -61,23 +63,42 @@ contract OwnableValidatorRecovery_EmailRecoveryModule_Integration_Test is // Time travel so that EmailAuth timestamp is valid vm.warp(12 seconds); // handle recovery request for guardian 1 - handleRecovery(accountAddress1, guardians1[0], recoveryDataHash1); + handleRecovery( + accountAddress1, guardians1[0], recoveryDataHash1, emailRecoveryModuleAddress + ); uint256 executeBefore = block.timestamp + expiry; - IEmailRecoveryManager.RecoveryRequest memory recoveryRequest = - emailRecoveryModule.getRecoveryRequest(accountAddress1); - assertEq(recoveryRequest.executeAfter, 0); - assertEq(recoveryRequest.executeBefore, executeBefore); - assertEq(recoveryRequest.currentWeight, 1); - assertEq(recoveryRequest.recoveryDataHash, recoveryDataHash1); + ( + uint256 _executeAfter, + uint256 _executeBefore, + uint256 currentWeight, + bytes32 recoveryDataHash + ) = emailRecoveryModule.getRecoveryRequest(accountAddress1); + bool hasGuardian1Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[0]); + bool hasGuardian2Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[1]); + assertEq(_executeAfter, 0); + assertEq(_executeBefore, executeBefore); + assertEq(currentWeight, 1); + assertEq(recoveryDataHash, recoveryDataHash1); + assertEq(hasGuardian1Voted, true); + assertEq(hasGuardian2Voted, false); // handle recovery request for guardian 2 uint256 executeAfter = block.timestamp + delay; - handleRecovery(accountAddress1, guardians1[1], recoveryDataHash1); - recoveryRequest = emailRecoveryModule.getRecoveryRequest(accountAddress1); - assertEq(recoveryRequest.executeAfter, executeAfter); - assertEq(recoveryRequest.executeBefore, executeBefore); - assertEq(recoveryRequest.currentWeight, 3); - assertEq(recoveryRequest.recoveryDataHash, recoveryDataHash1); + handleRecovery( + accountAddress1, guardians1[1], recoveryDataHash1, emailRecoveryModuleAddress + ); + (_executeAfter, _executeBefore, currentWeight, recoveryDataHash) = + emailRecoveryModule.getRecoveryRequest(accountAddress1); + hasGuardian1Voted = emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[0]); + hasGuardian2Voted = emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[1]); + assertEq(_executeAfter, executeAfter); + assertEq(_executeBefore, executeBefore); + assertEq(currentWeight, 3); + assertEq(recoveryDataHash, recoveryDataHash1); + assertEq(hasGuardian1Voted, true); + assertEq(hasGuardian2Voted, true); // Time travel so that the recovery delay has passed vm.warp(block.timestamp + delay); @@ -85,29 +106,40 @@ contract OwnableValidatorRecovery_EmailRecoveryModule_Integration_Test is // Complete recovery emailRecoveryModule.completeRecovery(accountAddress1, recoveryData1); - recoveryRequest = emailRecoveryModule.getRecoveryRequest(accountAddress1); + (_executeAfter, _executeBefore, currentWeight, recoveryDataHash) = + emailRecoveryModule.getRecoveryRequest(accountAddress1); + hasGuardian1Voted = emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[0]); + hasGuardian2Voted = emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[1]); address updatedOwner = validator.owners(accountAddress1); - assertEq(recoveryRequest.executeAfter, 0); - assertEq(recoveryRequest.executeBefore, 0); - assertEq(recoveryRequest.currentWeight, 0); - assertEq(recoveryRequest.recoveryDataHash, bytes32(0)); + assertEq(_executeAfter, 0); + assertEq(_executeBefore, 0); + assertEq(currentWeight, 0); + assertEq(recoveryDataHash, bytes32(0)); + assertEq(hasGuardian1Voted, false); + assertEq(hasGuardian2Voted, false); assertEq(updatedOwner, newOwner1); } function test_Recover_RevertWhen_MixAccountHandleAcceptance() public { - acceptGuardian(accountAddress1, guardians1[0]); - acceptGuardianWithAccountSalt(accountAddress2, guardians1[1], accountSalt2); + skipIfCommandHandlerType(CommandHandlerType.SafeRecoveryCommandHandler); + + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardianWithAccountSalt( + accountAddress2, guardians1[1], emailRecoveryModuleAddress, accountSalt2 + ); vm.warp(12 seconds); - EmailAuthMsg memory emailAuthMsg = - getRecoveryEmailAuthMessage(accountAddress1, guardians1[1], recoveryDataHash1); + EmailAuthMsg memory emailAuthMsg = getRecoveryEmailAuthMessage( + accountAddress1, guardians1[1], recoveryDataHash1, emailRecoveryModuleAddress + ); vm.expectRevert("guardian is not deployed"); emailRecoveryModule.handleRecovery(emailAuthMsg, templateIdx); - emailAuthMsg = - getRecoveryEmailAuthMessage(accountAddress1, guardians1[0], recoveryDataHash1); + emailAuthMsg = getRecoveryEmailAuthMessage( + accountAddress1, guardians1[0], recoveryDataHash1, emailRecoveryModuleAddress + ); vm.expectRevert( abi.encodeWithSelector( @@ -118,15 +150,25 @@ contract OwnableValidatorRecovery_EmailRecoveryModule_Integration_Test is } function test_Recover_RevertWhen_MixAccountHandleRecovery() public { - acceptGuardianWithAccountSalt(accountAddress2, guardians1[1], accountSalt2); + skipIfCommandHandlerType(CommandHandlerType.SafeRecoveryCommandHandler); + + acceptGuardianWithAccountSalt( + accountAddress2, guardians1[1], emailRecoveryModuleAddress, accountSalt2 + ); - acceptGuardian(accountAddress1, guardians1[0]); - acceptGuardian(accountAddress1, guardians1[1]); + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); vm.warp(block.timestamp + 12 seconds); - handleRecovery(accountAddress1, guardians1[0], recoveryDataHash1); + handleRecovery( + accountAddress1, guardians1[0], recoveryDataHash1, emailRecoveryModuleAddress + ); EmailAuthMsg memory emailAuthMsg = getRecoveryEmailAuthMessageWithAccountSalt( - accountAddress2, guardians1[1], recoveryDataHash2, accountSalt2 + accountAddress2, + guardians1[1], + recoveryDataHash2, + emailRecoveryModuleAddress, + accountSalt2 ); vm.expectRevert( @@ -140,10 +182,13 @@ contract OwnableValidatorRecovery_EmailRecoveryModule_Integration_Test is } function test_Recover_RevertWhen_UninstallModuleBeforeAnyGuardiansAccepted() public { - instance1.uninstallModule(MODULE_TYPE_EXECUTOR, recoveryModuleAddress, ""); + skipIfCommandHandlerType(CommandHandlerType.SafeRecoveryCommandHandler); + + instance1.uninstallModule(MODULE_TYPE_EXECUTOR, emailRecoveryModuleAddress, ""); - EmailAuthMsg memory emailAuthMsg = - getAcceptanceEmailAuthMessage(accountAddress1, guardians1[0]); + EmailAuthMsg memory emailAuthMsg = getAcceptanceEmailAuthMessage( + accountAddress1, guardians1[0], emailRecoveryModuleAddress + ); vm.expectRevert(IEmailRecoveryManager.RecoveryIsNotActivated.selector); emailRecoveryModule.handleAcceptance(emailAuthMsg, templateIdx); @@ -152,11 +197,14 @@ contract OwnableValidatorRecovery_EmailRecoveryModule_Integration_Test is function test_Recover_RevertWhen_UninstallModuleBeforeEnoughAcceptedAndTryHandleAcceptance() public { - acceptGuardian(accountAddress1, guardians1[0]); - instance1.uninstallModule(MODULE_TYPE_EXECUTOR, recoveryModuleAddress, ""); + skipIfCommandHandlerType(CommandHandlerType.SafeRecoveryCommandHandler); - EmailAuthMsg memory emailAuthMsg = - getAcceptanceEmailAuthMessage(accountAddress1, guardians1[1]); + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + instance1.uninstallModule(MODULE_TYPE_EXECUTOR, emailRecoveryModuleAddress, ""); + + EmailAuthMsg memory emailAuthMsg = getAcceptanceEmailAuthMessage( + accountAddress1, guardians1[1], emailRecoveryModuleAddress + ); vm.expectRevert(IEmailRecoveryManager.RecoveryIsNotActivated.selector); emailRecoveryModule.handleAcceptance(emailAuthMsg, templateIdx); @@ -165,24 +213,31 @@ contract OwnableValidatorRecovery_EmailRecoveryModule_Integration_Test is function test_Recover_RevertWhen_UninstallModuleAfterEnoughAcceptedAndTryHandleRecovery() public { - acceptGuardian(accountAddress1, guardians1[0]); - acceptGuardian(accountAddress1, guardians1[1]); + skipIfCommandHandlerType(CommandHandlerType.SafeRecoveryCommandHandler); + + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); vm.warp(12 seconds); - EmailAuthMsg memory emailAuthMsg = - getRecoveryEmailAuthMessage(accountAddress1, guardians1[0], recoveryDataHash1); + EmailAuthMsg memory emailAuthMsg = getRecoveryEmailAuthMessage( + accountAddress1, guardians1[0], recoveryDataHash1, emailRecoveryModuleAddress + ); - instance1.uninstallModule(MODULE_TYPE_EXECUTOR, recoveryModuleAddress, ""); + instance1.uninstallModule(MODULE_TYPE_EXECUTOR, emailRecoveryModuleAddress, ""); vm.expectRevert(IEmailRecoveryManager.RecoveryIsNotActivated.selector); emailRecoveryModule.handleRecovery(emailAuthMsg, templateIdx); } function test_Recover_RevertWhen_UninstallModuleAfterOneApprovalAndTryHandleRecovery() public { - acceptGuardian(accountAddress1, guardians1[0]); - acceptGuardian(accountAddress1, guardians1[1]); + skipIfCommandHandlerType(CommandHandlerType.SafeRecoveryCommandHandler); + + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); vm.warp(12 seconds); - handleRecovery(accountAddress1, guardians1[0], recoveryDataHash1); + handleRecovery( + accountAddress1, guardians1[0], recoveryDataHash1, emailRecoveryModuleAddress + ); vm.startPrank(accountAddress1); vm.expectRevert(IGuardianManager.RecoveryInProcess.selector); @@ -192,11 +247,17 @@ contract OwnableValidatorRecovery_EmailRecoveryModule_Integration_Test is function test_Recover_RevertWhen_UninstallModuleProcessRecoveryAndTryCompleteRecovery() public { - acceptGuardian(accountAddress1, guardians1[0]); - acceptGuardian(accountAddress1, guardians1[1]); + skipIfCommandHandlerType(CommandHandlerType.SafeRecoveryCommandHandler); + + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); vm.warp(12 seconds); - handleRecovery(accountAddress1, guardians1[0], recoveryDataHash1); - handleRecovery(accountAddress1, guardians1[1], recoveryDataHash1); + handleRecovery( + accountAddress1, guardians1[0], recoveryDataHash1, emailRecoveryModuleAddress + ); + handleRecovery( + accountAddress1, guardians1[1], recoveryDataHash1, emailRecoveryModuleAddress + ); vm.warp(block.timestamp + delay); vm.startPrank(accountAddress1); @@ -205,28 +266,33 @@ contract OwnableValidatorRecovery_EmailRecoveryModule_Integration_Test is } function test_Recover_RevertWhen_UninstallModuleAndTryRecoveryAgain() public { + skipIfCommandHandlerType(CommandHandlerType.SafeRecoveryCommandHandler); + executeRecoveryFlowForAccount(accountAddress1, guardians1, recoveryDataHash1, recoveryData1); address updatedOwner1 = validator.owners(accountAddress1); assertEq(updatedOwner1, newOwner1); - instance1.uninstallModule(MODULE_TYPE_EXECUTOR, recoveryModuleAddress, ""); + instance1.uninstallModule(MODULE_TYPE_EXECUTOR, emailRecoveryModuleAddress, ""); - EmailAuthMsg memory emailAuthMsg = - getAcceptanceEmailAuthMessage(accountAddress1, guardians1[0]); + EmailAuthMsg memory emailAuthMsg = getAcceptanceEmailAuthMessage( + accountAddress1, guardians1[0], emailRecoveryModuleAddress + ); vm.expectRevert(IEmailRecoveryManager.RecoveryIsNotActivated.selector); emailRecoveryModule.handleAcceptance(emailAuthMsg, templateIdx); } function test_Recover_UninstallModuleAndRecoverAgain() public { + skipIfCommandHandlerType(CommandHandlerType.SafeRecoveryCommandHandler); + executeRecoveryFlowForAccount(accountAddress1, guardians1, recoveryDataHash1, recoveryData1); address updatedOwner = validator.owners(accountAddress1); assertEq(updatedOwner, newOwner1); - instance1.uninstallModule(MODULE_TYPE_EXECUTOR, recoveryModuleAddress, ""); + instance1.uninstallModule(MODULE_TYPE_EXECUTOR, emailRecoveryModuleAddress, ""); instance1.installModule({ moduleTypeId: MODULE_TYPE_EXECUTOR, - module: recoveryModuleAddress, + module: emailRecoveryModuleAddress, data: abi.encode(isInstalledContext, guardians1, guardianWeights, threshold, delay, expiry) }); @@ -242,7 +308,54 @@ contract OwnableValidatorRecovery_EmailRecoveryModule_Integration_Test is } function test_Recover_RotatesMultipleOwnersSuccessfully() public { + skipIfCommandHandlerType(CommandHandlerType.SafeRecoveryCommandHandler); + + executeRecoveryFlowForAccount(accountAddress1, guardians1, recoveryDataHash1, recoveryData1); + executeRecoveryFlowForAccount(accountAddress2, guardians2, recoveryDataHash2, recoveryData2); + executeRecoveryFlowForAccount(accountAddress3, guardians3, recoveryDataHash3, recoveryData3); + + address updatedOwner1 = validator.owners(accountAddress1); + address updatedOwner2 = validator.owners(accountAddress2); + address updatedOwner3 = validator.owners(accountAddress3); + assertEq(updatedOwner1, newOwner1); + assertEq(updatedOwner2, newOwner2); + assertEq(updatedOwner3, newOwner3); + } + + function test_ActivateKillSwitchDoesNotImpactOtherModules() public { + skipIfCommandHandlerType(CommandHandlerType.SafeRecoveryCommandHandler); + + bytes32 commandHandlerSalt = bytes32(uint256(1)); + bytes32 recoveryModuleSalt = bytes32(uint256(1)); + (address newRecoveryModuleAddress,) = emailRecoveryFactory.deployEmailRecoveryModule( + commandHandlerSalt, + recoveryModuleSalt, + getHandlerBytecode(), + minimumDelay, + killSwitchAuthorizer, + address(dkimRegistry), + validatorAddress, + functionSelector + ); + AccountInstance memory newAccountInstance = makeAccountInstance("account1"); + newAccountInstance.installModule({ + moduleTypeId: MODULE_TYPE_EXECUTOR, + module: newRecoveryModuleAddress, + data: abi.encode(isInstalledContext, guardians1, guardianWeights, threshold, delay, expiry) + }); + + vm.prank(killSwitchAuthorizer); + IEmailRecoveryManager(newRecoveryModuleAddress).toggleKillSwitch(); + vm.stopPrank(); + + // toggling kill switch for one module does not inpact other modules executeRecoveryFlowForAccount(accountAddress1, guardians1, recoveryDataHash1, recoveryData1); + + // new module should not be useable + vm.expectRevert(IGuardianManager.KillSwitchEnabled.selector); + EmailAccountRecovery(newRecoveryModuleAddress).completeRecovery( + accountAddress1, abi.encodePacked("1") + ); executeRecoveryFlowForAccount(accountAddress2, guardians2, recoveryDataHash2, recoveryData2); executeRecoveryFlowForAccount(accountAddress3, guardians3, recoveryDataHash3, recoveryData3); @@ -253,4 +366,31 @@ contract OwnableValidatorRecovery_EmailRecoveryModule_Integration_Test is assertEq(updatedOwner2, newOwner2); assertEq(updatedOwner3, newOwner3); } + + function test_Recover_RevertWhenUninstallModuleAndRecoverAgainWithKillSwitch() public { + skipIfCommandHandlerType(CommandHandlerType.SafeRecoveryCommandHandler); + string memory currentAccountType = vm.envOr("ACCOUNT_TYPE", string("")); + if (bytes(currentAccountType).length == 0 || Strings.equal(currentAccountType, "DEFAULT")) { + vm.skip(false); + } else { + vm.skip(true); + } + + executeRecoveryFlowForAccount(accountAddress1, guardians1, recoveryDataHash1, recoveryData1); + address updatedOwner = validator.owners(accountAddress1); + assertEq(updatedOwner, newOwner1); + + vm.prank(killSwitchAuthorizer); + IEmailRecoveryManager(emailRecoveryModuleAddress).toggleKillSwitch(); + vm.stopPrank(); + + instance1.uninstallModule(MODULE_TYPE_EXECUTOR, emailRecoveryModuleAddress, ""); + // the second module installation should fail after the kill switch is enabled. + instance1.expect4337Revert(IGuardianManager.KillSwitchEnabled.selector); + instance1.installModule({ + moduleTypeId: MODULE_TYPE_EXECUTOR, + module: emailRecoveryModuleAddress, + data: abi.encode(isInstalledContext, guardians1, guardianWeights, threshold, delay, expiry) + }); + } } diff --git a/test/integration/OwnableValidatorRecovery/EmailRecoveryModule/EmailRecoveryModuleBase.t.sol b/test/integration/OwnableValidatorRecovery/EmailRecoveryModule/EmailRecoveryModuleBase.t.sol index 6525fddb..2c0c1d49 100644 --- a/test/integration/OwnableValidatorRecovery/EmailRecoveryModule/EmailRecoveryModuleBase.t.sol +++ b/test/integration/OwnableValidatorRecovery/EmailRecoveryModule/EmailRecoveryModuleBase.t.sol @@ -1,74 +1,37 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; import { ModuleKitHelpers, ModuleKitUserOp } from "modulekit/ModuleKit.sol"; import { MODULE_TYPE_EXECUTOR, MODULE_TYPE_VALIDATOR } from "modulekit/external/ERC7579.sol"; import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; -import { - EmailAuth, - EmailAuthMsg, - EmailProof -} from "ether-email-auth/packages/contracts/src/EmailAuth.sol"; -import { SubjectUtils } from "ether-email-auth/packages/contracts/src/libraries/SubjectUtils.sol"; -import { EmailRecoverySubjectHandler } from "src/handlers/EmailRecoverySubjectHandler.sol"; +import { AccountHidingRecoveryCommandHandler } from + "src/handlers/AccountHidingRecoveryCommandHandler.sol"; import { EmailRecoveryFactory } from "src/factories/EmailRecoveryFactory.sol"; -import { EmailRecoveryManager } from "src/EmailRecoveryManager.sol"; import { EmailRecoveryModule } from "src/modules/EmailRecoveryModule.sol"; -import { OwnableValidator } from "src/test/OwnableValidator.sol"; -import { IntegrationBase } from "../../IntegrationBase.t.sol"; +import { BaseTest, CommandHandlerType } from "../../../Base.t.sol"; -abstract contract OwnableValidatorRecovery_EmailRecoveryModule_Base is IntegrationBase { +abstract contract OwnableValidatorRecovery_EmailRecoveryModule_Base is BaseTest { using ModuleKitHelpers for *; using ModuleKitUserOp for *; using Strings for uint256; using Strings for address; - EmailRecoveryFactory emailRecoveryFactory; - EmailRecoverySubjectHandler emailRecoveryHandler; - EmailRecoveryModule emailRecoveryModule; + EmailRecoveryFactory public emailRecoveryFactory; + address public commandHandlerAddress; + EmailRecoveryModule public emailRecoveryModule; - address recoveryModuleAddress; - address validatorAddress; + address public emailRecoveryModuleAddress; - OwnableValidator validator; - bytes isInstalledContext; - bytes4 functionSelector; - bytes recoveryData1; - bytes recoveryData2; - bytes recoveryData3; - bytes32 recoveryDataHash1; - bytes32 recoveryDataHash2; - bytes32 recoveryDataHash3; - - uint256 nullifierCount; + bytes public recoveryData1; + bytes public recoveryData2; + bytes public recoveryData3; + bytes32 public recoveryDataHash1; + bytes32 public recoveryDataHash2; + bytes32 public recoveryDataHash3; function setUp() public virtual override { super.setUp(); - // Deploy validator to be recovered - validator = new OwnableValidator(); - validatorAddress = address(validator); - isInstalledContext = bytes("0"); - functionSelector = bytes4(keccak256(bytes("changeOwner(address)"))); - - emailRecoveryFactory = new EmailRecoveryFactory(address(verifier), address(emailAuthImpl)); - emailRecoveryHandler = new EmailRecoverySubjectHandler(); - - // Deploy EmailRecoveryManager & EmailRecoveryModule - bytes32 subjectHandlerSalt = bytes32(uint256(0)); - bytes32 recoveryModuleSalt = bytes32(uint256(0)); - bytes memory subjectHandlerBytecode = type(EmailRecoverySubjectHandler).creationCode; - (recoveryModuleAddress,) = emailRecoveryFactory.deployEmailRecoveryModule( - subjectHandlerSalt, - recoveryModuleSalt, - subjectHandlerBytecode, - address(dkimRegistry), - validatorAddress, - functionSelector - ); - emailRecoveryModule = EmailRecoveryModule(recoveryModuleAddress); - bytes memory changeOwnerCalldata1 = abi.encodeWithSelector(functionSelector, newOwner1); bytes memory changeOwnerCalldata2 = abi.encodeWithSelector(functionSelector, newOwner2); bytes memory changeOwnerCalldata3 = abi.encodeWithSelector(functionSelector, newOwner3); @@ -79,20 +42,6 @@ abstract contract OwnableValidatorRecovery_EmailRecoveryModule_Base is Integrati recoveryDataHash2 = keccak256(recoveryData2); recoveryDataHash3 = keccak256(recoveryData3); - // Compute guardian addresses - guardians1 = new address[](3); - guardians1[0] = emailRecoveryModule.computeEmailAuthAddress(instance1.account, accountSalt1); - guardians1[1] = emailRecoveryModule.computeEmailAuthAddress(instance1.account, accountSalt2); - guardians1[2] = emailRecoveryModule.computeEmailAuthAddress(instance1.account, accountSalt3); - guardians2 = new address[](3); - guardians2[0] = emailRecoveryModule.computeEmailAuthAddress(instance2.account, accountSalt1); - guardians2[1] = emailRecoveryModule.computeEmailAuthAddress(instance2.account, accountSalt2); - guardians2[2] = emailRecoveryModule.computeEmailAuthAddress(instance2.account, accountSalt3); - guardians3 = new address[](3); - guardians3[0] = emailRecoveryModule.computeEmailAuthAddress(instance3.account, accountSalt1); - guardians3[1] = emailRecoveryModule.computeEmailAuthAddress(instance3.account, accountSalt2); - guardians3[2] = emailRecoveryModule.computeEmailAuthAddress(instance3.account, accountSalt3); - bytes memory recoveryModuleInstallData1 = abi.encode(isInstalledContext, guardians1, guardianWeights, threshold, delay, expiry); bytes memory recoveryModuleInstallData2 = @@ -108,7 +57,7 @@ abstract contract OwnableValidatorRecovery_EmailRecoveryModule_Base is Integrati }); instance1.installModule({ moduleTypeId: MODULE_TYPE_EXECUTOR, - module: recoveryModuleAddress, + module: emailRecoveryModuleAddress, data: recoveryModuleInstallData1 }); @@ -120,7 +69,7 @@ abstract contract OwnableValidatorRecovery_EmailRecoveryModule_Base is Integrati }); instance2.installModule({ moduleTypeId: MODULE_TYPE_EXECUTOR, - module: recoveryModuleAddress, + module: emailRecoveryModuleAddress, data: recoveryModuleInstallData2 }); @@ -132,203 +81,60 @@ abstract contract OwnableValidatorRecovery_EmailRecoveryModule_Base is Integrati }); instance3.installModule({ moduleTypeId: MODULE_TYPE_EXECUTOR, - module: recoveryModuleAddress, + module: emailRecoveryModuleAddress, data: recoveryModuleInstallData3 }); } // Helper functions - function generateMockEmailProof( - string memory subject, - bytes32 nullifier, + function computeEmailAuthAddress( + address account, bytes32 accountSalt ) public view - returns (EmailProof memory) - { - EmailProof memory emailProof; - emailProof.domainName = "gmail.com"; - emailProof.publicKeyHash = bytes32( - vm.parseUint( - "6632353713085157925504008443078919716322386156160602218536961028046468237192" - ) - ); - emailProof.timestamp = block.timestamp; - emailProof.maskedSubject = subject; - emailProof.emailNullifier = nullifier; - emailProof.accountSalt = accountSalt; - emailProof.isCodeExist = true; - emailProof.proof = bytes("0"); - - return emailProof; - } - - function getAccountSaltForGuardian( - address account, - address guardian - ) - public - returns (bytes32) - { - address[] memory guardians; - if (account == instance1.account) { - guardians = guardians1; - } else if (account == instance2.account) { - guardians = guardians2; - } else if (account == instance3.account) { - guardians = guardians3; - } else { - revert("Invalid account address"); - } - if (guardian == guardians[0]) { - return accountSalt1; - } - if (guardian == guardians[1]) { - return accountSalt2; - } - if (guardian == guardians[2]) { - return accountSalt3; - } - - revert("Invalid guardian address"); - } - - function generateNewNullifier() public returns (bytes32) { - return keccak256(abi.encode(nullifierCount++)); - } - - function acceptGuardian(address account, address guardian) public { - EmailAuthMsg memory emailAuthMsg = getAcceptanceEmailAuthMessage(account, guardian); - emailRecoveryModule.handleAcceptance(emailAuthMsg, templateIdx); - } - - // WithAccountSalt variation - used for creating incorrect recovery setups - function acceptGuardianWithAccountSalt( - address account, - address guardian, - bytes32 optionalAccountSalt - ) - public - { - EmailAuthMsg memory emailAuthMsg = - getAcceptanceEmailAuthMessageWithAccountSalt(account, guardian, optionalAccountSalt); - emailRecoveryModule.handleAcceptance(emailAuthMsg, templateIdx); - } - - function getAcceptanceEmailAuthMessage( - address account, - address guardian - ) - public - returns (EmailAuthMsg memory) + override + returns (address) { - return getAcceptanceEmailAuthMessageWithAccountSalt(account, guardian, bytes32(0)); + return emailRecoveryModule.computeEmailAuthAddress(account, accountSalt); } - // WithAccountSalt variation - used for creating incorrect recovery setups - function getAcceptanceEmailAuthMessageWithAccountSalt( - address account, - address guardian, - bytes32 optionalAccountSalt - ) - public - returns (EmailAuthMsg memory) - { - string memory accountString = SubjectUtils.addressToChecksumHexString(account); - string memory subject = string.concat("Accept guardian request for ", accountString); - bytes32 nullifier = generateNewNullifier(); - - bytes32 accountSalt; - if (optionalAccountSalt == bytes32(0)) { - accountSalt = getAccountSaltForGuardian(account, guardian); - } else { - accountSalt = optionalAccountSalt; - } - - EmailProof memory emailProof = generateMockEmailProof(subject, nullifier, accountSalt); - - bytes[] memory subjectParamsForAcceptance = new bytes[](1); - subjectParamsForAcceptance[0] = abi.encode(account); - return EmailAuthMsg({ - templateId: emailRecoveryModule.computeAcceptanceTemplateId(templateIdx), - subjectParams: subjectParamsForAcceptance, - skipedSubjectPrefix: 0, - proof: emailProof - }); - } - - function handleRecovery(address account, address guardian, bytes32 recoveryDataHash) public { - EmailAuthMsg memory emailAuthMsg = - getRecoveryEmailAuthMessage(account, guardian, recoveryDataHash); - emailRecoveryModule.handleRecovery(emailAuthMsg, templateIdx); - } - - // WithAccountSalt variation - used for creating incorrect recovery setups - function handleRecoveryWithAccountSalt( - address account, - address guardian, - bytes32 recoveryDataHash, - bytes32 optionalAccountSalt - ) - public - { - EmailAuthMsg memory emailAuthMsg = getRecoveryEmailAuthMessageWithAccountSalt( - account, guardian, recoveryDataHash, optionalAccountSalt - ); - emailRecoveryModule.handleRecovery(emailAuthMsg, templateIdx); - } + function deployModule(bytes memory handlerBytecode) public override { + emailRecoveryFactory = new EmailRecoveryFactory(address(verifier), address(emailAuthImpl)); - function getRecoveryEmailAuthMessage( - address account, - address guardian, - bytes32 recoveryDataHash - ) - public - returns (EmailAuthMsg memory) - { - return getRecoveryEmailAuthMessageWithAccountSalt( - account, guardian, recoveryDataHash, bytes32(0) + bytes32 commandHandlerSalt = bytes32(uint256(0)); + bytes32 recoveryModuleSalt = bytes32(uint256(0)); + (emailRecoveryModuleAddress, commandHandlerAddress) = emailRecoveryFactory + .deployEmailRecoveryModule( + commandHandlerSalt, + recoveryModuleSalt, + handlerBytecode, + minimumDelay, + killSwitchAuthorizer, + address(dkimRegistry), + validatorAddress, + functionSelector ); - } - - // WithAccountSalt variation - used for creating incorrect recovery setups - function getRecoveryEmailAuthMessageWithAccountSalt( - address account, - address guardian, - bytes32 recoveryDataHash, - bytes32 optionalAccountSalt - ) - public - returns (EmailAuthMsg memory) - { - string memory accountString = SubjectUtils.addressToChecksumHexString(account); - string memory recoveryDataHashString = uint256(recoveryDataHash).toHexString(32); - string memory subjectPart1 = string.concat("Recover account ", accountString); - string memory subjectPart2 = string.concat(" using recovery hash ", recoveryDataHashString); - - string memory subject = string.concat(subjectPart1, subjectPart2); - bytes32 nullifier = generateNewNullifier(); - - bytes32 accountSalt; - if (optionalAccountSalt == bytes32(0)) { - accountSalt = getAccountSaltForGuardian(account, guardian); - } else { - accountSalt = optionalAccountSalt; + emailRecoveryModule = EmailRecoveryModule(emailRecoveryModuleAddress); + + if (getCommandHandlerType() == CommandHandlerType.AccountHidingRecoveryCommandHandler) { + AccountHidingRecoveryCommandHandler(commandHandlerAddress).storeAccountHash( + accountAddress1 + ); + AccountHidingRecoveryCommandHandler(commandHandlerAddress).storeAccountHash( + accountAddress2 + ); + AccountHidingRecoveryCommandHandler(commandHandlerAddress).storeAccountHash( + accountAddress3 + ); } + } - EmailProof memory emailProof = generateMockEmailProof(subject, nullifier, accountSalt); - - bytes[] memory subjectParamsForRecovery = new bytes[](2); - subjectParamsForRecovery[0] = abi.encode(account); - subjectParamsForRecovery[1] = abi.encode(recoveryDataHashString); - - return EmailAuthMsg({ - templateId: emailRecoveryModule.computeRecoveryTemplateId(templateIdx), - subjectParams: subjectParamsForRecovery, - skipedSubjectPrefix: 0, - proof: emailProof - }); + function setRecoveryData() public override { + functionSelector = bytes4(keccak256(bytes("changeOwner(address)"))); + recoveryCalldata = abi.encodeWithSelector(functionSelector, newOwner1); + recoveryData = abi.encode(validatorAddress, recoveryCalldata); + recoveryDataHash = keccak256(recoveryData); } } diff --git a/test/integration/OwnableValidatorRecovery/UniversalEmailRecoveryModule/UniversalEmailRecoveryModule.t.sol b/test/integration/OwnableValidatorRecovery/UniversalEmailRecoveryModule/UniversalEmailRecoveryModule.t.sol index 5b620d65..cd22a70d 100644 --- a/test/integration/OwnableValidatorRecovery/UniversalEmailRecoveryModule/UniversalEmailRecoveryModule.t.sol +++ b/test/integration/OwnableValidatorRecovery/UniversalEmailRecoveryModule/UniversalEmailRecoveryModule.t.sol @@ -1,16 +1,16 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; -import { ModuleKitHelpers, ModuleKitUserOp } from "modulekit/ModuleKit.sol"; +import { ModuleKitHelpers } from "modulekit/ModuleKit.sol"; import { MODULE_TYPE_EXECUTOR, MODULE_TYPE_VALIDATOR } from "modulekit/external/ERC7579.sol"; -import { EmailAuthMsg } from "ether-email-auth/packages/contracts/src/EmailAuth.sol"; +import { EmailAuthMsg } from "@zk-email/ether-email-auth-contracts/src/EmailAuth.sol"; import { IEmailRecoveryManager } from "src/interfaces/IEmailRecoveryManager.sol"; import { IGuardianManager } from "src/interfaces/IGuardianManager.sol"; import { UniversalEmailRecoveryModule } from "src/modules/UniversalEmailRecoveryModule.sol"; import { GuardianStorage, GuardianStatus } from "src/libraries/EnumerableGuardianMap.sol"; import { OwnableValidator } from "src/test/OwnableValidator.sol"; +import { CommandHandlerType } from "../../../Base.t.sol"; import { OwnableValidatorRecovery_UniversalEmailRecoveryModule_Base } from "./UniversalEmailRecoveryModuleBase.t.sol"; @@ -29,11 +29,11 @@ contract OwnableValidatorRecovery_UniversalEmailRecoveryModule_Integration_Test ) internal { - acceptGuardian(account, guardians[0]); - acceptGuardian(account, guardians[1]); + acceptGuardian(account, guardians[0], emailRecoveryModuleAddress); + acceptGuardian(account, guardians[1], emailRecoveryModuleAddress); vm.warp(block.timestamp + 12 seconds); - handleRecovery(account, guardians[0], recoveryDataHash); - handleRecovery(account, guardians[1], recoveryDataHash); + handleRecovery(account, guardians[0], recoveryDataHash, emailRecoveryModuleAddress); + handleRecovery(account, guardians[1], recoveryDataHash, emailRecoveryModuleAddress); vm.warp(block.timestamp + delay); emailRecoveryModule.completeRecovery(account, recoveryData); } @@ -43,15 +43,17 @@ contract OwnableValidatorRecovery_UniversalEmailRecoveryModule_Integration_Test } function test_Recover_RotatesOwnerSuccessfully() public { + skipIfCommandHandlerType(CommandHandlerType.SafeRecoveryCommandHandler); + // Accept guardian 1 - acceptGuardian(accountAddress1, guardians1[0]); + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); GuardianStorage memory guardianStorage1 = emailRecoveryModule.getGuardian(accountAddress1, guardians1[0]); assertEq(uint256(guardianStorage1.status), uint256(GuardianStatus.ACCEPTED)); assertEq(guardianStorage1.weight, uint256(1)); // Accept guardian 2 - acceptGuardian(accountAddress1, guardians1[1]); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); GuardianStorage memory guardianStorage2 = emailRecoveryModule.getGuardian(accountAddress1, guardians1[1]); assertEq(uint256(guardianStorage2.status), uint256(GuardianStatus.ACCEPTED)); @@ -61,22 +63,41 @@ contract OwnableValidatorRecovery_UniversalEmailRecoveryModule_Integration_Test vm.warp(12 seconds); // handle recovery request for guardian 1 uint256 executeBefore = block.timestamp + expiry; - handleRecovery(accountAddress1, guardians1[0], recoveryDataHash1); - IEmailRecoveryManager.RecoveryRequest memory recoveryRequest = - emailRecoveryModule.getRecoveryRequest(accountAddress1); - assertEq(recoveryRequest.executeAfter, 0); - assertEq(recoveryRequest.executeBefore, executeBefore); - assertEq(recoveryRequest.currentWeight, 1); - assertEq(recoveryRequest.recoveryDataHash, recoveryDataHash1); + handleRecovery( + accountAddress1, guardians1[0], recoveryDataHash1, emailRecoveryModuleAddress + ); + ( + uint256 _executeAfter, + uint256 _executeBefore, + uint256 currentWeight, + bytes32 recoveryDataHash + ) = emailRecoveryModule.getRecoveryRequest(accountAddress1); + bool hasGuardian1Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[0]); + bool hasGuardian2Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[1]); + assertEq(_executeAfter, 0); + assertEq(_executeBefore, executeBefore); + assertEq(currentWeight, 1); + assertEq(recoveryDataHash, recoveryDataHash1); + assertEq(hasGuardian1Voted, true); + assertEq(hasGuardian2Voted, false); // handle recovery request for guardian 2 uint256 executeAfter = block.timestamp + delay; - handleRecovery(accountAddress1, guardians1[1], recoveryDataHash1); - recoveryRequest = emailRecoveryModule.getRecoveryRequest(accountAddress1); - assertEq(recoveryRequest.executeAfter, executeAfter); - assertEq(recoveryRequest.executeBefore, executeBefore); - assertEq(recoveryRequest.currentWeight, 3); - assertEq(recoveryRequest.recoveryDataHash, recoveryDataHash1); + handleRecovery( + accountAddress1, guardians1[1], recoveryDataHash1, emailRecoveryModuleAddress + ); + (_executeAfter, _executeBefore, currentWeight, recoveryDataHash) = + emailRecoveryModule.getRecoveryRequest(accountAddress1); + hasGuardian1Voted = emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[0]); + hasGuardian2Voted = emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[1]); + assertEq(_executeAfter, executeAfter); + assertEq(_executeBefore, executeBefore); + assertEq(currentWeight, 3); + assertEq(recoveryDataHash, recoveryDataHash1); + assertEq(hasGuardian1Voted, true); + assertEq(hasGuardian2Voted, true); // Time travel so that the recovery delay has passed vm.warp(block.timestamp + delay); @@ -84,29 +105,40 @@ contract OwnableValidatorRecovery_UniversalEmailRecoveryModule_Integration_Test // Complete recovery emailRecoveryModule.completeRecovery(accountAddress1, recoveryData1); - recoveryRequest = emailRecoveryModule.getRecoveryRequest(accountAddress1); + (_executeAfter, _executeBefore, currentWeight, recoveryDataHash) = + emailRecoveryModule.getRecoveryRequest(accountAddress1); + hasGuardian1Voted = emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[0]); + hasGuardian2Voted = emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[1]); address updatedOwner = validator.owners(accountAddress1); - assertEq(recoveryRequest.executeAfter, 0); - assertEq(recoveryRequest.executeBefore, 0); - assertEq(recoveryRequest.currentWeight, 0); - assertEq(recoveryRequest.recoveryDataHash, bytes32(0)); + assertEq(_executeAfter, 0); + assertEq(_executeBefore, 0); + assertEq(currentWeight, 0); + assertEq(recoveryDataHash, bytes32(0)); + assertEq(hasGuardian1Voted, false); + assertEq(hasGuardian2Voted, false); assertEq(updatedOwner, newOwner1); } function test_Recover_RevertWhen_MixAccountHandleAcceptance() public { - acceptGuardian(accountAddress1, guardians1[0]); - acceptGuardianWithAccountSalt(accountAddress2, guardians1[1], accountSalt2); + skipIfCommandHandlerType(CommandHandlerType.SafeRecoveryCommandHandler); + + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardianWithAccountSalt( + accountAddress2, guardians1[1], emailRecoveryModuleAddress, accountSalt2 + ); vm.warp(12 seconds); - EmailAuthMsg memory emailAuthMsg = - getRecoveryEmailAuthMessage(accountAddress1, guardians1[1], recoveryDataHash1); + EmailAuthMsg memory emailAuthMsg = getRecoveryEmailAuthMessage( + accountAddress1, guardians1[1], recoveryDataHash1, emailRecoveryModuleAddress + ); vm.expectRevert("guardian is not deployed"); emailRecoveryModule.handleRecovery(emailAuthMsg, templateIdx); - emailAuthMsg = - getRecoveryEmailAuthMessage(accountAddress1, guardians1[0], recoveryDataHash1); + emailAuthMsg = getRecoveryEmailAuthMessage( + accountAddress1, guardians1[0], recoveryDataHash1, emailRecoveryModuleAddress + ); vm.expectRevert( abi.encodeWithSelector( @@ -117,15 +149,25 @@ contract OwnableValidatorRecovery_UniversalEmailRecoveryModule_Integration_Test } function test_Recover_RevertWhen_MixAccountHandleRecovery() public { - acceptGuardianWithAccountSalt(accountAddress2, guardians1[1], accountSalt2); + skipIfCommandHandlerType(CommandHandlerType.SafeRecoveryCommandHandler); + + acceptGuardianWithAccountSalt( + accountAddress2, guardians1[1], emailRecoveryModuleAddress, accountSalt2 + ); - acceptGuardian(accountAddress1, guardians1[0]); - acceptGuardian(accountAddress1, guardians1[1]); + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); vm.warp(12 seconds); - handleRecovery(accountAddress1, guardians1[0], recoveryDataHash1); + handleRecovery( + accountAddress1, guardians1[0], recoveryDataHash1, emailRecoveryModuleAddress + ); EmailAuthMsg memory emailAuthMsg = getRecoveryEmailAuthMessageWithAccountSalt( - accountAddress2, guardians1[1], recoveryDataHash2, accountSalt2 + accountAddress2, + guardians1[1], + recoveryDataHash2, + emailRecoveryModuleAddress, + accountSalt2 ); vm.expectRevert( @@ -139,10 +181,13 @@ contract OwnableValidatorRecovery_UniversalEmailRecoveryModule_Integration_Test } function test_Recover_RevertWhen_UninstallModuleBeforeAnyGuardiansAccepted() public { - instance1.uninstallModule(MODULE_TYPE_EXECUTOR, recoveryModuleAddress, ""); + skipIfCommandHandlerType(CommandHandlerType.SafeRecoveryCommandHandler); - EmailAuthMsg memory emailAuthMsg = - getAcceptanceEmailAuthMessage(accountAddress1, guardians1[0]); + instance1.uninstallModule(MODULE_TYPE_EXECUTOR, emailRecoveryModuleAddress, ""); + + EmailAuthMsg memory emailAuthMsg = getAcceptanceEmailAuthMessage( + accountAddress1, guardians1[0], emailRecoveryModuleAddress + ); vm.expectRevert(IEmailRecoveryManager.RecoveryIsNotActivated.selector); emailRecoveryModule.handleAcceptance(emailAuthMsg, templateIdx); @@ -151,11 +196,14 @@ contract OwnableValidatorRecovery_UniversalEmailRecoveryModule_Integration_Test function test_Recover_RevertWhen_UninstallModuleBeforeEnoughAcceptedAndTryHandleAcceptance() public { - acceptGuardian(accountAddress1, guardians1[0]); - instance1.uninstallModule(MODULE_TYPE_EXECUTOR, recoveryModuleAddress, ""); + skipIfCommandHandlerType(CommandHandlerType.SafeRecoveryCommandHandler); + + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + instance1.uninstallModule(MODULE_TYPE_EXECUTOR, emailRecoveryModuleAddress, ""); - EmailAuthMsg memory emailAuthMsg = - getAcceptanceEmailAuthMessage(accountAddress1, guardians1[1]); + EmailAuthMsg memory emailAuthMsg = getAcceptanceEmailAuthMessage( + accountAddress1, guardians1[1], emailRecoveryModuleAddress + ); vm.expectRevert(IEmailRecoveryManager.RecoveryIsNotActivated.selector); emailRecoveryModule.handleAcceptance(emailAuthMsg, templateIdx); @@ -164,24 +212,31 @@ contract OwnableValidatorRecovery_UniversalEmailRecoveryModule_Integration_Test function test_Recover_RevertWhen_UninstallModuleAfterEnoughAcceptedAndTryHandleRecovery() public { - acceptGuardian(accountAddress1, guardians1[0]); - acceptGuardian(accountAddress1, guardians1[1]); + skipIfCommandHandlerType(CommandHandlerType.SafeRecoveryCommandHandler); + + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); vm.warp(12 seconds); - EmailAuthMsg memory emailAuthMsg = - getRecoveryEmailAuthMessage(accountAddress1, guardians1[0], recoveryDataHash1); + EmailAuthMsg memory emailAuthMsg = getRecoveryEmailAuthMessage( + accountAddress1, guardians1[0], recoveryDataHash1, emailRecoveryModuleAddress + ); - instance1.uninstallModule(MODULE_TYPE_EXECUTOR, recoveryModuleAddress, ""); + instance1.uninstallModule(MODULE_TYPE_EXECUTOR, emailRecoveryModuleAddress, ""); vm.expectRevert(IEmailRecoveryManager.RecoveryIsNotActivated.selector); emailRecoveryModule.handleRecovery(emailAuthMsg, templateIdx); } function test_Recover_RevertWhen_UninstallModuleAfterOneApprovalAndTryHandleRecovery() public { - acceptGuardian(accountAddress1, guardians1[0]); - acceptGuardian(accountAddress1, guardians1[1]); + skipIfCommandHandlerType(CommandHandlerType.SafeRecoveryCommandHandler); + + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); vm.warp(12 seconds); - handleRecovery(accountAddress1, guardians1[0], recoveryDataHash1); + handleRecovery( + accountAddress1, guardians1[0], recoveryDataHash1, emailRecoveryModuleAddress + ); vm.startPrank(accountAddress1); vm.expectRevert(IGuardianManager.RecoveryInProcess.selector); @@ -191,11 +246,17 @@ contract OwnableValidatorRecovery_UniversalEmailRecoveryModule_Integration_Test function test_Recover_RevertWhen_UninstallModuleProcessRecoveryAndTryCompleteRecovery() public { - acceptGuardian(accountAddress1, guardians1[0]); - acceptGuardian(accountAddress1, guardians1[1]); + skipIfCommandHandlerType(CommandHandlerType.SafeRecoveryCommandHandler); + + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); vm.warp(12 seconds); - handleRecovery(accountAddress1, guardians1[0], recoveryDataHash1); - handleRecovery(accountAddress1, guardians1[1], recoveryDataHash1); + handleRecovery( + accountAddress1, guardians1[0], recoveryDataHash1, emailRecoveryModuleAddress + ); + handleRecovery( + accountAddress1, guardians1[1], recoveryDataHash1, emailRecoveryModuleAddress + ); vm.warp(block.timestamp + delay); vm.startPrank(accountAddress1); @@ -204,28 +265,34 @@ contract OwnableValidatorRecovery_UniversalEmailRecoveryModule_Integration_Test } function test_Recover_RevertWhen_UninstallModuleAndTryRecoveryAgain() public { + skipIfCommandHandlerType(CommandHandlerType.SafeRecoveryCommandHandler); + executeRecoveryFlowForAccount(accountAddress1, guardians1, recoveryDataHash1, recoveryData1); address updatedOwner1 = validator.owners(accountAddress1); assertEq(updatedOwner1, newOwner1); - instance1.uninstallModule(MODULE_TYPE_EXECUTOR, recoveryModuleAddress, ""); + instance1.uninstallModule(MODULE_TYPE_EXECUTOR, emailRecoveryModuleAddress, ""); - EmailAuthMsg memory emailAuthMsg = - getAcceptanceEmailAuthMessage(accountAddress1, guardians1[0]); + EmailAuthMsg memory emailAuthMsg = getAcceptanceEmailAuthMessage( + accountAddress1, guardians1[0], emailRecoveryModuleAddress + ); vm.expectRevert(IEmailRecoveryManager.RecoveryIsNotActivated.selector); emailRecoveryModule.handleAcceptance(emailAuthMsg, templateIdx); } function test_Recover_UninstallModuleAndRecoverAgain() public { + skipIfCommandHandlerType(CommandHandlerType.SafeRecoveryCommandHandler); + executeRecoveryFlowForAccount(accountAddress1, guardians1, recoveryDataHash1, recoveryData1); address updatedOwner = validator.owners(accountAddress1); assertEq(updatedOwner, newOwner1); - instance1.uninstallModule(MODULE_TYPE_EXECUTOR, recoveryModuleAddress, ""); + instance1.uninstallModule(MODULE_TYPE_EXECUTOR, emailRecoveryModuleAddress, ""); + instance1.installModule({ moduleTypeId: MODULE_TYPE_EXECUTOR, - module: recoveryModuleAddress, + module: emailRecoveryModuleAddress, data: abi.encode( validatorAddress, isInstalledContext, @@ -250,6 +317,8 @@ contract OwnableValidatorRecovery_UniversalEmailRecoveryModule_Integration_Test } function test_Recover_RotatesMultipleOwnersSuccessfully() public { + skipIfCommandHandlerType(CommandHandlerType.SafeRecoveryCommandHandler); + executeRecoveryFlowForAccount(accountAddress1, guardians1, recoveryDataHash1, recoveryData1); executeRecoveryFlowForAccount(accountAddress2, guardians2, recoveryDataHash2, recoveryData2); executeRecoveryFlowForAccount(accountAddress3, guardians3, recoveryDataHash3, recoveryData3); @@ -263,6 +332,8 @@ contract OwnableValidatorRecovery_UniversalEmailRecoveryModule_Integration_Test } function test_Recover_RecoversSameValidatorAgain() public { + skipIfCommandHandlerType(CommandHandlerType.SafeRecoveryCommandHandler); + executeRecoveryFlowForAccount(accountAddress1, guardians1, recoveryDataHash1, recoveryData1); address updatedOwner1 = validator.owners(accountAddress1); assertEq(updatedOwner1, newOwner1); @@ -271,8 +342,16 @@ contract OwnableValidatorRecovery_UniversalEmailRecoveryModule_Integration_Test bytes memory newValidatorRecoveryData = abi.encode(validatorAddress, newChangeOwnerCalldata); bytes32 newValidatorRecoveryDataHash = keccak256(newValidatorRecoveryData); - handleRecovery(accountAddress1, guardians1[0], newValidatorRecoveryDataHash); - handleRecovery(accountAddress1, guardians1[1], newValidatorRecoveryDataHash); + vm.warp( + block.timestamp + emailRecoveryModule.CANCEL_EXPIRED_RECOVERY_COOLDOWN() + 1 seconds + ); + + handleRecovery( + accountAddress1, guardians1[0], newValidatorRecoveryDataHash, emailRecoveryModuleAddress + ); + handleRecovery( + accountAddress1, guardians1[1], newValidatorRecoveryDataHash, emailRecoveryModuleAddress + ); vm.warp(block.timestamp + delay); emailRecoveryModule.completeRecovery(accountAddress1, newValidatorRecoveryData); @@ -284,6 +363,8 @@ contract OwnableValidatorRecovery_UniversalEmailRecoveryModule_Integration_Test ) public { + skipIfCommandHandlerType(CommandHandlerType.SafeRecoveryCommandHandler); + OwnableValidator validator2 = new OwnableValidator(); address validator2Address = address(validator2); instance1.installModule({ @@ -300,8 +381,16 @@ contract OwnableValidatorRecovery_UniversalEmailRecoveryModule_Integration_Test address updatedOwner1 = validator.owners(accountAddress1); assertEq(updatedOwner1, newOwner1); - handleRecovery(accountAddress1, guardians1[0], validator2RecoveryDataHash); - handleRecovery(accountAddress1, guardians1[1], validator2RecoveryDataHash); + vm.warp( + block.timestamp + emailRecoveryModule.CANCEL_EXPIRED_RECOVERY_COOLDOWN() + 1 seconds + ); + + handleRecovery( + accountAddress1, guardians1[0], validator2RecoveryDataHash, emailRecoveryModuleAddress + ); + handleRecovery( + accountAddress1, guardians1[1], validator2RecoveryDataHash, emailRecoveryModuleAddress + ); vm.warp(block.timestamp + delay); vm.expectRevert( @@ -313,6 +402,8 @@ contract OwnableValidatorRecovery_UniversalEmailRecoveryModule_Integration_Test } function test_Recover_RecoversMultipleValidatorsOneAfterTheOther() public { + skipIfCommandHandlerType(CommandHandlerType.SafeRecoveryCommandHandler); + OwnableValidator validator2 = new OwnableValidator(); address validator2Address = address(validator2); instance1.installModule({ @@ -332,8 +423,16 @@ contract OwnableValidatorRecovery_UniversalEmailRecoveryModule_Integration_Test bytes memory validator2RecoveryData = abi.encode(validator2Address, newChangeOwnerCalldata); bytes32 validator2RecoveryDataHash = keccak256(validator2RecoveryData); - handleRecovery(accountAddress1, guardians1[0], validator2RecoveryDataHash); - handleRecovery(accountAddress1, guardians1[1], validator2RecoveryDataHash); + vm.warp( + block.timestamp + emailRecoveryModule.CANCEL_EXPIRED_RECOVERY_COOLDOWN() + 1 seconds + ); + + handleRecovery( + accountAddress1, guardians1[0], validator2RecoveryDataHash, emailRecoveryModuleAddress + ); + handleRecovery( + accountAddress1, guardians1[1], validator2RecoveryDataHash, emailRecoveryModuleAddress + ); vm.warp(block.timestamp + delay); emailRecoveryModule.completeRecovery(accountAddress1, validator2RecoveryData); @@ -342,6 +441,8 @@ contract OwnableValidatorRecovery_UniversalEmailRecoveryModule_Integration_Test } function test_Recover_RevertWhen_RecoversMultipleValidatorsAtOnce() public { + skipIfCommandHandlerType(CommandHandlerType.SafeRecoveryCommandHandler); + OwnableValidator validator2 = new OwnableValidator(); address validator2Address = address(validator2); instance1.installModule({ @@ -355,16 +456,19 @@ contract OwnableValidatorRecovery_UniversalEmailRecoveryModule_Integration_Test bytes32 validator2RecoveryDataHash = keccak256(validator2RecoveryData); // Accept guardians - acceptGuardian(accountAddress1, guardians1[0]); - acceptGuardian(accountAddress1, guardians1[1]); + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); vm.warp(block.timestamp + 12 seconds); // process recovery for validator 1 - handleRecovery(accountAddress1, guardians1[0], recoveryDataHash1); + handleRecovery( + accountAddress1, guardians1[0], recoveryDataHash1, emailRecoveryModuleAddress + ); vm.warp(block.timestamp + 12 seconds); - EmailAuthMsg memory emailAuthMsg = - getRecoveryEmailAuthMessage(accountAddress1, guardians1[0], validator2RecoveryDataHash); + EmailAuthMsg memory emailAuthMsg = getRecoveryEmailAuthMessage( + accountAddress1, guardians1[1], validator2RecoveryDataHash, emailRecoveryModuleAddress + ); vm.expectRevert( abi.encodeWithSelector( @@ -376,4 +480,37 @@ contract OwnableValidatorRecovery_UniversalEmailRecoveryModule_Integration_Test // process recovery for validator 2 emailRecoveryModule.handleRecovery(emailAuthMsg, templateIdx); } + + function test_Recover_RevertWhenUninstallModuleAndRecoverAgainWithKillSwitch() public { + skipIfCommandHandlerType(CommandHandlerType.SafeRecoveryCommandHandler); + string memory currentAccountType = vm.envOr("ACCOUNT_TYPE", string("")); + if (bytes(currentAccountType).length != 0) { + vm.skip(true); + } + executeRecoveryFlowForAccount(accountAddress1, guardians1, recoveryDataHash1, recoveryData1); + address updatedOwner1 = validator.owners(accountAddress1); + assertEq(updatedOwner1, newOwner1); + + instance1.uninstallModule(MODULE_TYPE_EXECUTOR, emailRecoveryModuleAddress, ""); + + vm.prank(killSwitchAuthorizer); + IEmailRecoveryManager(emailRecoveryModuleAddress).toggleKillSwitch(); + vm.stopPrank(); + + instance1.expect4337Revert(IGuardianManager.KillSwitchEnabled.selector); + instance1.installModule({ + moduleTypeId: MODULE_TYPE_EXECUTOR, + module: emailRecoveryModuleAddress, + data: abi.encode( + validatorAddress, + isInstalledContext, + functionSelector, + guardians1, + guardianWeights, + threshold, + delay, + expiry + ) + }); + } } diff --git a/test/integration/OwnableValidatorRecovery/UniversalEmailRecoveryModule/UniversalEmailRecoveryModuleBase.t.sol b/test/integration/OwnableValidatorRecovery/UniversalEmailRecoveryModule/UniversalEmailRecoveryModuleBase.t.sol index d5eb4f7d..cf5e6cad 100644 --- a/test/integration/OwnableValidatorRecovery/UniversalEmailRecoveryModule/UniversalEmailRecoveryModuleBase.t.sol +++ b/test/integration/OwnableValidatorRecovery/UniversalEmailRecoveryModule/UniversalEmailRecoveryModuleBase.t.sol @@ -1,74 +1,40 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; import { ModuleKitHelpers, ModuleKitUserOp } from "modulekit/ModuleKit.sol"; import { MODULE_TYPE_EXECUTOR, MODULE_TYPE_VALIDATOR } from "modulekit/external/ERC7579.sol"; import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; -import { - EmailAuth, - EmailAuthMsg, - EmailProof -} from "ether-email-auth/packages/contracts/src/EmailAuth.sol"; -import { SubjectUtils } from "ether-email-auth/packages/contracts/src/libraries/SubjectUtils.sol"; -import { EmailRecoverySubjectHandler } from "src/handlers/EmailRecoverySubjectHandler.sol"; -import { EmailRecoveryFactory } from "src/factories/EmailRecoveryFactory.sol"; +import { Create2 } from "@openzeppelin/contracts/utils/Create2.sol"; + +import { AccountHidingRecoveryCommandHandler } from + "src/handlers/AccountHidingRecoveryCommandHandler.sol"; import { EmailRecoveryUniversalFactory } from "src/factories/EmailRecoveryUniversalFactory.sol"; -import { EmailRecoveryManager } from "src/EmailRecoveryManager.sol"; import { UniversalEmailRecoveryModuleHarness } from "../../../unit/UniversalEmailRecoveryModuleHarness.sol"; -import { OwnableValidator } from "src/test/OwnableValidator.sol"; -import { IntegrationBase } from "../../IntegrationBase.t.sol"; +import { BaseTest, CommandHandlerType } from "../../../Base.t.sol"; -abstract contract OwnableValidatorRecovery_UniversalEmailRecoveryModule_Base is IntegrationBase { +abstract contract OwnableValidatorRecovery_UniversalEmailRecoveryModule_Base is BaseTest { using ModuleKitHelpers for *; using ModuleKitUserOp for *; using Strings for uint256; using Strings for address; - EmailRecoveryUniversalFactory emailRecoveryFactory; - EmailRecoverySubjectHandler emailRecoveryHandler; - UniversalEmailRecoveryModuleHarness emailRecoveryModule; - - address recoveryModuleAddress; - address validatorAddress; + EmailRecoveryUniversalFactory public emailRecoveryFactory; + address public commandHandlerAddress; + UniversalEmailRecoveryModuleHarness public emailRecoveryModule; - OwnableValidator validator; - bytes isInstalledContext; - bytes4 functionSelector; - bytes recoveryData1; - bytes recoveryData2; - bytes recoveryData3; - bytes32 recoveryDataHash1; - bytes32 recoveryDataHash2; - bytes32 recoveryDataHash3; + address public emailRecoveryModuleAddress; - uint256 nullifierCount; + bytes public recoveryData1; + bytes public recoveryData2; + bytes public recoveryData3; + bytes32 public recoveryDataHash1; + bytes32 public recoveryDataHash2; + bytes32 public recoveryDataHash3; function setUp() public virtual override { super.setUp(); - emailRecoveryFactory = - new EmailRecoveryUniversalFactory(address(verifier), address(emailAuthImpl)); - emailRecoveryHandler = new EmailRecoverySubjectHandler(); - - // Deploy EmailRecoveryManager & UniversalEmailRecoveryModule - bytes32 subjectHandlerSalt = bytes32(uint256(0)); - bytes32 recoveryModuleSalt = bytes32(uint256(0)); - bytes memory subjectHandlerBytecode = type(EmailRecoverySubjectHandler).creationCode; - emailRecoveryModule = new UniversalEmailRecoveryModuleHarness( - address(verifier), - address(dkimRegistry), - address(emailAuthImpl), - address(emailRecoveryHandler) - ); - recoveryModuleAddress = address(emailRecoveryModule); - - // Deploy validator to be recovered - validator = new OwnableValidator(); - validatorAddress = address(validator); - isInstalledContext = bytes("0"); - functionSelector = bytes4(keccak256(bytes("changeOwner(address)"))); bytes memory changeOwnerCalldata1 = abi.encodeWithSelector(functionSelector, newOwner1); bytes memory changeOwnerCalldata2 = abi.encodeWithSelector(functionSelector, newOwner2); bytes memory changeOwnerCalldata3 = abi.encodeWithSelector(functionSelector, newOwner3); @@ -79,20 +45,6 @@ abstract contract OwnableValidatorRecovery_UniversalEmailRecoveryModule_Base is recoveryDataHash2 = keccak256(recoveryData2); recoveryDataHash3 = keccak256(recoveryData3); - // Compute guardian addresses - guardians1 = new address[](3); - guardians1[0] = emailRecoveryModule.computeEmailAuthAddress(instance1.account, accountSalt1); - guardians1[1] = emailRecoveryModule.computeEmailAuthAddress(instance1.account, accountSalt2); - guardians1[2] = emailRecoveryModule.computeEmailAuthAddress(instance1.account, accountSalt3); - guardians2 = new address[](3); - guardians2[0] = emailRecoveryModule.computeEmailAuthAddress(instance2.account, accountSalt1); - guardians2[1] = emailRecoveryModule.computeEmailAuthAddress(instance2.account, accountSalt2); - guardians2[2] = emailRecoveryModule.computeEmailAuthAddress(instance2.account, accountSalt3); - guardians3 = new address[](3); - guardians3[0] = emailRecoveryModule.computeEmailAuthAddress(instance3.account, accountSalt1); - guardians3[1] = emailRecoveryModule.computeEmailAuthAddress(instance3.account, accountSalt2); - guardians3[2] = emailRecoveryModule.computeEmailAuthAddress(instance3.account, accountSalt3); - bytes memory recoveryModuleInstallData1 = abi.encode( validatorAddress, isInstalledContext, @@ -132,7 +84,7 @@ abstract contract OwnableValidatorRecovery_UniversalEmailRecoveryModule_Base is }); instance1.installModule({ moduleTypeId: MODULE_TYPE_EXECUTOR, - module: recoveryModuleAddress, + module: emailRecoveryModuleAddress, data: recoveryModuleInstallData1 }); @@ -144,7 +96,7 @@ abstract contract OwnableValidatorRecovery_UniversalEmailRecoveryModule_Base is }); instance2.installModule({ moduleTypeId: MODULE_TYPE_EXECUTOR, - module: recoveryModuleAddress, + module: emailRecoveryModuleAddress, data: recoveryModuleInstallData2 }); @@ -156,203 +108,56 @@ abstract contract OwnableValidatorRecovery_UniversalEmailRecoveryModule_Base is }); instance3.installModule({ moduleTypeId: MODULE_TYPE_EXECUTOR, - module: recoveryModuleAddress, + module: emailRecoveryModuleAddress, data: recoveryModuleInstallData3 }); } // Helper functions - function generateMockEmailProof( - string memory subject, - bytes32 nullifier, + function computeEmailAuthAddress( + address account, bytes32 accountSalt ) public view - returns (EmailProof memory) + override + returns (address) { - EmailProof memory emailProof; - emailProof.domainName = "gmail.com"; - emailProof.publicKeyHash = bytes32( - vm.parseUint( - "6632353713085157925504008443078919716322386156160602218536961028046468237192" - ) - ); - emailProof.timestamp = block.timestamp; - emailProof.maskedSubject = subject; - emailProof.emailNullifier = nullifier; - emailProof.accountSalt = accountSalt; - emailProof.isCodeExist = true; - emailProof.proof = bytes("0"); - - return emailProof; + return emailRecoveryModule.computeEmailAuthAddress(account, accountSalt); } - function getAccountSaltForGuardian( - address account, - address guardian - ) - public - returns (bytes32) - { - address[] memory guardians; - if (account == instance1.account) { - guardians = guardians1; - } else if (account == instance2.account) { - guardians = guardians2; - } else if (account == instance3.account) { - guardians = guardians3; - } else { - revert("Invalid account address"); - } - if (guardian == guardians[0]) { - return accountSalt1; - } - if (guardian == guardians[1]) { - return accountSalt2; - } - if (guardian == guardians[2]) { - return accountSalt3; - } - - revert("Invalid guardian address"); - } - - function generateNewNullifier() public returns (bytes32) { - return keccak256(abi.encode(nullifierCount++)); - } - - function acceptGuardian(address account, address guardian) public { - EmailAuthMsg memory emailAuthMsg = getAcceptanceEmailAuthMessage(account, guardian); - emailRecoveryModule.handleAcceptance(emailAuthMsg, templateIdx); - } - - // WithAccountSalt variation - used for creating incorrect recovery setups - function acceptGuardianWithAccountSalt( - address account, - address guardian, - bytes32 optionalAccountSalt - ) - public - { - EmailAuthMsg memory emailAuthMsg = - getAcceptanceEmailAuthMessageWithAccountSalt(account, guardian, optionalAccountSalt); - emailRecoveryModule.handleAcceptance(emailAuthMsg, templateIdx); - } - - function getAcceptanceEmailAuthMessage( - address account, - address guardian - ) - public - returns (EmailAuthMsg memory) - { - return getAcceptanceEmailAuthMessageWithAccountSalt(account, guardian, bytes32(0)); - } + function deployModule(bytes memory handlerBytecode) public override { + bytes32 commandHandlerSalt = bytes32(uint256(0)); + commandHandlerAddress = Create2.deploy(0, commandHandlerSalt, handlerBytecode); - // WithAccountSalt variation - used for creating incorrect recovery setups - function getAcceptanceEmailAuthMessageWithAccountSalt( - address account, - address guardian, - bytes32 optionalAccountSalt - ) - public - returns (EmailAuthMsg memory) - { - string memory accountString = SubjectUtils.addressToChecksumHexString(account); - string memory subject = string.concat("Accept guardian request for ", accountString); - bytes32 nullifier = generateNewNullifier(); - - bytes32 accountSalt; - if (optionalAccountSalt == bytes32(0)) { - accountSalt = getAccountSaltForGuardian(account, guardian); - } else { - accountSalt = optionalAccountSalt; - } - - EmailProof memory emailProof = generateMockEmailProof(subject, nullifier, accountSalt); - - bytes[] memory subjectParamsForAcceptance = new bytes[](1); - subjectParamsForAcceptance[0] = abi.encode(account); - return EmailAuthMsg({ - templateId: emailRecoveryModule.computeAcceptanceTemplateId(templateIdx), - subjectParams: subjectParamsForAcceptance, - skipedSubjectPrefix: 0, - proof: emailProof - }); - } - - function handleRecovery(address account, address guardian, bytes32 recoveryDataHash) public { - EmailAuthMsg memory emailAuthMsg = - getRecoveryEmailAuthMessage(account, guardian, recoveryDataHash); - emailRecoveryModule.handleRecovery(emailAuthMsg, templateIdx); - } - - // WithAccountSalt variation - used for creating incorrect recovery setups - function handleRecoveryWithAccountSalt( - address account, - address guardian, - bytes32 recoveryDataHash, - bytes32 optionalAccountSalt - ) - public - { - EmailAuthMsg memory emailAuthMsg = getRecoveryEmailAuthMessageWithAccountSalt( - account, guardian, recoveryDataHash, optionalAccountSalt - ); - emailRecoveryModule.handleRecovery(emailAuthMsg, templateIdx); - } - - function getRecoveryEmailAuthMessage( - address account, - address guardian, - bytes32 recoveryDataHash - ) - public - returns (EmailAuthMsg memory) - { - return getRecoveryEmailAuthMessageWithAccountSalt( - account, guardian, recoveryDataHash, bytes32(0) + emailRecoveryModule = new UniversalEmailRecoveryModuleHarness( + address(verifier), + address(dkimRegistry), + address(emailAuthImpl), + commandHandlerAddress, + minimumDelay, + killSwitchAuthorizer ); - } - - // WithAccountSalt variation - used for creating incorrect recovery setups - function getRecoveryEmailAuthMessageWithAccountSalt( - address account, - address guardian, - bytes32 recoveryDataHash, - bytes32 optionalAccountSalt - ) - public - returns (EmailAuthMsg memory) - { - string memory accountString = SubjectUtils.addressToChecksumHexString(account); - string memory recoveryDataHashString = uint256(recoveryDataHash).toHexString(32); - string memory subjectPart1 = string.concat("Recover account ", accountString); - string memory subjectPart2 = string.concat(" using recovery hash ", recoveryDataHashString); - - string memory subject = string.concat(subjectPart1, subjectPart2); - bytes32 nullifier = generateNewNullifier(); - - bytes32 accountSalt; - if (optionalAccountSalt == bytes32(0)) { - accountSalt = getAccountSaltForGuardian(account, guardian); - } else { - accountSalt = optionalAccountSalt; + emailRecoveryModuleAddress = address(emailRecoveryModule); + + if (getCommandHandlerType() == CommandHandlerType.AccountHidingRecoveryCommandHandler) { + AccountHidingRecoveryCommandHandler(commandHandlerAddress).storeAccountHash( + accountAddress1 + ); + AccountHidingRecoveryCommandHandler(commandHandlerAddress).storeAccountHash( + accountAddress2 + ); + AccountHidingRecoveryCommandHandler(commandHandlerAddress).storeAccountHash( + accountAddress3 + ); } + } - EmailProof memory emailProof = generateMockEmailProof(subject, nullifier, accountSalt); - - bytes[] memory subjectParamsForRecovery = new bytes[](2); - subjectParamsForRecovery[0] = abi.encode(account); - subjectParamsForRecovery[1] = abi.encode(recoveryDataHashString); - - return EmailAuthMsg({ - templateId: emailRecoveryModule.computeRecoveryTemplateId(templateIdx), - subjectParams: subjectParamsForRecovery, - skipedSubjectPrefix: 0, - proof: emailProof - }); + function setRecoveryData() public override { + functionSelector = bytes4(keccak256(bytes("changeOwner(address)"))); + recoveryCalldata = abi.encodeWithSelector(functionSelector, newOwner1); + recoveryData = abi.encode(validatorAddress, recoveryCalldata); + recoveryDataHash = keccak256(recoveryData); } } diff --git a/test/integration/SafeRecovery/SafeIntegrationBase.t.sol b/test/integration/SafeRecovery/SafeIntegrationBase.t.sol index 9a436434..c97e87c1 100644 --- a/test/integration/SafeRecovery/SafeIntegrationBase.t.sol +++ b/test/integration/SafeRecovery/SafeIntegrationBase.t.sol @@ -1,44 +1,26 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; - import { ModuleKitHelpers } from "modulekit/ModuleKit.sol"; import { MODULE_TYPE_EXECUTOR } from "modulekit/external/ERC7579.sol"; -import { EmailAuthMsg, EmailProof } from "ether-email-auth/packages/contracts/src/EmailAuth.sol"; -import { SubjectUtils } from "ether-email-auth/packages/contracts/src/libraries/SubjectUtils.sol"; +import { EmailAuthMsg, EmailProof } from "@zk-email/ether-email-auth-contracts/src/EmailAuth.sol"; +import { CommandUtils } from "@zk-email/ether-email-auth-contracts/src/libraries/CommandUtils.sol"; import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; +import { Create2 } from "@openzeppelin/contracts/utils/Create2.sol"; -import { EmailRecoveryManager } from "src/EmailRecoveryManager.sol"; +import { AccountHidingRecoveryCommandHandler } from + "src/handlers/AccountHidingRecoveryCommandHandler.sol"; import { UniversalEmailRecoveryModule } from "src/modules/UniversalEmailRecoveryModule.sol"; -import { SafeRecoverySubjectHandler } from "src/handlers/SafeRecoverySubjectHandler.sol"; -import { IntegrationBase } from "../IntegrationBase.t.sol"; +import { BaseTest, CommandHandlerType } from "../../Base.t.sol"; -abstract contract SafeIntegrationBase is IntegrationBase { +abstract contract SafeIntegrationBase is BaseTest { using ModuleKitHelpers for *; using Strings for uint256; using Strings for address; - SafeRecoverySubjectHandler safeRecoverySubjectHandler; - UniversalEmailRecoveryModule emailRecoveryModule; - address recoveryModuleAddress; - - bytes isInstalledContext; - bytes4 functionSelector; - - uint256 nullifierCount; - - /** - * Helper function to return if current account type is safe or not - */ - function isAccountTypeSafe() public returns (bool) { - string memory currentAccountType = vm.envOr("ACCOUNT_TYPE", string("")); - if (Strings.equal(currentAccountType, "SAFE")) { - return true; - } else { - return false; - } - } + address public commandHandlerAddress; + UniversalEmailRecoveryModule public emailRecoveryModule; + address public emailRecoveryModuleAddress; function setUp() public virtual override { if (!isAccountTypeSafe()) { @@ -46,38 +28,9 @@ abstract contract SafeIntegrationBase is IntegrationBase { } super.setUp(); - // Deploy handler, manager and module - safeRecoverySubjectHandler = new SafeRecoverySubjectHandler(); - - emailRecoveryModule = new UniversalEmailRecoveryModule( - address(verifier), - address(dkimRegistry), - address(emailAuthImpl), - address(safeRecoverySubjectHandler) - ); - recoveryModuleAddress = address(emailRecoveryModule); - - isInstalledContext = bytes("0"); - functionSelector = bytes4(keccak256(bytes("swapOwner(address,address,address)"))); - - // Compute guardian addresses - guardians1 = new address[](3); - guardians1[0] = emailRecoveryModule.computeEmailAuthAddress(accountAddress1, accountSalt1); - guardians1[1] = emailRecoveryModule.computeEmailAuthAddress(accountAddress1, accountSalt2); - guardians1[2] = emailRecoveryModule.computeEmailAuthAddress(accountAddress1, accountSalt3); - guardians2 = new address[](3); - guardians2[0] = emailRecoveryModule.computeEmailAuthAddress(instance2.account, accountSalt1); - guardians2[1] = emailRecoveryModule.computeEmailAuthAddress(instance2.account, accountSalt2); - guardians2[2] = emailRecoveryModule.computeEmailAuthAddress(instance2.account, accountSalt3); - guardians3 = new address[](3); - guardians3[0] = emailRecoveryModule.computeEmailAuthAddress(instance3.account, accountSalt1); - guardians3[1] = emailRecoveryModule.computeEmailAuthAddress(instance3.account, accountSalt2); - guardians3[2] = emailRecoveryModule.computeEmailAuthAddress(instance3.account, accountSalt3); - - vm.prank(accountAddress1); instance1.installModule({ moduleTypeId: MODULE_TYPE_EXECUTOR, - module: recoveryModuleAddress, + module: emailRecoveryModuleAddress, data: abi.encode( address(accountAddress1), isInstalledContext, @@ -89,109 +42,68 @@ abstract contract SafeIntegrationBase is IntegrationBase { expiry ) }); - vm.stopPrank(); } - function generateMockEmailProof( - string memory subject, - bytes32 nullifier, + function computeEmailAuthAddress( + address account, bytes32 accountSalt ) public view - returns (EmailProof memory) + override + returns (address) { - EmailProof memory emailProof; - emailProof.domainName = "gmail.com"; - emailProof.publicKeyHash = bytes32( - vm.parseUint( - "6632353713085157925504008443078919716322386156160602218536961028046468237192" - ) - ); - emailProof.timestamp = block.timestamp; - emailProof.maskedSubject = subject; - emailProof.emailNullifier = nullifier; - emailProof.accountSalt = accountSalt; - emailProof.isCodeExist = true; - emailProof.proof = bytes("0"); - - return emailProof; + return emailRecoveryModule.computeEmailAuthAddress(account, accountSalt); } - function getAccountSaltForGuardian(address guardian) public returns (bytes32) { - if (guardian == guardians1[0]) { - return accountSalt1; - } - if (guardian == guardians1[1]) { - return accountSalt2; - } - if (guardian == guardians1[2]) { - return accountSalt3; - } + function deployModule(bytes memory handlerBytecode) public override { + bytes32 commandHandlerSalt = bytes32(uint256(0)); + commandHandlerAddress = Create2.deploy(0, commandHandlerSalt, handlerBytecode); - revert("Invalid guardian address"); - } - - function generateNewNullifier() public returns (bytes32) { - return keccak256(abi.encode(nullifierCount++)); - } - - function acceptGuardian(address account, address guardian) public { - EmailAuthMsg memory emailAuthMsg = getAcceptanceEmailAuthMessage(account, guardian); - emailRecoveryModule.handleAcceptance(emailAuthMsg, templateIdx); - } - - function getAcceptanceEmailAuthMessage( - address account, - address guardian - ) - public - returns (EmailAuthMsg memory) - { - string memory accountString = SubjectUtils.addressToChecksumHexString(account); - string memory subject = string.concat("Accept guardian request for ", accountString); - bytes32 nullifier = generateNewNullifier(); - bytes32 accountSalt = getAccountSaltForGuardian(guardian); - - EmailProof memory emailProof = generateMockEmailProof(subject, nullifier, accountSalt); + emailRecoveryModule = new UniversalEmailRecoveryModule( + address(verifier), + address(dkimRegistry), + address(emailAuthImpl), + commandHandlerAddress, + minimumDelay, + killSwitchAuthorizer + ); + emailRecoveryModuleAddress = address(emailRecoveryModule); - bytes[] memory subjectParamsForAcceptance = new bytes[](1); - subjectParamsForAcceptance[0] = abi.encode(account); - return EmailAuthMsg({ - templateId: emailRecoveryModule.computeAcceptanceTemplateId(templateIdx), - subjectParams: subjectParamsForAcceptance, - skipedSubjectPrefix: 0, - proof: emailProof - }); + if (getCommandHandlerType() == CommandHandlerType.AccountHidingRecoveryCommandHandler) { + AccountHidingRecoveryCommandHandler(commandHandlerAddress).storeAccountHash( + accountAddress1 + ); + } } - function handleRecovery( + function handleRecoveryForSafe( address account, address oldOwner, - address newOwner, + address newOwner1, address guardian ) public { EmailAuthMsg memory emailAuthMsg = - getRecoveryEmailAuthMessage(account, oldOwner, newOwner, guardian); + getRecoveryEmailAuthMessage(account, oldOwner, newOwner1, guardian); emailRecoveryModule.handleRecovery(emailAuthMsg, templateIdx); } function getRecoveryEmailAuthMessage( address account, address oldOwner, - address newOwner, + address newOwner1, address guardian ) public returns (EmailAuthMsg memory) { - string memory accountString = SubjectUtils.addressToChecksumHexString(account); - string memory oldOwnerString = SubjectUtils.addressToChecksumHexString(oldOwner); - string memory newOwnerString = SubjectUtils.addressToChecksumHexString(newOwner); + string memory accountString = CommandUtils.addressToChecksumHexString(account); + string memory oldOwnerString = CommandUtils.addressToChecksumHexString(oldOwner); + string memory newOwnerString = CommandUtils.addressToChecksumHexString(newOwner1); - string memory subject = string.concat( + string memory command = string.concat( "Recover account ", accountString, " from old owner ", @@ -200,20 +112,27 @@ abstract contract SafeIntegrationBase is IntegrationBase { newOwnerString ); bytes32 nullifier = generateNewNullifier(); - bytes32 accountSalt = getAccountSaltForGuardian(guardian); + bytes32 accountSalt = getAccountSaltForGuardian(account, guardian); - EmailProof memory emailProof = generateMockEmailProof(subject, nullifier, accountSalt); + EmailProof memory emailProof = generateMockEmailProof(command, nullifier, accountSalt); - bytes[] memory subjectParamsForRecovery = new bytes[](3); - subjectParamsForRecovery[0] = abi.encode(account); - subjectParamsForRecovery[1] = abi.encode(oldOwner); - subjectParamsForRecovery[2] = abi.encode(newOwner); + bytes[] memory commandParamsForRecovery = new bytes[](3); + commandParamsForRecovery[0] = abi.encode(account); + commandParamsForRecovery[1] = abi.encode(oldOwner); + commandParamsForRecovery[2] = abi.encode(newOwner1); return EmailAuthMsg({ templateId: emailRecoveryModule.computeRecoveryTemplateId(templateIdx), - subjectParams: subjectParamsForRecovery, - skipedSubjectPrefix: 0, + commandParams: commandParamsForRecovery, + skippedCommandPrefix: 0, proof: emailProof }); } + + function setRecoveryData() public override { + functionSelector = bytes4(keccak256(bytes("swapOwner(address,address,address)"))); + recoveryCalldata = abi.encodeWithSelector(functionSelector, address(1), owner1, newOwner1); + recoveryData = abi.encode(accountAddress1, recoveryCalldata); + recoveryDataHash = keccak256(recoveryData); + } } diff --git a/test/integration/SafeRecovery/SafeNativeIntegrationBase.t.sol b/test/integration/SafeRecovery/SafeNativeIntegrationBase.t.sol index 0c350f80..bbc7ee52 100644 --- a/test/integration/SafeRecovery/SafeNativeIntegrationBase.t.sol +++ b/test/integration/SafeRecovery/SafeNativeIntegrationBase.t.sol @@ -1,56 +1,30 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; - import { ModuleKitHelpers } from "modulekit/ModuleKit.sol"; -import { MODULE_TYPE_EXECUTOR } from "modulekit/external/ERC7579.sol"; -import { EmailAuthMsg, EmailProof } from "ether-email-auth/packages/contracts/src/EmailAuth.sol"; -import { SubjectUtils } from "ether-email-auth/packages/contracts/src/libraries/SubjectUtils.sol"; +import { EmailAuthMsg, EmailProof } from "@zk-email/ether-email-auth-contracts/src/EmailAuth.sol"; +import { CommandUtils } from "@zk-email/ether-email-auth-contracts/src/libraries/CommandUtils.sol"; import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; +import { Create2 } from "@openzeppelin/contracts/utils/Create2.sol"; import { Safe } from "@safe-global/safe-contracts/contracts/Safe.sol"; import { SafeProxy } from "@safe-global/safe-contracts/contracts/proxies/SafeProxy.sol"; -import { SafeEmailRecoveryModule } from "src/modules/SafeEmailRecoveryModule.sol"; -import { AccountHidingRecoverySubjectHandler } from - "src/handlers/AccountHidingRecoverySubjectHandler.sol"; -import { IntegrationBase } from "../IntegrationBase.t.sol"; +import { SafeEmailRecoveryModuleHarness } from "test/unit/SafeEmailRecoveryModuleHarness.sol"; +import { AccountHidingRecoveryCommandHandler } from + "src/handlers/AccountHidingRecoveryCommandHandler.sol"; +import { BaseTest, CommandHandlerType } from "../../Base.t.sol"; +import { IEmailRecoveryModule } from "../../Base.t.sol"; -abstract contract SafeNativeIntegrationBase is IntegrationBase { +abstract contract SafeNativeIntegrationBase is BaseTest { using ModuleKitHelpers for *; using Strings for uint256; using Strings for address; - SafeEmailRecoveryModule emailRecoveryModule; - address emailRecoveryModuleAddress; + SafeEmailRecoveryModuleHarness public emailRecoveryModule; + address public emailRecoveryModuleAddress; Safe public safeSingleton; Safe public safe; - address public safeAddress; - address public owner; - bytes isInstalledContext; - bytes4 functionSelector; - uint256 nullifierCount; - address subjectHandler; - - /** - * Helper function to return if current account type is safe or not - */ - function isAccountTypeSafe() public returns (bool) { - string memory currentAccountType = vm.envOr("ACCOUNT_TYPE", string("")); - if (Strings.equal(currentAccountType, "SAFE")) { - return true; - } else { - return false; - } - } - - function skipIfNotSafeAccountType() public { - if (isAccountTypeSafe()) { - vm.skip(false); - } else { - vm.skip(true); - } - } + address public commandHandlerAddress; function setUp() public virtual override { if (!isAccountTypeSafe()) { @@ -58,69 +32,62 @@ abstract contract SafeNativeIntegrationBase is IntegrationBase { } super.setUp(); - subjectHandler = address(new AccountHidingRecoverySubjectHandler()); - emailRecoveryModule = new SafeEmailRecoveryModule( - address(verifier), - address(dkimRegistry), - address(emailAuthImpl), - address(subjectHandler) - ); - emailRecoveryModuleAddress = address(emailRecoveryModule); - safeSingleton = new Safe(); SafeProxy safeProxy = new SafeProxy(address(safeSingleton)); safe = Safe(payable(address(safeProxy))); - safeAddress = address(safe); + accountAddress1 = address(safe); - isInstalledContext = bytes("0"); - functionSelector = bytes4(keccak256(bytes("swapOwner(address,address,address)"))); + if (getCommandHandlerType() == CommandHandlerType.AccountHidingRecoveryCommandHandler) { + AccountHidingRecoveryCommandHandler(commandHandlerAddress).storeAccountHash( + accountAddress1 + ); + } - // Compute guardian addresses - guardians1 = new address[](3); - guardians1[0] = emailRecoveryModule.computeEmailAuthAddress(safeAddress, accountSalt1); - guardians1[1] = emailRecoveryModule.computeEmailAuthAddress(safeAddress, accountSalt2); - guardians1[2] = emailRecoveryModule.computeEmailAuthAddress(safeAddress, accountSalt3); + // Overwrite the default values + guardians1[0] = emailRecoveryModule.computeEmailAuthAddress(accountAddress1, accountSalt1); + guardians1[1] = emailRecoveryModule.computeEmailAuthAddress(accountAddress1, accountSalt2); + guardians1[2] = emailRecoveryModule.computeEmailAuthAddress(accountAddress1, accountSalt3); address[] memory owners = new address[](1); - owner = owner1; - owners[0] = owner; + owners[0] = owner1; safe.setup( owners, 1, address(0), bytes("0"), address(0), address(0), 0, payable(address(0)) ); - vm.startPrank(safeAddress); + vm.startPrank(accountAddress1); safe.enableModule(address(emailRecoveryModule)); vm.stopPrank(); } - function generateMockEmailProof( - string memory subject, - bytes32 nullifier, + function computeEmailAuthAddress( + address account, bytes32 accountSalt ) public view - returns (EmailProof memory) + override + returns (address) { - EmailProof memory emailProof; - emailProof.domainName = "gmail.com"; - emailProof.publicKeyHash = bytes32( - vm.parseUint( - "6632353713085157925504008443078919716322386156160602218536961028046468237192" - ) + return emailRecoveryModule.computeEmailAuthAddress(account, accountSalt); + } + + function deployModule(bytes memory handlerBytecode) public override { + bytes32 commandHandlerSalt = bytes32(uint256(0)); + commandHandlerAddress = Create2.deploy(0, commandHandlerSalt, handlerBytecode); + + emailRecoveryModule = new SafeEmailRecoveryModuleHarness( + address(verifier), + address(dkimRegistry), + address(emailAuthImpl), + commandHandlerAddress, + minimumDelay, + killSwitchAuthorizer ); - emailProof.timestamp = block.timestamp; - emailProof.maskedSubject = subject; - emailProof.emailNullifier = nullifier; - emailProof.accountSalt = accountSalt; - emailProof.isCodeExist = true; - emailProof.proof = bytes("0"); - - return emailProof; + emailRecoveryModuleAddress = address(emailRecoveryModule); } - function getAccountSaltForGuardian(address guardian) public returns (bytes32) { + function getAccountSaltForGuardian(address guardian) public view returns (bytes32) { if (guardian == guardians1[0]) { return accountSalt1; } @@ -131,44 +98,61 @@ abstract contract SafeNativeIntegrationBase is IntegrationBase { return accountSalt3; } + /* solhint-disable-next-line gas-custom-errors, custom-errors */ revert("Invalid guardian address"); } - function generateNewNullifier() public returns (bytes32) { - return keccak256(abi.encode(nullifierCount++)); - } - - function acceptGuardian(address account, address guardian) public { - EmailAuthMsg memory emailAuthMsg = getAcceptanceEmailAuthMessage(account, guardian); - emailRecoveryModule.handleAcceptance(emailAuthMsg, templateIdx); - } - - function getAcceptanceEmailAuthMessage( + function getAcceptanceEmailAuthMessageWithAccountSalt( address account, - address guardian + address guardian, + address _emailRecoveryModule, + bytes32 optionalAccountSalt ) public + override returns (EmailAuthMsg memory) { - bytes32 accountHash = keccak256(abi.encodePacked(account)); - string memory accountHashString = uint256(accountHash).toHexString(32); - string memory subject = string.concat("Accept guardian request for ", accountHashString); + string memory command; + bytes[] memory commandParamsForAcceptance = new bytes[](1); + if (getCommandHandlerType() == CommandHandlerType.AccountHidingRecoveryCommandHandler) { + bytes32 accountHash = keccak256(abi.encodePacked(account)); + string memory accountHashString = uint256(accountHash).toHexString(32); + command = string.concat("Accept guardian request for ", accountHashString); + commandParamsForAcceptance[0] = abi.encode(accountHashString); + } else { + string memory accountString = CommandUtils.addressToChecksumHexString(account); + command = string.concat("Accept guardian request for ", accountString); + commandParamsForAcceptance[0] = abi.encode(account); + } + bytes32 nullifier = generateNewNullifier(); - bytes32 accountSalt = getAccountSaltForGuardian(guardian); - EmailProof memory emailProof = generateMockEmailProof(subject, nullifier, accountSalt); + bytes32 accountSalt; + if (optionalAccountSalt == bytes32(0)) { + accountSalt = getAccountSaltForGuardian(account, guardian); + } else { + accountSalt = optionalAccountSalt; + } + + EmailProof memory emailProof = generateMockEmailProof(command, nullifier, accountSalt); - bytes[] memory subjectParamsForAcceptance = new bytes[](1); - subjectParamsForAcceptance[0] = abi.encode(accountHashString); return EmailAuthMsg({ - templateId: emailRecoveryModule.computeAcceptanceTemplateId(templateIdx), - subjectParams: subjectParamsForAcceptance, - skipedSubjectPrefix: 0, + templateId: IEmailRecoveryModule(_emailRecoveryModule).computeAcceptanceTemplateId( + templateIdx + ), + commandParams: commandParamsForAcceptance, + skippedCommandPrefix: 0, proof: emailProof }); } - function handleRecovery(address account, bytes32 recoveryDataHash, address guardian) public { + function handleRecoveryForSafe( + address account, + bytes32 recoveryDataHash, + address guardian + ) + public + { EmailAuthMsg memory emailAuthMsg = getRecoveryEmailAuthMessage(account, recoveryDataHash, guardian); emailRecoveryModule.handleRecovery(emailAuthMsg, templateIdx); @@ -182,26 +166,51 @@ abstract contract SafeNativeIntegrationBase is IntegrationBase { public returns (EmailAuthMsg memory) { - bytes32 accountHash = keccak256(abi.encodePacked(account)); - string memory accountHashString = uint256(accountHash).toHexString(32); - string memory recoveryDataHashString = uint256(recoveryDataHash).toHexString(32); - string memory subjectPart1 = string.concat("Recover account ", accountHashString); - string memory subjectPart2 = string.concat(" using recovery hash ", recoveryDataHashString); - string memory subject = string.concat(subjectPart1, subjectPart2); + string memory command; + bytes[] memory commandParamsForRecovery = new bytes[](2); + if (getCommandHandlerType() == CommandHandlerType.AccountHidingRecoveryCommandHandler) { + bytes32 accountHash = keccak256(abi.encodePacked(account)); + string memory accountHashString = uint256(accountHash).toHexString(32); + string memory recoveryDataHashString = uint256(recoveryDataHash).toHexString(32); + string memory commandPart1 = string.concat("Recover account ", accountHashString); + string memory commandPart2 = + string.concat(" using recovery hash ", recoveryDataHashString); + command = string.concat(commandPart1, commandPart2); + + commandParamsForRecovery = new bytes[](2); + commandParamsForRecovery[0] = abi.encode(accountHashString); + commandParamsForRecovery[1] = abi.encode(recoveryDataHashString); + } + if (getCommandHandlerType() == CommandHandlerType.EmailRecoveryCommandHandler) { + string memory accountString = CommandUtils.addressToChecksumHexString(account); + string memory recoveryDataHashString = uint256(recoveryDataHash).toHexString(32); + string memory commandPart1 = string.concat("Recover account ", accountString); + string memory commandPart2 = + string.concat(" using recovery hash ", recoveryDataHashString); + command = string.concat(commandPart1, commandPart2); + + commandParamsForRecovery = new bytes[](2); + commandParamsForRecovery[0] = abi.encode(account); + commandParamsForRecovery[1] = abi.encode(recoveryDataHashString); + } bytes32 nullifier = generateNewNullifier(); bytes32 accountSalt = getAccountSaltForGuardian(guardian); - EmailProof memory emailProof = generateMockEmailProof(subject, nullifier, accountSalt); + EmailProof memory emailProof = generateMockEmailProof(command, nullifier, accountSalt); - bytes[] memory subjectParamsForRecovery = new bytes[](2); - subjectParamsForRecovery[0] = abi.encode(accountHashString); - subjectParamsForRecovery[1] = abi.encode(recoveryDataHashString); return EmailAuthMsg({ templateId: emailRecoveryModule.computeRecoveryTemplateId(templateIdx), - subjectParams: subjectParamsForRecovery, - skipedSubjectPrefix: 0, + commandParams: commandParamsForRecovery, + skippedCommandPrefix: 0, proof: emailProof }); } + + function setRecoveryData() public override { + functionSelector = bytes4(keccak256(bytes("swapOwner(address,address,address)"))); + recoveryCalldata = abi.encodeWithSelector(functionSelector, address(1), owner1, newOwner1); + recoveryData = abi.encode(accountAddress1, recoveryCalldata); + recoveryDataHash = keccak256(recoveryData); + } } diff --git a/test/integration/SafeRecovery/SafeRecovery.t.sol b/test/integration/SafeRecovery/SafeRecovery.t.sol index 0f8cd1ec..4f706231 100644 --- a/test/integration/SafeRecovery/SafeRecovery.t.sol +++ b/test/integration/SafeRecovery/SafeRecovery.t.sol @@ -1,16 +1,12 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; import { ModuleKitHelpers, ModuleKitUserOp } from "modulekit/ModuleKit.sol"; -import { MODULE_TYPE_EXECUTOR } from "erc7579/interfaces/IERC7579Module.sol"; -import { IERC7579Account } from "erc7579/interfaces/IERC7579Account.sol"; import { Safe } from "@safe-global/safe-contracts/contracts/Safe.sol"; -import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; -import { IEmailRecoveryManager } from "src/interfaces/IEmailRecoveryManager.sol"; -import { GuardianStorage, GuardianStatus } from "src/libraries/EnumerableGuardianMap.sol"; import { SafeIntegrationBase } from "./SafeIntegrationBase.t.sol"; +import { CommandHandlerType } from "../../Base.t.sol"; +import { GuardianStorage, GuardianStatus } from "src/libraries/EnumerableGuardianMap.sol"; contract SafeRecovery_Integration_Test is SafeIntegrationBase { using ModuleKitHelpers for *; @@ -24,29 +20,29 @@ contract SafeRecovery_Integration_Test is SafeIntegrationBase { if (!isAccountTypeSafe()) { vm.skip(true); } + skipIfNotCommandHandlerType(CommandHandlerType.SafeRecoveryCommandHandler); bytes memory swapOwnerCalldata = abi.encodeWithSignature( "swapOwner(address,address,address)", address(1), owner1, newOwner1 ); bytes memory recoveryData = abi.encode(accountAddress1, swapOwnerCalldata); - bytes32 recoveryDataHash = keccak256(recoveryData); - bytes[] memory subjectParamsForRecovery = new bytes[](3); - subjectParamsForRecovery[0] = abi.encode(accountAddress1); - subjectParamsForRecovery[1] = abi.encode(owner1); - subjectParamsForRecovery[2] = abi.encode(newOwner1); + bytes[] memory commandParamsForRecovery = new bytes[](3); + commandParamsForRecovery[0] = abi.encode(accountAddress1); + commandParamsForRecovery[1] = abi.encode(owner1); + commandParamsForRecovery[2] = abi.encode(newOwner1); GuardianStorage memory guardianStorage1 = emailRecoveryModule.getGuardian(accountAddress1, guardians1[0]); // Accept guardian - acceptGuardian(accountAddress1, guardians1[0]); + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); guardianStorage1 = emailRecoveryModule.getGuardian(accountAddress1, guardians1[0]); assertEq(uint256(guardianStorage1.status), uint256(GuardianStatus.ACCEPTED)); assertEq(guardianStorage1.weight, uint256(1)); // Accept guardian - acceptGuardian(accountAddress1, guardians1[1]); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); GuardianStorage memory guardianStorage2 = emailRecoveryModule.getGuardian(accountAddress1, guardians1[1]); assertEq(uint256(guardianStorage2.status), uint256(GuardianStatus.ACCEPTED)); @@ -56,29 +52,42 @@ contract SafeRecovery_Integration_Test is SafeIntegrationBase { vm.warp(12 seconds); // handle recovery request for guardian 1 - handleRecovery(accountAddress1, owner1, newOwner1, guardians1[0]); - IEmailRecoveryManager.RecoveryRequest memory recoveryRequest = - emailRecoveryModule.getRecoveryRequest(accountAddress1); - assertEq(recoveryRequest.currentWeight, 1); + handleRecoveryForSafe(accountAddress1, owner1, newOwner1, guardians1[0]); + (,, uint256 currentWeight,) = emailRecoveryModule.getRecoveryRequest(accountAddress1); + bool hasGuardian1Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[0]); + assertEq(currentWeight, 1); + assertEq(hasGuardian1Voted, true); // handle recovery request for guardian 2 uint256 executeAfter = block.timestamp + delay; uint256 executeBefore = block.timestamp + expiry; - handleRecovery(accountAddress1, owner1, newOwner1, guardians1[1]); - recoveryRequest = emailRecoveryModule.getRecoveryRequest(accountAddress1); - assertEq(recoveryRequest.executeAfter, executeAfter); - assertEq(recoveryRequest.executeBefore, executeBefore); - assertEq(recoveryRequest.currentWeight, 3); + handleRecoveryForSafe(accountAddress1, owner1, newOwner1, guardians1[1]); + (uint256 _executeAfter, uint256 _executeBefore, uint256 currentWeight2,) = + emailRecoveryModule.getRecoveryRequest(accountAddress1); + hasGuardian1Voted = emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[0]); + bool hasGuardian2Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[1]); + assertEq(_executeAfter, executeAfter); + assertEq(_executeBefore, executeBefore); + assertEq(currentWeight2, 3); + assertEq(hasGuardian1Voted, true); + assertEq(hasGuardian2Voted, true); vm.warp(block.timestamp + delay); // Complete recovery emailRecoveryModule.completeRecovery(accountAddress1, recoveryData); - recoveryRequest = emailRecoveryModule.getRecoveryRequest(accountAddress1); - assertEq(recoveryRequest.executeAfter, 0); - assertEq(recoveryRequest.executeBefore, 0); - assertEq(recoveryRequest.currentWeight, 0); + (_executeAfter, _executeBefore, currentWeight2,) = + emailRecoveryModule.getRecoveryRequest(accountAddress1); + hasGuardian1Voted = emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[0]); + hasGuardian2Voted = emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[1]); + assertEq(_executeAfter, 0); + assertEq(_executeBefore, 0); + assertEq(currentWeight2, 0); + assertEq(hasGuardian1Voted, false); + assertEq(hasGuardian2Voted, false); vm.prank(accountAddress1); bool isOwner = Safe(payable(accountAddress1)).isOwner(newOwner1); @@ -88,7 +97,7 @@ contract SafeRecovery_Integration_Test is SafeIntegrationBase { assertFalse(oldOwnerIsOwner); } - // FIXME: This test cannot uninstall the module - reverts with no error message + // FIXME: (merge-ok) This test cannot uninstall the module - reverts with no error message // function test_OnUninstall_DeInitsStateSuccessfully() public { // // configure and complete an entire recovery request // test_Recover_RotatesOwnerSuccessfully(); @@ -98,7 +107,7 @@ contract SafeRecovery_Integration_Test is SafeIntegrationBase { // // Uninstall module // vm.prank(accountAddress1); - // account.uninstallModule(MODULE_TYPE_EXECUTOR, recoveryModuleAddress, ""); + // account.uninstallModule(MODULE_TYPE_EXECUTOR, emailRecoveryModuleAddress, ""); // vm.stopPrank(); // // bool isModuleInstalled = account.isModuleInstalled( @@ -118,10 +127,10 @@ contract SafeRecovery_Integration_Test is SafeIntegrationBase { // // assert that the recovery request has been cleared successfully // IEmailRecoveryManager.RecoveryRequest memory recoveryRequest = // emailRecoveryModule.getRecoveryRequest(accountAddress1); - // assertEq(recoveryRequest.executeAfter, 0); - // assertEq(recoveryRequest.executeBefore, 0); - // assertEq(recoveryRequest.currentWeight, 0); - // assertEq(recoveryRequest.subjectParams.length, 0); + // assertEq(executeAfter, 0); + // assertEq(executeBefore, 0); + // assertEq(currentWeight, 0); + // assertEq(commandParams.length, 0); // // assert that guardian storage has been cleared successfully for guardian 1 // GuardianStorage memory guardianStorage1 = diff --git a/test/integration/SafeRecovery/SafeRecoveryNativeModule.t.sol b/test/integration/SafeRecovery/SafeRecoveryNativeModule.t.sol index 5d5c06f9..ef607a37 100644 --- a/test/integration/SafeRecovery/SafeRecoveryNativeModule.t.sol +++ b/test/integration/SafeRecovery/SafeRecoveryNativeModule.t.sol @@ -1,20 +1,11 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; -import { ModuleKitHelpers, ModuleKitUserOp } from "modulekit/ModuleKit.sol"; -import { MODULE_TYPE_EXECUTOR } from "erc7579/interfaces/IERC7579Module.sol"; -import { IERC7579Account } from "erc7579/interfaces/IERC7579Account.sol"; import { Safe } from "@safe-global/safe-contracts/contracts/Safe.sol"; -import { SafeProxy } from "@safe-global/safe-contracts/contracts/proxies/SafeProxy.sol"; -import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; -import { EmailAuthMsg } from "ether-email-auth/packages/contracts/src/EmailAuth.sol"; -import { SafeEmailRecoveryModule } from "src/modules/SafeEmailRecoveryModule.sol"; -import { IEmailRecoveryManager } from "src/interfaces/IEmailRecoveryManager.sol"; -import { AccountHidingRecoverySubjectHandler } from - "src/handlers/AccountHidingRecoverySubjectHandler.sol"; +import { EmailAuthMsg } from "@zk-email/ether-email-auth-contracts/src/EmailAuth.sol"; import { GuardianStorage, GuardianStatus } from "src/libraries/EnumerableGuardianMap.sol"; import { SafeNativeIntegrationBase } from "./SafeNativeIntegrationBase.t.sol"; +import { CommandHandlerType } from "../../Base.t.sol"; contract SafeRecoveryNativeModule_Integration_Test is SafeNativeIntegrationBase { function setUp() public override { @@ -23,38 +14,32 @@ contract SafeRecoveryNativeModule_Integration_Test is SafeNativeIntegrationBase function testIntegration_AccountRecovery() public { skipIfNotSafeAccountType(); + skipIfCommandHandlerType(CommandHandlerType.SafeRecoveryCommandHandler); - address newOwner = owner2; // Configure recovery - vm.startPrank(safeAddress); + vm.startPrank(accountAddress1); emailRecoveryModule.configureSafeRecovery( guardians1, guardianWeights, threshold, delay, expiry ); vm.stopPrank(); - bytes memory recoveryCalldata = abi.encodeWithSignature( - "swapOwner(address,address,address)", address(1), owner, newOwner - ); - bytes memory recoveryData = abi.encode(safeAddress, recoveryCalldata); - bytes32 recoveryDataHash = keccak256(recoveryData); - - bytes32 accountHash = keccak256(abi.encodePacked(safeAddress)); - - AccountHidingRecoverySubjectHandler(subjectHandler).storeAccountHash(safeAddress); - // Accept guardian - EmailAuthMsg memory emailAuthMsg = getAcceptanceEmailAuthMessage(safeAddress, guardians1[0]); + EmailAuthMsg memory emailAuthMsg = getAcceptanceEmailAuthMessageWithAccountSalt( + accountAddress1, guardians1[0], emailRecoveryModuleAddress, accountSalt1 + ); emailRecoveryModule.handleAcceptance(emailAuthMsg, templateIdx); GuardianStorage memory guardianStorage1 = - emailRecoveryModule.getGuardian(safeAddress, guardians1[0]); + emailRecoveryModule.getGuardian(accountAddress1, guardians1[0]); assertEq(uint256(guardianStorage1.status), uint256(GuardianStatus.ACCEPTED)); assertEq(guardianStorage1.weight, uint256(1)); // Accept guardian - emailAuthMsg = getAcceptanceEmailAuthMessage(safeAddress, guardians1[1]); + emailAuthMsg = getAcceptanceEmailAuthMessageWithAccountSalt( + accountAddress1, guardians1[1], emailRecoveryModuleAddress, accountSalt2 + ); emailRecoveryModule.handleAcceptance(emailAuthMsg, templateIdx); GuardianStorage memory guardianStorage2 = - emailRecoveryModule.getGuardian(safeAddress, guardians1[1]); + emailRecoveryModule.getGuardian(accountAddress1, guardians1[1]); assertEq(uint256(guardianStorage2.status), uint256(GuardianStatus.ACCEPTED)); assertEq(guardianStorage2.weight, uint256(2)); @@ -63,41 +48,61 @@ contract SafeRecoveryNativeModule_Integration_Test is SafeNativeIntegrationBase // handle recovery request for guardian 1 uint256 executeBefore = block.timestamp + expiry; - emailAuthMsg = getRecoveryEmailAuthMessage(safeAddress, recoveryDataHash, guardians1[0]); + emailAuthMsg = getRecoveryEmailAuthMessage(accountAddress1, recoveryDataHash, guardians1[0]); emailRecoveryModule.handleRecovery(emailAuthMsg, templateIdx); - IEmailRecoveryManager.RecoveryRequest memory recoveryRequest = - emailRecoveryModule.getRecoveryRequest(safeAddress); - assertEq(recoveryRequest.executeAfter, 0); - assertEq(recoveryRequest.executeBefore, executeBefore); - assertEq(recoveryRequest.currentWeight, 1); - assertEq(recoveryRequest.recoveryDataHash, recoveryDataHash); + ( + uint256 _executeAfter, + uint256 _executeBefore, + uint256 currentWeight, + bytes32 _recoveryDataHash + ) = emailRecoveryModule.getRecoveryRequest(accountAddress1); + bool hasGuardian1Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[0]); + bool hasGuardian2Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[1]); + assertEq(_executeAfter, 0); + assertEq(_executeBefore, executeBefore); + assertEq(currentWeight, 1); + assertEq(_recoveryDataHash, recoveryDataHash); + assertEq(hasGuardian1Voted, true); + assertEq(hasGuardian2Voted, false); // handle recovery request for guardian 2 uint256 executeAfter = block.timestamp + delay; - emailAuthMsg = getRecoveryEmailAuthMessage(safeAddress, recoveryDataHash, guardians1[1]); + emailAuthMsg = getRecoveryEmailAuthMessage(accountAddress1, recoveryDataHash, guardians1[1]); emailRecoveryModule.handleRecovery(emailAuthMsg, templateIdx); - recoveryRequest = emailRecoveryModule.getRecoveryRequest(safeAddress); - assertEq(recoveryRequest.executeAfter, executeAfter); - assertEq(recoveryRequest.executeBefore, executeBefore); - assertEq(recoveryRequest.currentWeight, 3); - assertEq(recoveryRequest.recoveryDataHash, recoveryDataHash); + (_executeAfter, _executeBefore, currentWeight, _recoveryDataHash) = + emailRecoveryModule.getRecoveryRequest(accountAddress1); + hasGuardian1Voted = emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[0]); + hasGuardian2Voted = emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[1]); + assertEq(_executeAfter, executeAfter); + assertEq(_executeBefore, executeBefore); + assertEq(currentWeight, 3); + assertEq(_recoveryDataHash, recoveryDataHash); + assertEq(hasGuardian1Voted, true); + assertEq(hasGuardian2Voted, true); vm.warp(block.timestamp + delay); // Complete recovery - emailRecoveryModule.completeRecovery(safeAddress, recoveryData); - - recoveryRequest = emailRecoveryModule.getRecoveryRequest(safeAddress); - assertEq(recoveryRequest.executeAfter, 0); - assertEq(recoveryRequest.executeBefore, 0); - assertEq(recoveryRequest.currentWeight, 0); - assertEq(recoveryRequest.recoveryDataHash, bytes32(0)); - - vm.prank(safeAddress); - bool isOwner = Safe(payable(safeAddress)).isOwner(newOwner); + emailRecoveryModule.completeRecovery(accountAddress1, recoveryData); + + (_executeAfter, _executeBefore, currentWeight, _recoveryDataHash) = + emailRecoveryModule.getRecoveryRequest(accountAddress1); + hasGuardian1Voted = emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[0]); + hasGuardian2Voted = emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[1]); + assertEq(_executeAfter, 0); + assertEq(_executeBefore, 0); + assertEq(currentWeight, 0); + assertEq(_recoveryDataHash, bytes32(0)); + assertEq(hasGuardian1Voted, false); + assertEq(hasGuardian2Voted, false); + + vm.prank(accountAddress1); + bool isOwner = Safe(payable(accountAddress1)).isOwner(newOwner1); assertTrue(isOwner); - bool oldOwnerIsOwner = Safe(payable(safeAddress)).isOwner(owner); + bool oldOwnerIsOwner = Safe(payable(accountAddress1)).isOwner(owner1); assertFalse(oldOwnerIsOwner); } @@ -105,20 +110,19 @@ contract SafeRecoveryNativeModule_Integration_Test is SafeNativeIntegrationBase testIntegration_AccountRecovery(); bool isModuleEnabled = - Safe(payable(safeAddress)).isModuleEnabled(emailRecoveryModuleAddress); + Safe(payable(accountAddress1)).isModuleEnabled(emailRecoveryModuleAddress); assertTrue(isModuleEnabled); // Uninstall module - // instance1.uninstallModule(MODULE_TYPE_EXECUTOR, recoveryModuleAddress, ""); - vm.prank(safeAddress); - Safe(payable(safeAddress)).disableModule(address(1), emailRecoveryModuleAddress); + vm.prank(accountAddress1); + Safe(payable(accountAddress1)).disableModule(address(1), emailRecoveryModuleAddress); vm.stopPrank(); - isModuleEnabled = Safe(payable(safeAddress)).isModuleEnabled(emailRecoveryModuleAddress); + isModuleEnabled = Safe(payable(accountAddress1)).isModuleEnabled(emailRecoveryModuleAddress); assertFalse(isModuleEnabled); - vm.prank(safeAddress); - emailRecoveryModule.resetWhenDisabled(safeAddress); + vm.prank(accountAddress1); + emailRecoveryModule.resetWhenDisabled(accountAddress1); vm.stopPrank(); } } diff --git a/test/unit/EmailRecoveryManager/acceptGuardian.t.sol b/test/unit/EmailRecoveryManager/acceptGuardian.t.sol index 636157db..e1808ec2 100644 --- a/test/unit/EmailRecoveryManager/acceptGuardian.t.sol +++ b/test/unit/EmailRecoveryManager/acceptGuardian.t.sol @@ -1,47 +1,68 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; import { ModuleKitHelpers, ModuleKitUserOp } from "modulekit/ModuleKit.sol"; import { MODULE_TYPE_EXECUTOR } from "modulekit/external/ERC7579.sol"; +import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; import { IEmailRecoveryManager } from "src/interfaces/IEmailRecoveryManager.sol"; -import { GuardianManager } from "src/GuardianManager.sol"; import { IGuardianManager } from "src/interfaces/IGuardianManager.sol"; import { GuardianStorage, GuardianStatus } from "src/libraries/EnumerableGuardianMap.sol"; import { UnitBase } from "../UnitBase.t.sol"; +import { CommandHandlerType } from "../../Base.t.sol"; contract EmailRecoveryManager_acceptGuardian_Test is UnitBase { using ModuleKitHelpers for *; using ModuleKitUserOp for *; + using Strings for uint256; - bytes[] subjectParams; - bytes32 nullifier; + bytes[] public commandParams; + bytes32 public nullifier; function setUp() public override { super.setUp(); - subjectParams = new bytes[](1); - subjectParams[0] = abi.encode(accountAddress); + if (getCommandHandlerType() == CommandHandlerType.AccountHidingRecoveryCommandHandler) { + commandParams = new bytes[](1); + commandParams[0] = + abi.encode(uint256(keccak256(abi.encodePacked(accountAddress1))).toHexString(32)); + } else { + commandParams = new bytes[](1); + commandParams[0] = abi.encode(accountAddress1); + } + nullifier = keccak256(abi.encode("nullifier 1")); } + function test_AcceptGuardian_RevertWhen_KillSwitchEnabled() public { + vm.prank(killSwitchAuthorizer); + emailRecoveryModule.toggleKillSwitch(); + vm.stopPrank(); + + vm.expectRevert(IGuardianManager.KillSwitchEnabled.selector); + emailRecoveryModule.exposed_acceptGuardian( + guardians1[0], templateIdx, commandParams, nullifier + ); + } + function test_AcceptGuardian_RevertWhen_AlreadyRecovering() public { - acceptGuardian(accountSalt1); - acceptGuardian(accountSalt2); + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); vm.warp(12 seconds); - handleRecovery(recoveryDataHash, accountSalt1); + handleRecovery(accountAddress1, guardians1[0], recoveryDataHash, emailRecoveryModuleAddress); vm.expectRevert(IGuardianManager.RecoveryInProcess.selector); - emailRecoveryModule.exposed_acceptGuardian(guardian1, templateIdx, subjectParams, nullifier); + emailRecoveryModule.exposed_acceptGuardian( + guardians1[0], templateIdx, commandParams, nullifier + ); } function test_AcceptGuardian_RevertWhen_RecoveryModuleNotInstalled() public { - vm.prank(accountAddress); - instance.uninstallModule(MODULE_TYPE_EXECUTOR, recoveryModuleAddress, ""); - vm.stopPrank(); + instance1.uninstallModule(MODULE_TYPE_EXECUTOR, emailRecoveryModuleAddress, ""); vm.expectRevert(IEmailRecoveryManager.RecoveryIsNotActivated.selector); - emailRecoveryModule.exposed_acceptGuardian(guardian1, templateIdx, subjectParams, nullifier); + emailRecoveryModule.exposed_acceptGuardian( + guardians1[0], templateIdx, commandParams, nullifier + ); } function test_AcceptGuardian_RevertWhen_GuardianStatusIsNONE() public { @@ -55,12 +76,14 @@ contract EmailRecoveryManager_acceptGuardian_Test is UnitBase { ) ); emailRecoveryModule.exposed_acceptGuardian( - invalidGuardian, templateIdx, subjectParams, nullifier + invalidGuardian, templateIdx, commandParams, nullifier ); } function test_AcceptGuardian_RevertWhen_GuardianStatusIsACCEPTED() public { - emailRecoveryModule.exposed_acceptGuardian(guardian1, templateIdx, subjectParams, nullifier); + emailRecoveryModule.exposed_acceptGuardian( + guardians1[0], templateIdx, commandParams, nullifier + ); vm.expectRevert( abi.encodeWithSelector( @@ -69,21 +92,25 @@ contract EmailRecoveryManager_acceptGuardian_Test is UnitBase { uint256(GuardianStatus.REQUESTED) ) ); - emailRecoveryModule.exposed_acceptGuardian(guardian1, templateIdx, subjectParams, nullifier); + emailRecoveryModule.exposed_acceptGuardian( + guardians1[0], templateIdx, commandParams, nullifier + ); } function test_AcceptGuardian_Succeeds() public { vm.expectEmit(); - emit IEmailRecoveryManager.GuardianAccepted(accountAddress, guardian1); - emailRecoveryModule.exposed_acceptGuardian(guardian1, templateIdx, subjectParams, nullifier); + emit IEmailRecoveryManager.GuardianAccepted(accountAddress1, guardians1[0]); + emailRecoveryModule.exposed_acceptGuardian( + guardians1[0], templateIdx, commandParams, nullifier + ); GuardianStorage memory guardianStorage = - emailRecoveryModule.getGuardian(accountAddress, guardian1); + emailRecoveryModule.getGuardian(accountAddress1, guardians1[0]); assertEq(uint256(guardianStorage.status), uint256(GuardianStatus.ACCEPTED)); assertEq(guardianStorage.weight, uint256(1)); IGuardianManager.GuardianConfig memory guardianConfig = - emailRecoveryModule.getGuardianConfig(accountAddress); + emailRecoveryModule.getGuardianConfig(accountAddress1); assertEq(guardianConfig.acceptedWeight, guardianStorage.weight); } } diff --git a/test/unit/EmailRecoveryManager/acceptanceSubjectTemplates.t.sol b/test/unit/EmailRecoveryManager/acceptanceCommandTemplates.t.sol similarity index 65% rename from test/unit/EmailRecoveryManager/acceptanceSubjectTemplates.t.sol rename to test/unit/EmailRecoveryManager/acceptanceCommandTemplates.t.sol index a3115ee3..e35d662e 100644 --- a/test/unit/EmailRecoveryManager/acceptanceSubjectTemplates.t.sol +++ b/test/unit/EmailRecoveryManager/acceptanceCommandTemplates.t.sol @@ -1,16 +1,17 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; import { UnitBase } from "../UnitBase.t.sol"; +import { CommandHandlerType } from "../../Base.t.sol"; -contract EmailRecoveryManager_acceptanceSubjectTemplates_Test is UnitBase { +contract EmailRecoveryManager_acceptanceCommandTemplates_Test is UnitBase { function setUp() public override { super.setUp(); } - function test_AcceptanceSubjectTemplates_Succeeds() public view { - string[][] memory templates = emailRecoveryModule.acceptanceSubjectTemplates(); + function test_AcceptanceCommandTemplates_Succeeds() public { + skipIfNotCommandHandlerType(CommandHandlerType.EmailRecoveryCommandHandler); + string[][] memory templates = emailRecoveryModule.acceptanceCommandTemplates(); assertEq(templates.length, 1); assertEq(templates[0].length, 5); diff --git a/test/unit/EmailRecoveryManager/cancelExpiredRecovery.t.sol b/test/unit/EmailRecoveryManager/cancelExpiredRecovery.t.sol index 6a156fa8..1d8063e9 100644 --- a/test/unit/EmailRecoveryManager/cancelExpiredRecovery.t.sol +++ b/test/unit/EmailRecoveryManager/cancelExpiredRecovery.t.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; import { UnitBase } from "../UnitBase.t.sol"; +import { IGuardianManager } from "src/interfaces/IGuardianManager.sol"; import { IEmailRecoveryManager } from "src/interfaces/IEmailRecoveryManager.sol"; contract EmailRecoveryManager_cancelExpiredRecovery_Test is UnitBase { @@ -13,172 +13,298 @@ contract EmailRecoveryManager_cancelExpiredRecovery_Test is UnitBase { super.setUp(); } + function test_CancelExpiredRecovery_RevertWhen_KillSwitchEnabled() public { + vm.prank(killSwitchAuthorizer); + emailRecoveryModule.toggleKillSwitch(); + vm.stopPrank(); + + vm.expectRevert(IGuardianManager.KillSwitchEnabled.selector); + emailRecoveryModule.cancelExpiredRecovery(accountAddress1); + } + function test_CancelExpiredRecovery_RevertWhen_NoRecoveryInProcess() public { - vm.startPrank(accountAddress); + vm.startPrank(accountAddress1); vm.expectRevert(IEmailRecoveryManager.NoRecoveryInProcess.selector); - emailRecoveryModule.cancelExpiredRecovery(accountAddress); + emailRecoveryModule.cancelExpiredRecovery(accountAddress1); } function test_CancelExpiredRecovery_CannotCancelNotStartedRecoveryRequest() public { address otherAddress = address(99); - acceptGuardian(accountSalt1); - acceptGuardian(accountSalt2); + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); vm.warp(12 seconds); - IEmailRecoveryManager.RecoveryRequest memory recoveryRequest = - emailRecoveryModule.getRecoveryRequest(accountAddress); - assertEq(recoveryRequest.executeAfter, 0); - assertEq(recoveryRequest.executeBefore, 0); - assertEq(recoveryRequest.currentWeight, 0); - assertEq(recoveryRequest.recoveryDataHash, bytes32(0)); + ( + uint256 executeAfter, + uint256 executeBefore, + uint256 currentWeight, + bytes32 _recoveryDataHash + ) = emailRecoveryModule.getRecoveryRequest(accountAddress1); + IEmailRecoveryManager.PreviousRecoveryRequest memory previousRecoveryRequest = + emailRecoveryModule.getPreviousRecoveryRequest(accountAddress1); + bool hasGuardian1Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[0]); + bool hasGuardian2Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[1]); + assertEq(executeAfter, 0); + assertEq(executeBefore, 0); + assertEq(currentWeight, 0); + assertEq(_recoveryDataHash, bytes32(0)); + assertEq(previousRecoveryRequest.previousGuardianInitiated, address(0)); + assertEq(previousRecoveryRequest.cancelRecoveryCooldown, 0); + assertEq(hasGuardian1Voted, false); + assertEq(hasGuardian2Voted, false); vm.startPrank(otherAddress); vm.expectRevert(IEmailRecoveryManager.NoRecoveryInProcess.selector); - emailRecoveryModule.cancelExpiredRecovery(accountAddress); + emailRecoveryModule.cancelExpiredRecovery(accountAddress1); } function test_CancelExpiredRecovery_RevertWhen_PartialRequest_ExpiryNotPassed() public { - acceptGuardian(accountSalt1); - acceptGuardian(accountSalt2); + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); vm.warp(12 seconds + 1 seconds); - handleRecovery(recoveryDataHash, accountSalt1); + handleRecovery(accountAddress1, guardians1[0], recoveryDataHash, emailRecoveryModuleAddress); - IEmailRecoveryManager.RecoveryRequest memory recoveryRequest = - emailRecoveryModule.getRecoveryRequest(accountAddress); - assertEq(recoveryRequest.executeAfter, 0); - assertEq(recoveryRequest.executeBefore, block.timestamp + expiry); - assertEq(recoveryRequest.currentWeight, 1); - assertEq(recoveryRequest.recoveryDataHash, recoveryDataHash); + ( + uint256 executeAfter, + uint256 executeBefore, + uint256 currentWeight, + bytes32 _recoveryDataHash + ) = emailRecoveryModule.getRecoveryRequest(accountAddress1); + IEmailRecoveryManager.PreviousRecoveryRequest memory previousRecoveryRequest = + emailRecoveryModule.getPreviousRecoveryRequest(accountAddress1); + bool hasGuardian1Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[0]); + bool hasGuardian2Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[1]); + assertEq(executeAfter, 0); + assertEq(executeBefore, block.timestamp + expiry); + assertEq(currentWeight, 1); + assertEq(_recoveryDataHash, recoveryDataHash); + assertEq(previousRecoveryRequest.previousGuardianInitiated, guardians1[0]); + assertEq(previousRecoveryRequest.cancelRecoveryCooldown, 0); + assertEq(hasGuardian1Voted, true); + assertEq(hasGuardian2Voted, false); address otherAddress = address(99); vm.startPrank(otherAddress); vm.expectRevert( abi.encodeWithSelector( IEmailRecoveryManager.RecoveryHasNotExpired.selector, - accountAddress, + accountAddress1, block.timestamp, block.timestamp + expiry ) ); - emailRecoveryModule.cancelExpiredRecovery(accountAddress); + emailRecoveryModule.cancelExpiredRecovery(accountAddress1); } function test_CancelExpiredRecovery_RevertWhen_FullRequest_ExpiryNotPassed() public { address otherAddress = address(99); - acceptGuardian(accountSalt1); - acceptGuardian(accountSalt2); + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); vm.warp(12 seconds); - handleRecovery(recoveryDataHash, accountSalt1); - handleRecovery(recoveryDataHash, accountSalt2); + handleRecovery(accountAddress1, guardians1[0], recoveryDataHash, emailRecoveryModuleAddress); + handleRecovery(accountAddress1, guardians1[1], recoveryDataHash, emailRecoveryModuleAddress); - IEmailRecoveryManager.RecoveryRequest memory recoveryRequest = - emailRecoveryModule.getRecoveryRequest(accountAddress); - assertEq(recoveryRequest.executeAfter, block.timestamp + delay); - assertEq(recoveryRequest.executeBefore, block.timestamp + expiry); - assertEq(recoveryRequest.currentWeight, 3); - assertEq(recoveryRequest.recoveryDataHash, recoveryDataHash); + ( + uint256 executeAfter, + uint256 executeBefore, + uint256 currentWeight, + bytes32 _recoveryDataHash + ) = emailRecoveryModule.getRecoveryRequest(accountAddress1); + IEmailRecoveryManager.PreviousRecoveryRequest memory previousRecoveryRequest = + emailRecoveryModule.getPreviousRecoveryRequest(accountAddress1); + bool hasGuardian1Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[0]); + bool hasGuardian2Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[1]); + assertEq(executeAfter, block.timestamp + delay); + assertEq(executeBefore, block.timestamp + expiry); + assertEq(currentWeight, 3); + assertEq(_recoveryDataHash, recoveryDataHash); + assertEq(previousRecoveryRequest.previousGuardianInitiated, guardians1[0]); + assertEq(previousRecoveryRequest.cancelRecoveryCooldown, 0); + assertEq(hasGuardian1Voted, true); + assertEq(hasGuardian2Voted, true); // executeBefore > block.timestamp vm.startPrank(otherAddress); vm.expectRevert( abi.encodeWithSelector( IEmailRecoveryManager.RecoveryHasNotExpired.selector, - accountAddress, + accountAddress1, block.timestamp, block.timestamp + expiry ) ); - emailRecoveryModule.cancelExpiredRecovery(accountAddress); + emailRecoveryModule.cancelExpiredRecovery(accountAddress1); } function test_CancelExpiredRecovery_PartialRequest_Succeeds() public { - acceptGuardian(accountSalt1); - acceptGuardian(accountSalt2); + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); vm.warp(12 seconds + 1 seconds); - handleRecovery(recoveryDataHash, accountSalt1); + handleRecovery(accountAddress1, guardians1[0], recoveryDataHash, emailRecoveryModuleAddress); - IEmailRecoveryManager.RecoveryRequest memory recoveryRequest = - emailRecoveryModule.getRecoveryRequest(accountAddress); - assertEq(recoveryRequest.executeAfter, 0); - assertEq(recoveryRequest.executeBefore, block.timestamp + expiry); - assertEq(recoveryRequest.currentWeight, 1); - assertEq(recoveryRequest.recoveryDataHash, recoveryDataHash); + ( + uint256 executeAfter, + uint256 executeBefore, + uint256 currentWeight, + bytes32 _recoveryDataHash + ) = emailRecoveryModule.getRecoveryRequest(accountAddress1); + IEmailRecoveryManager.PreviousRecoveryRequest memory previousRecoveryRequest = + emailRecoveryModule.getPreviousRecoveryRequest(accountAddress1); + bool hasGuardian1Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[0]); + bool hasGuardian2Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[1]); + assertEq(executeAfter, 0); + assertEq(executeBefore, block.timestamp + expiry); + assertEq(currentWeight, 1); + assertEq(_recoveryDataHash, recoveryDataHash); + assertEq(previousRecoveryRequest.previousGuardianInitiated, guardians1[0]); + assertEq(previousRecoveryRequest.cancelRecoveryCooldown, 0); + assertEq(hasGuardian1Voted, true); + assertEq(hasGuardian2Voted, false); vm.warp(block.timestamp + expiry); address otherAddress = address(99); vm.startPrank(otherAddress); vm.expectEmit(); - emit IEmailRecoveryManager.RecoveryCancelled(accountAddress); - emailRecoveryModule.cancelExpiredRecovery(accountAddress); - - recoveryRequest = emailRecoveryModule.getRecoveryRequest(accountAddress); - assertEq(recoveryRequest.executeAfter, 0); - assertEq(recoveryRequest.executeBefore, 0); - assertEq(recoveryRequest.currentWeight, 0); - assertEq(recoveryRequest.recoveryDataHash, ""); + emit IEmailRecoveryManager.RecoveryCancelled(accountAddress1); + emailRecoveryModule.cancelExpiredRecovery(accountAddress1); + + (executeAfter, executeBefore, currentWeight, recoveryDataHash) = + emailRecoveryModule.getRecoveryRequest(accountAddress1); + previousRecoveryRequest = emailRecoveryModule.getPreviousRecoveryRequest(accountAddress1); + hasGuardian1Voted = emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[0]); + hasGuardian2Voted = emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[1]); + assertEq(executeAfter, 0); + assertEq(executeBefore, 0); + assertEq(currentWeight, 0); + assertEq(recoveryDataHash, ""); + assertEq(previousRecoveryRequest.previousGuardianInitiated, guardians1[0]); + assertEq( + previousRecoveryRequest.cancelRecoveryCooldown, + block.timestamp + emailRecoveryModule.CANCEL_EXPIRED_RECOVERY_COOLDOWN() + ); + assertEq(hasGuardian1Voted, false); + assertEq(hasGuardian2Voted, false); } function test_CancelExpiredRecovery_FullRequest_SucceedsWhenExecuteBeforeEqualsTimestamp() public { - acceptGuardian(accountSalt1); - acceptGuardian(accountSalt2); + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); vm.warp(12 seconds + 1 seconds); - handleRecovery(recoveryDataHash, accountSalt1); - handleRecovery(recoveryDataHash, accountSalt2); + handleRecovery(accountAddress1, guardians1[0], recoveryDataHash, emailRecoveryModuleAddress); + handleRecovery(accountAddress1, guardians1[1], recoveryDataHash, emailRecoveryModuleAddress); - IEmailRecoveryManager.RecoveryRequest memory recoveryRequest = - emailRecoveryModule.getRecoveryRequest(accountAddress); - assertEq(recoveryRequest.executeAfter, block.timestamp + delay); - assertEq(recoveryRequest.executeBefore, block.timestamp + expiry); - assertEq(recoveryRequest.currentWeight, 3); - assertEq(recoveryRequest.recoveryDataHash, recoveryDataHash); + ( + uint256 executeAfter, + uint256 executeBefore, + uint256 currentWeight, + bytes32 _recoveryDataHash + ) = emailRecoveryModule.getRecoveryRequest(accountAddress1); + IEmailRecoveryManager.PreviousRecoveryRequest memory previousRecoveryRequest = + emailRecoveryModule.getPreviousRecoveryRequest(accountAddress1); + bool hasGuardian1Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[0]); + bool hasGuardian2Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[1]); + assertEq(executeAfter, block.timestamp + delay); + assertEq(executeBefore, block.timestamp + expiry); + assertEq(currentWeight, 3); + assertEq(_recoveryDataHash, recoveryDataHash); + assertEq(previousRecoveryRequest.previousGuardianInitiated, guardians1[0]); + assertEq(previousRecoveryRequest.cancelRecoveryCooldown, 0); + assertEq(hasGuardian1Voted, true); + assertEq(hasGuardian2Voted, true); // executeBefore == block.timestamp vm.warp(block.timestamp + expiry); address otherAddress = address(99); vm.startPrank(otherAddress); vm.expectEmit(); - emit IEmailRecoveryManager.RecoveryCancelled(accountAddress); - emailRecoveryModule.cancelExpiredRecovery(accountAddress); - - recoveryRequest = emailRecoveryModule.getRecoveryRequest(accountAddress); - assertEq(recoveryRequest.executeAfter, 0); - assertEq(recoveryRequest.executeBefore, 0); - assertEq(recoveryRequest.currentWeight, 0); - assertEq(recoveryRequest.recoveryDataHash, ""); + emit IEmailRecoveryManager.RecoveryCancelled(accountAddress1); + emailRecoveryModule.cancelExpiredRecovery(accountAddress1); + + (executeAfter, executeBefore, currentWeight, _recoveryDataHash) = + emailRecoveryModule.getRecoveryRequest(accountAddress1); + previousRecoveryRequest = emailRecoveryModule.getPreviousRecoveryRequest(accountAddress1); + hasGuardian1Voted = emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[0]); + hasGuardian2Voted = emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[1]); + assertEq(executeAfter, 0); + assertEq(executeBefore, 0); + assertEq(currentWeight, 0); + assertEq(_recoveryDataHash, ""); + assertEq(previousRecoveryRequest.previousGuardianInitiated, guardians1[0]); + assertEq( + previousRecoveryRequest.cancelRecoveryCooldown, + block.timestamp + emailRecoveryModule.CANCEL_EXPIRED_RECOVERY_COOLDOWN() + ); + assertEq(hasGuardian1Voted, false); + assertEq(hasGuardian2Voted, false); } function test_CancelExpiredRecovery_FullRequest_SucceedsWhenExecuteBeforeIsLessThanTimestamp() public { - acceptGuardian(accountSalt1); - acceptGuardian(accountSalt2); + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); vm.warp(12 seconds + 1 seconds); - handleRecovery(recoveryDataHash, accountSalt1); - handleRecovery(recoveryDataHash, accountSalt2); + handleRecovery(accountAddress1, guardians1[0], recoveryDataHash, emailRecoveryModuleAddress); + handleRecovery(accountAddress1, guardians1[1], recoveryDataHash, emailRecoveryModuleAddress); - IEmailRecoveryManager.RecoveryRequest memory recoveryRequest = - emailRecoveryModule.getRecoveryRequest(accountAddress); - assertEq(recoveryRequest.executeAfter, block.timestamp + delay); - assertEq(recoveryRequest.executeBefore, block.timestamp + expiry); - assertEq(recoveryRequest.currentWeight, 3); - assertEq(recoveryRequest.recoveryDataHash, recoveryDataHash); + ( + uint256 executeAfter, + uint256 executeBefore, + uint256 currentWeight, + bytes32 _recoveryDataHash + ) = emailRecoveryModule.getRecoveryRequest(accountAddress1); + IEmailRecoveryManager.PreviousRecoveryRequest memory previousRecoveryRequest = + emailRecoveryModule.getPreviousRecoveryRequest(accountAddress1); + bool hasGuardian1Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[0]); + bool hasGuardian2Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[1]); + assertEq(executeAfter, block.timestamp + delay); + assertEq(executeBefore, block.timestamp + expiry); + assertEq(currentWeight, 3); + assertEq(_recoveryDataHash, recoveryDataHash); + assertEq(previousRecoveryRequest.previousGuardianInitiated, guardians1[0]); + assertEq(previousRecoveryRequest.cancelRecoveryCooldown, 0); + assertEq(hasGuardian1Voted, true); + assertEq(hasGuardian2Voted, true); // executeBefore < block.timestamp vm.warp(block.timestamp + expiry + 1 seconds); address otherAddress = address(99); vm.startPrank(otherAddress); vm.expectEmit(); - emit IEmailRecoveryManager.RecoveryCancelled(accountAddress); - emailRecoveryModule.cancelExpiredRecovery(accountAddress); - - recoveryRequest = emailRecoveryModule.getRecoveryRequest(accountAddress); - assertEq(recoveryRequest.executeAfter, 0); - assertEq(recoveryRequest.executeBefore, 0); - assertEq(recoveryRequest.currentWeight, 0); - assertEq(recoveryRequest.recoveryDataHash, ""); + emit IEmailRecoveryManager.RecoveryCancelled(accountAddress1); + emailRecoveryModule.cancelExpiredRecovery(accountAddress1); + + (executeAfter, executeBefore, currentWeight, _recoveryDataHash) = + emailRecoveryModule.getRecoveryRequest(accountAddress1); + previousRecoveryRequest = emailRecoveryModule.getPreviousRecoveryRequest(accountAddress1); + hasGuardian1Voted = emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[0]); + hasGuardian2Voted = emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[1]); + assertEq(executeAfter, 0); + assertEq(executeBefore, 0); + assertEq(currentWeight, 0); + assertEq(_recoveryDataHash, ""); + assertEq(previousRecoveryRequest.previousGuardianInitiated, guardians1[0]); + assertEq( + previousRecoveryRequest.cancelRecoveryCooldown, + block.timestamp + emailRecoveryModule.CANCEL_EXPIRED_RECOVERY_COOLDOWN() + ); + assertEq(hasGuardian1Voted, false); + assertEq(hasGuardian2Voted, false); } } diff --git a/test/unit/EmailRecoveryManager/cancelRecovery.t.sol b/test/unit/EmailRecoveryManager/cancelRecovery.t.sol index d6661e2b..a5f3dd40 100644 --- a/test/unit/EmailRecoveryManager/cancelRecovery.t.sol +++ b/test/unit/EmailRecoveryManager/cancelRecovery.t.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; import { UnitBase } from "../UnitBase.t.sol"; +import { IGuardianManager } from "src/interfaces/IGuardianManager.sol"; import { IEmailRecoveryManager } from "src/interfaces/IEmailRecoveryManager.sol"; contract EmailRecoveryManager_cancelRecovery_Test is UnitBase { @@ -13,8 +13,17 @@ contract EmailRecoveryManager_cancelRecovery_Test is UnitBase { super.setUp(); } + function test_CancelRecovery_RevertWhen_KillSwitchEnabled() public { + vm.prank(killSwitchAuthorizer); + emailRecoveryModule.toggleKillSwitch(); + vm.stopPrank(); + + vm.expectRevert(IGuardianManager.KillSwitchEnabled.selector); + emailRecoveryModule.cancelRecovery(); + } + function test_CancelRecovery_RevertWhen_NoRecoveryInProcess() public { - vm.startPrank(accountAddress); + vm.startPrank(accountAddress1); vm.expectRevert(IEmailRecoveryManager.NoRecoveryInProcess.selector); emailRecoveryModule.cancelRecovery(); } @@ -22,17 +31,31 @@ contract EmailRecoveryManager_cancelRecovery_Test is UnitBase { function test_CancelRecovery_CannotCancelWrongRecoveryRequest() public { address otherAddress = address(99); - acceptGuardian(accountSalt1); - acceptGuardian(accountSalt2); + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); vm.warp(12 seconds); - handleRecovery(recoveryDataHash, accountSalt1); - - IEmailRecoveryManager.RecoveryRequest memory recoveryRequest = - emailRecoveryModule.getRecoveryRequest(accountAddress); - assertEq(recoveryRequest.executeAfter, 0); - assertEq(recoveryRequest.executeBefore, block.timestamp + expiry); - assertEq(recoveryRequest.currentWeight, 1); - assertEq(recoveryRequest.recoveryDataHash, recoveryDataHash); + handleRecovery(accountAddress1, guardians1[0], recoveryDataHash, emailRecoveryModuleAddress); + + ( + uint256 executeAfter, + uint256 executeBefore, + uint256 currentWeight, + bytes32 _recoveryDataHash + ) = emailRecoveryModule.getRecoveryRequest(accountAddress1); + IEmailRecoveryManager.PreviousRecoveryRequest memory previousRecoveryRequest = + emailRecoveryModule.getPreviousRecoveryRequest(accountAddress1); + bool hasGuardian1Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[0]); + bool hasGuardian2Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[1]); + assertEq(executeAfter, 0); + assertEq(executeBefore, block.timestamp + expiry); + assertEq(currentWeight, 1); + assertEq(_recoveryDataHash, recoveryDataHash); + assertEq(previousRecoveryRequest.previousGuardianInitiated, guardians1[0]); + assertEq(previousRecoveryRequest.cancelRecoveryCooldown, 0); + assertEq(hasGuardian1Voted, true); + assertEq(hasGuardian2Voted, false); vm.startPrank(otherAddress); vm.expectRevert(IEmailRecoveryManager.NoRecoveryInProcess.selector); @@ -40,51 +63,95 @@ contract EmailRecoveryManager_cancelRecovery_Test is UnitBase { } function test_CancelRecovery_PartialRequest_Succeeds() public { - acceptGuardian(accountSalt1); - acceptGuardian(accountSalt2); + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); vm.warp(12 seconds); - handleRecovery(recoveryDataHash, accountSalt1); - - IEmailRecoveryManager.RecoveryRequest memory recoveryRequest = - emailRecoveryModule.getRecoveryRequest(accountAddress); - assertEq(recoveryRequest.executeAfter, 0); - assertEq(recoveryRequest.executeBefore, block.timestamp + expiry); - assertEq(recoveryRequest.currentWeight, 1); - assertEq(recoveryRequest.recoveryDataHash, recoveryDataHash); - - vm.startPrank(accountAddress); + handleRecovery(accountAddress1, guardians1[0], recoveryDataHash, emailRecoveryModuleAddress); + + ( + uint256 executeAfter, + uint256 executeBefore, + uint256 currentWeight, + bytes32 _recoveryDataHash + ) = emailRecoveryModule.getRecoveryRequest(accountAddress1); + IEmailRecoveryManager.PreviousRecoveryRequest memory previousRecoveryRequest = + emailRecoveryModule.getPreviousRecoveryRequest(accountAddress1); + bool hasGuardian1Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[0]); + bool hasGuardian2Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[1]); + assertEq(executeAfter, 0); + assertEq(executeBefore, block.timestamp + expiry); + assertEq(currentWeight, 1); + assertEq(_recoveryDataHash, recoveryDataHash); + assertEq(previousRecoveryRequest.previousGuardianInitiated, guardians1[0]); + assertEq(previousRecoveryRequest.cancelRecoveryCooldown, 0); + assertEq(hasGuardian1Voted, true); + assertEq(hasGuardian2Voted, false); + + vm.startPrank(accountAddress1); emailRecoveryModule.cancelRecovery(); - recoveryRequest = emailRecoveryModule.getRecoveryRequest(accountAddress); - assertEq(recoveryRequest.executeAfter, 0); - assertEq(recoveryRequest.executeBefore, 0); - assertEq(recoveryRequest.currentWeight, 0); - assertEq(recoveryRequest.recoveryDataHash, ""); + (executeAfter, executeBefore, currentWeight, _recoveryDataHash) = + emailRecoveryModule.getRecoveryRequest(accountAddress1); + previousRecoveryRequest = emailRecoveryModule.getPreviousRecoveryRequest(accountAddress1); + hasGuardian1Voted = emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[0]); + hasGuardian2Voted = emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[1]); + assertEq(executeAfter, 0); + assertEq(executeBefore, 0); + assertEq(currentWeight, 0); + assertEq(_recoveryDataHash, ""); + assertEq(previousRecoveryRequest.previousGuardianInitiated, guardians1[0]); + assertEq(previousRecoveryRequest.cancelRecoveryCooldown, 0); + assertEq(hasGuardian1Voted, false); + assertEq(hasGuardian2Voted, false); } function test_CancelRecovery_FullRequest_Succeeds() public { - acceptGuardian(accountSalt1); - acceptGuardian(accountSalt2); + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); vm.warp(12 seconds); - handleRecovery(recoveryDataHash, accountSalt1); - handleRecovery(recoveryDataHash, accountSalt2); - - IEmailRecoveryManager.RecoveryRequest memory recoveryRequest = - emailRecoveryModule.getRecoveryRequest(accountAddress); - assertEq(recoveryRequest.executeAfter, block.timestamp + delay); - assertEq(recoveryRequest.executeBefore, block.timestamp + expiry); - assertEq(recoveryRequest.currentWeight, 3); - assertEq(recoveryRequest.recoveryDataHash, recoveryDataHash); - - vm.startPrank(accountAddress); + handleRecovery(accountAddress1, guardians1[0], recoveryDataHash, emailRecoveryModuleAddress); + handleRecovery(accountAddress1, guardians1[1], recoveryDataHash, emailRecoveryModuleAddress); + + ( + uint256 executeAfter, + uint256 executeBefore, + uint256 currentWeight, + bytes32 _recoveryDataHash + ) = emailRecoveryModule.getRecoveryRequest(accountAddress1); + IEmailRecoveryManager.PreviousRecoveryRequest memory previousRecoveryRequest = + emailRecoveryModule.getPreviousRecoveryRequest(accountAddress1); + bool hasGuardian1Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[0]); + bool hasGuardian2Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[1]); + assertEq(executeAfter, block.timestamp + delay); + assertEq(executeBefore, block.timestamp + expiry); + assertEq(currentWeight, 3); + assertEq(_recoveryDataHash, recoveryDataHash); + assertEq(previousRecoveryRequest.previousGuardianInitiated, guardians1[0]); + assertEq(previousRecoveryRequest.cancelRecoveryCooldown, 0); + assertEq(hasGuardian1Voted, true); + assertEq(hasGuardian2Voted, true); + + vm.startPrank(accountAddress1); vm.expectEmit(); - emit IEmailRecoveryManager.RecoveryCancelled(accountAddress); + emit IEmailRecoveryManager.RecoveryCancelled(accountAddress1); emailRecoveryModule.cancelRecovery(); - recoveryRequest = emailRecoveryModule.getRecoveryRequest(accountAddress); - assertEq(recoveryRequest.executeAfter, 0); - assertEq(recoveryRequest.executeBefore, 0); - assertEq(recoveryRequest.currentWeight, 0); - assertEq(recoveryRequest.recoveryDataHash, ""); + (executeAfter, executeBefore, currentWeight, _recoveryDataHash) = + emailRecoveryModule.getRecoveryRequest(accountAddress1); + previousRecoveryRequest = emailRecoveryModule.getPreviousRecoveryRequest(accountAddress1); + hasGuardian1Voted = emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[0]); + hasGuardian2Voted = emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[1]); + assertEq(executeAfter, 0); + assertEq(executeBefore, 0); + assertEq(currentWeight, 0); + assertEq(_recoveryDataHash, ""); + assertEq(previousRecoveryRequest.previousGuardianInitiated, guardians1[0]); + assertEq(previousRecoveryRequest.cancelRecoveryCooldown, 0); + assertEq(hasGuardian1Voted, false); + assertEq(hasGuardian2Voted, false); } } diff --git a/test/unit/EmailRecoveryManager/clearRecoveryRequest.t.sol b/test/unit/EmailRecoveryManager/clearRecoveryRequest.t.sol new file mode 100644 index 00000000..ac9789ee --- /dev/null +++ b/test/unit/EmailRecoveryManager/clearRecoveryRequest.t.sol @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { UnitBase } from "../UnitBase.t.sol"; +import { IEmailRecoveryManager } from "src/interfaces/IEmailRecoveryManager.sol"; + +contract EmailRecoveryManager_clearRecoveryRequest_Test is UnitBase { + function setUp() public override { + super.setUp(); + } + + function test_ClearRecoveryRequest_DoesNotRevertWhenInvalidAccount() public { + emailRecoveryModule.exposed_clearRecoveryRequest(address(0)); + emailRecoveryModule.exposed_clearRecoveryRequest(address(1)); + } + + function test_ClearRecoveryRequest_Succeeds() public { + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); + vm.warp(12 seconds); + handleRecovery(accountAddress1, guardians1[0], recoveryDataHash, emailRecoveryModuleAddress); + handleRecovery(accountAddress1, guardians1[1], recoveryDataHash, emailRecoveryModuleAddress); + + ( + uint256 executeAfter, + uint256 executeBefore, + uint256 currentWeight, + bytes32 _recoveryDataHash + ) = emailRecoveryModule.getRecoveryRequest(accountAddress1); + IEmailRecoveryManager.PreviousRecoveryRequest memory previousRecoveryRequest = + emailRecoveryModule.getPreviousRecoveryRequest(accountAddress1); + bool hasGuardian1Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[0]); + bool hasGuardian2Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[1]); + assertEq(previousRecoveryRequest.previousGuardianInitiated, guardians1[0]); + assertEq(previousRecoveryRequest.cancelRecoveryCooldown, 0); + assertEq(executeAfter, block.timestamp + delay); + assertEq(executeBefore, block.timestamp + expiry); + assertEq(currentWeight, 3); + assertEq(_recoveryDataHash, recoveryDataHash); + assertEq(hasGuardian1Voted, true); + assertEq(hasGuardian2Voted, true); + + emailRecoveryModule.exposed_clearRecoveryRequest(accountAddress1); + + // assert that the recovery request has been cleared successfully + (executeAfter, executeBefore, currentWeight, _recoveryDataHash) = + emailRecoveryModule.getRecoveryRequest(accountAddress1); + hasGuardian1Voted = emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[0]); + hasGuardian2Voted = emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[1]); + assertEq(executeAfter, 0); + assertEq(executeBefore, 0); + assertEq(currentWeight, 0); + assertEq(_recoveryDataHash, ""); + assertEq(previousRecoveryRequest.previousGuardianInitiated, guardians1[0]); + assertEq(previousRecoveryRequest.cancelRecoveryCooldown, 0); + assertEq(hasGuardian1Voted, false); + assertEq(hasGuardian2Voted, false); + + uint256 voteCount = emailRecoveryModule.workaround_getVoteCount(accountAddress1); + assertEq(voteCount, 0); + } + + function test_ClearRecoveryRequest_SucceedsWithMaxGuardians() public { + // There are already 3 guardians configured, and the maximum number of guardians is 32 + address[] memory guardians = new address[](29); + + for (uint256 i = 0; i < 29; i++) { + guardians[i] = computeEmailAuthAddress(instance1.account, keccak256(abi.encode(i))); + } + + // The total number of guardians is now 32, which is the maximum number + vm.startPrank(accountAddress1); + for (uint256 i = 0; i < 29; i++) { + emailRecoveryModule.addGuardian(guardians[i], 1); + } + vm.stopPrank(); + + // first 3 guardians that are already configured + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[2], emailRecoveryModuleAddress); + + // Next 29 guardians that have just been created + for (uint256 i = 0; i < 29; i++) { + acceptGuardianWithAccountSalt( + accountAddress1, guardians[i], emailRecoveryModuleAddress, keccak256(abi.encode(i)) + ); + } + vm.warp(block.timestamp + 12 seconds); + + // first 3 guardians that are already configured + vm.warp(block.timestamp + 12 seconds); + handleRecovery(accountAddress1, guardians1[0], recoveryDataHash, emailRecoveryModuleAddress); + handleRecovery(accountAddress1, guardians1[1], recoveryDataHash, emailRecoveryModuleAddress); + handleRecovery(accountAddress1, guardians1[2], recoveryDataHash, emailRecoveryModuleAddress); + + // Next 29 guardians that have just been created + for (uint256 i = 0; i < 29; i++) { + handleRecoveryWithAccountSalt( + accountAddress1, + guardians[i], + recoveryDataHash, + emailRecoveryModuleAddress, + keccak256(abi.encode(i)) + ); + } + + uint256 guardianCount = emailRecoveryModule.getGuardianConfig(accountAddress1).guardianCount; + assertEq(guardianCount, 32); + + ( + uint256 executeAfter, + uint256 executeBefore, + uint256 currentWeight, + bytes32 _recoveryDataHash + ) = emailRecoveryModule.getRecoveryRequest(accountAddress1); + IEmailRecoveryManager.PreviousRecoveryRequest memory previousRecoveryRequest = + emailRecoveryModule.getPreviousRecoveryRequest(accountAddress1); + bool hasGuardian1Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[0]); + bool hasGuardian2Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[1]); + bool hasGuardian3Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[2]); + assertEq(executeAfter, block.timestamp + delay); + assertEq(executeBefore, block.timestamp + expiry); + assertEq(currentWeight, 33); // pre-configured guardian 2 has a weight of 2, so the total is + // one more than 32 + assertEq(_recoveryDataHash, recoveryDataHash); + assertEq(previousRecoveryRequest.previousGuardianInitiated, guardians1[0]); + assertEq(previousRecoveryRequest.cancelRecoveryCooldown, 0); + assertTrue(hasGuardian1Voted); + assertTrue(hasGuardian2Voted); + assertTrue(hasGuardian3Voted); + for (uint256 i = 0; i < 29; i++) { + assertTrue(emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians[i])); + } + + emailRecoveryModule.exposed_clearRecoveryRequest(accountAddress1); + + // assert that the recovery request has been cleared successfully + (executeAfter, executeBefore, currentWeight, _recoveryDataHash) = + emailRecoveryModule.getRecoveryRequest(accountAddress1); + previousRecoveryRequest = emailRecoveryModule.getPreviousRecoveryRequest(accountAddress1); + hasGuardian1Voted = emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[0]); + hasGuardian2Voted = emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[1]); + hasGuardian3Voted = emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[2]); + assertEq(executeAfter, 0); + assertEq(executeBefore, 0); + assertEq(currentWeight, 0); + assertEq(_recoveryDataHash, ""); + assertEq(previousRecoveryRequest.previousGuardianInitiated, guardians1[0]); + assertEq(previousRecoveryRequest.cancelRecoveryCooldown, 0); + assertFalse(hasGuardian1Voted); + assertFalse(hasGuardian2Voted); + assertFalse(hasGuardian3Voted); + for (uint256 i = 0; i < 29; i++) { + assertFalse(emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians[i])); + } + + uint256 voteCount = emailRecoveryModule.workaround_getVoteCount(accountAddress1); + assertEq(voteCount, 0); + } +} diff --git a/test/unit/EmailRecoveryManager/completeRecovery.t.sol b/test/unit/EmailRecoveryManager/completeRecovery.t.sol index 9bb32d52..e18d98df 100644 --- a/test/unit/EmailRecoveryManager/completeRecovery.t.sol +++ b/test/unit/EmailRecoveryManager/completeRecovery.t.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; import { UnitBase } from "../UnitBase.t.sol"; +import { IGuardianManager } from "src/interfaces/IGuardianManager.sol"; import { IEmailRecoveryManager } from "src/interfaces/IEmailRecoveryManager.sol"; contract EmailRecoveryManager_completeRecovery_Test is UnitBase { @@ -10,6 +10,15 @@ contract EmailRecoveryManager_completeRecovery_Test is UnitBase { super.setUp(); } + function test_CompleteRecovery_RevertWhen_KillSwitchEnabled() public { + vm.prank(killSwitchAuthorizer); + emailRecoveryModule.toggleKillSwitch(); + vm.stopPrank(); + + vm.expectRevert(IGuardianManager.KillSwitchEnabled.selector); + emailRecoveryModule.completeRecovery(accountAddress1, recoveryData); + } + function test_CompleteRecovery_RevertWhen_InvalidAccountAddress() public { address invalidAccount = address(0); @@ -18,10 +27,10 @@ contract EmailRecoveryManager_completeRecovery_Test is UnitBase { } function test_CompleteRecovery_RevertWhen_NotEnoughApprovals() public { - acceptGuardian(accountSalt1); - acceptGuardian(accountSalt2); + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); vm.warp(12 seconds); - handleRecovery(recoveryDataHash, accountSalt1); + handleRecovery(accountAddress1, guardians1[0], recoveryDataHash, emailRecoveryModuleAddress); // only one guardian added and one approval vm.expectRevert( @@ -29,37 +38,37 @@ contract EmailRecoveryManager_completeRecovery_Test is UnitBase { IEmailRecoveryManager.NotEnoughApprovals.selector, guardianWeights[0], threshold ) ); - emailRecoveryModule.completeRecovery(accountAddress, recoveryData); + emailRecoveryModule.completeRecovery(accountAddress1, recoveryData); } function test_CompleteRecovery_RevertWhen_DelayNotPassed() public { - acceptGuardian(accountSalt1); - acceptGuardian(accountSalt2); + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); vm.warp(12 seconds); - handleRecovery(recoveryDataHash, accountSalt1); - handleRecovery(recoveryDataHash, accountSalt2); + handleRecovery(accountAddress1, guardians1[0], recoveryDataHash, emailRecoveryModuleAddress); + handleRecovery(accountAddress1, guardians1[1], recoveryDataHash, emailRecoveryModuleAddress); + + uint256 expectedExecuteAfter = block.timestamp + delay; // one second before it should be valid vm.warp(block.timestamp + delay - 1 seconds); vm.expectRevert( abi.encodeWithSelector( - IEmailRecoveryManager.DelayNotPassed.selector, - block.timestamp, - block.timestamp + delay + IEmailRecoveryManager.DelayNotPassed.selector, block.timestamp, expectedExecuteAfter ) ); - emailRecoveryModule.completeRecovery(accountAddress, recoveryData); + emailRecoveryModule.completeRecovery(accountAddress1, recoveryData); } function test_CompleteRecovery_RevertWhen_RecoveryRequestExpiredAndTimestampEqualToExpiry() public { - acceptGuardian(accountSalt1); - acceptGuardian(accountSalt2); + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); vm.warp(12 seconds); - handleRecovery(recoveryDataHash, accountSalt1); - handleRecovery(recoveryDataHash, accountSalt2); + handleRecovery(accountAddress1, guardians1[0], recoveryDataHash, emailRecoveryModuleAddress); + handleRecovery(accountAddress1, guardians1[1], recoveryDataHash, emailRecoveryModuleAddress); uint256 executeAfter = block.timestamp + expiry; // block.timestamp == recoveryRequest.executeBefore @@ -70,17 +79,17 @@ contract EmailRecoveryManager_completeRecovery_Test is UnitBase { IEmailRecoveryManager.RecoveryRequestExpired.selector, block.timestamp, executeAfter ) ); - emailRecoveryModule.completeRecovery(accountAddress, recoveryData); + emailRecoveryModule.completeRecovery(accountAddress1, recoveryData); } function test_CompleteRecovery_RevertWhen_RecoveryRequestExpiredAndTimestampMoreThanExpiry() public { - acceptGuardian(accountSalt1); - acceptGuardian(accountSalt2); + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); vm.warp(12 seconds); - handleRecovery(recoveryDataHash, accountSalt1); - handleRecovery(recoveryDataHash, accountSalt2); + handleRecovery(accountAddress1, guardians1[0], recoveryDataHash, emailRecoveryModuleAddress); + handleRecovery(accountAddress1, guardians1[1], recoveryDataHash, emailRecoveryModuleAddress); uint256 executeAfter = block.timestamp + expiry; // block.timestamp > recoveryRequest.executeBefore @@ -91,17 +100,17 @@ contract EmailRecoveryManager_completeRecovery_Test is UnitBase { IEmailRecoveryManager.RecoveryRequestExpired.selector, block.timestamp, executeAfter ) ); - emailRecoveryModule.completeRecovery(accountAddress, recoveryData); + emailRecoveryModule.completeRecovery(accountAddress1, recoveryData); } function test_CompleteRecovery_RevertWhen_InvalidRecoveryDataHash() public { bytes memory invalidRecoveryData = bytes("Invalid calldata"); - acceptGuardian(accountSalt1); - acceptGuardian(accountSalt2); + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); vm.warp(12 seconds); - handleRecovery(recoveryDataHash, accountSalt1); - handleRecovery(recoveryDataHash, accountSalt2); + handleRecovery(accountAddress1, guardians1[0], recoveryDataHash, emailRecoveryModuleAddress); + handleRecovery(accountAddress1, guardians1[1], recoveryDataHash, emailRecoveryModuleAddress); vm.warp(block.timestamp + delay); @@ -112,48 +121,77 @@ contract EmailRecoveryManager_completeRecovery_Test is UnitBase { recoveryDataHash ) ); - emailRecoveryModule.completeRecovery(accountAddress, invalidRecoveryData); + + emailRecoveryModule.completeRecovery(accountAddress1, invalidRecoveryData); } function test_CompleteRecovery_Succeeds() public { - acceptGuardian(accountSalt1); - acceptGuardian(accountSalt2); + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); vm.warp(12 seconds); - handleRecovery(recoveryDataHash, accountSalt1); - handleRecovery(recoveryDataHash, accountSalt2); + handleRecovery(accountAddress1, guardians1[0], recoveryDataHash, emailRecoveryModuleAddress); + handleRecovery(accountAddress1, guardians1[1], recoveryDataHash, emailRecoveryModuleAddress); vm.warp(block.timestamp + delay); vm.expectEmit(); - emit IEmailRecoveryManager.RecoveryCompleted(accountAddress); - emailRecoveryModule.completeRecovery(accountAddress, recoveryData); - - IEmailRecoveryManager.RecoveryRequest memory recoveryRequest = - emailRecoveryModule.getRecoveryRequest(accountAddress); - assertEq(recoveryRequest.executeAfter, 0); - assertEq(recoveryRequest.executeBefore, 0); - assertEq(recoveryRequest.currentWeight, 0); - assertEq(recoveryRequest.recoveryDataHash, ""); + emit IEmailRecoveryManager.RecoveryCompleted(accountAddress1); + emailRecoveryModule.completeRecovery(accountAddress1, recoveryData); + + ( + uint256 executeAfter, + uint256 executeBefore, + uint256 currentWeight, + bytes32 _recoveryDataHash + ) = emailRecoveryModule.getRecoveryRequest(accountAddress1); + IEmailRecoveryManager.PreviousRecoveryRequest memory previousRecoveryRequest = + emailRecoveryModule.getPreviousRecoveryRequest(accountAddress1); + bool hasGuardian1Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[0]); + bool hasGuardian2Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[1]); + assertEq(executeAfter, 0); + assertEq(executeBefore, 0); + assertEq(currentWeight, 0); + assertEq(_recoveryDataHash, ""); + assertEq(previousRecoveryRequest.previousGuardianInitiated, guardians1[0]); + assertEq(previousRecoveryRequest.cancelRecoveryCooldown, 0); + assertEq(hasGuardian1Voted, false); + assertEq(hasGuardian2Voted, false); } function test_CompleteRecovery_SucceedsAlmostExpiry() public { - acceptGuardian(accountSalt1); - acceptGuardian(accountSalt2); + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); vm.warp(12 seconds); - handleRecovery(recoveryDataHash, accountSalt1); - handleRecovery(recoveryDataHash, accountSalt2); + handleRecovery(accountAddress1, guardians1[0], recoveryDataHash, emailRecoveryModuleAddress); + handleRecovery(accountAddress1, guardians1[1], recoveryDataHash, emailRecoveryModuleAddress); vm.warp(block.timestamp + expiry - 1 seconds); vm.expectEmit(); - emit IEmailRecoveryManager.RecoveryCompleted(accountAddress); - emailRecoveryModule.completeRecovery(accountAddress, recoveryData); - - IEmailRecoveryManager.RecoveryRequest memory recoveryRequest = - emailRecoveryModule.getRecoveryRequest(accountAddress); - assertEq(recoveryRequest.executeAfter, 0); - assertEq(recoveryRequest.executeBefore, 0); - assertEq(recoveryRequest.currentWeight, 0); - assertEq(recoveryRequest.recoveryDataHash, ""); + emit IEmailRecoveryManager.RecoveryCompleted(accountAddress1); + emailRecoveryModule.completeRecovery(accountAddress1, recoveryData); + + ( + uint256 executeAfter, + uint256 executeBefore, + uint256 currentWeight, + bytes32 _recoveryDataHash + ) = emailRecoveryModule.getRecoveryRequest(accountAddress1); + IEmailRecoveryManager.PreviousRecoveryRequest memory previousRecoveryRequest = + emailRecoveryModule.getPreviousRecoveryRequest(accountAddress1); + bool hasGuardian1Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[0]); + bool hasGuardian2Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[1]); + assertEq(executeAfter, 0); + assertEq(executeBefore, 0); + assertEq(currentWeight, 0); + assertEq(_recoveryDataHash, ""); + assertEq(previousRecoveryRequest.previousGuardianInitiated, guardians1[0]); + assertEq(previousRecoveryRequest.cancelRecoveryCooldown, 0); + assertEq(hasGuardian1Voted, false); + assertEq(hasGuardian2Voted, false); } } diff --git a/test/unit/EmailRecoveryManager/configureRecovery.t.sol b/test/unit/EmailRecoveryManager/configureRecovery.t.sol index 06a0ebe6..0990fad2 100644 --- a/test/unit/EmailRecoveryManager/configureRecovery.t.sol +++ b/test/unit/EmailRecoveryManager/configureRecovery.t.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; import { ModuleKitHelpers } from "modulekit/ModuleKit.sol"; import { MODULE_TYPE_EXECUTOR } from "modulekit/external/ERC7579.sol"; import { IEmailRecoveryManager } from "src/interfaces/IEmailRecoveryManager.sol"; @@ -16,68 +15,80 @@ contract EmailRecoveryManager_configureRecovery_Test is UnitBase { super.setUp(); } + function test_ConfigureRecovery_RevertWhen_KillSwitchEnabled() public { + vm.prank(killSwitchAuthorizer); + emailRecoveryModule.toggleKillSwitch(); + vm.stopPrank(); + + vm.startPrank(accountAddress1); + vm.expectRevert(IGuardianManager.KillSwitchEnabled.selector); + emailRecoveryModule.exposed_configureRecovery( + guardians1, guardianWeights, threshold, delay, expiry + ); + } + function test_ConfigureRecovery_RevertWhen_AlreadyRecovering() public { - acceptGuardian(accountSalt1); - acceptGuardian(accountSalt2); + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); vm.warp(12 seconds); - handleRecovery(recoveryDataHash, accountSalt1); + handleRecovery(accountAddress1, guardians1[0], recoveryDataHash, emailRecoveryModuleAddress); vm.expectRevert(IEmailRecoveryManager.SetupAlreadyCalled.selector); - vm.startPrank(accountAddress); + vm.startPrank(accountAddress1); emailRecoveryModule.exposed_configureRecovery( - guardians, guardianWeights, threshold, delay, expiry + guardians1, guardianWeights, threshold, delay, expiry ); } function test_ConfigureRecovery_RevertWhen_ConfigureRecoveryCalledTwice() public { - vm.startPrank(accountAddress); + vm.startPrank(accountAddress1); vm.expectRevert(IEmailRecoveryManager.SetupAlreadyCalled.selector); emailRecoveryModule.exposed_configureRecovery( - guardians, guardianWeights, threshold, delay, expiry + guardians1, guardianWeights, threshold, delay, expiry ); } function test_ConfigureRecovery_Succeeds() public { - instance.uninstallModule(MODULE_TYPE_EXECUTOR, recoveryModuleAddress, ""); - vm.startPrank(accountAddress); - emailRecoveryModule.workaround_validatorsPush(accountAddress, validatorAddress); + instance1.uninstallModule(MODULE_TYPE_EXECUTOR, emailRecoveryModuleAddress, ""); + vm.startPrank(accountAddress1); + emailRecoveryModule.workaround_validatorsPush(accountAddress1, validatorAddress); - bool isActivated = emailRecoveryModule.isActivated(accountAddress); + bool isActivated = emailRecoveryModule.isActivated(accountAddress1); assertFalse(isActivated); vm.expectEmit(); emit IEmailRecoveryManager.RecoveryConfigured( - instance.account, guardians.length, totalWeight, threshold + instance1.account, guardians1.length, totalWeight, threshold ); emailRecoveryModule.exposed_configureRecovery( - guardians, guardianWeights, threshold, delay, expiry + guardians1, guardianWeights, threshold, delay, expiry ); IEmailRecoveryManager.RecoveryConfig memory recoveryConfig = - emailRecoveryModule.getRecoveryConfig(accountAddress); + emailRecoveryModule.getRecoveryConfig(accountAddress1); assertEq(recoveryConfig.delay, delay); assertEq(recoveryConfig.expiry, expiry); IGuardianManager.GuardianConfig memory guardianConfig = - emailRecoveryModule.getGuardianConfig(accountAddress); - assertEq(guardianConfig.guardianCount, guardians.length); + emailRecoveryModule.getGuardianConfig(accountAddress1); + assertEq(guardianConfig.guardianCount, guardians1.length); assertEq(guardianConfig.totalWeight, totalWeight); - assertEq(guardianConfig.acceptedWeight, 0); // no guardians accepted yet + assertEq(guardianConfig.acceptedWeight, 0); // no guardians1 accepted yet assertEq(guardianConfig.threshold, threshold); GuardianStorage memory guardian = - emailRecoveryModule.getGuardian(accountAddress, guardians[0]); + emailRecoveryModule.getGuardian(accountAddress1, guardians1[0]); assertEq(uint256(guardian.status), uint256(GuardianStatus.REQUESTED)); assertEq(guardian.weight, guardianWeights[0]); - isActivated = emailRecoveryModule.isActivated(accountAddress); + isActivated = emailRecoveryModule.isActivated(accountAddress1); assertTrue(isActivated); } function test_ConfigureRecovery_RevertWhen_ZeroGuardians() public { - instance.uninstallModule(MODULE_TYPE_EXECUTOR, recoveryModuleAddress, ""); - vm.startPrank(accountAddress); - emailRecoveryModule.workaround_validatorsPush(accountAddress, validatorAddress); + instance1.uninstallModule(MODULE_TYPE_EXECUTOR, emailRecoveryModuleAddress, ""); + vm.startPrank(accountAddress1); + emailRecoveryModule.workaround_validatorsPush(accountAddress1, validatorAddress); address[] memory zeroGuardians; vm.expectRevert( @@ -93,39 +104,39 @@ contract EmailRecoveryManager_configureRecovery_Test is UnitBase { } function test_ConfigureRecovery_RevertWhen_ZeroGuardianWeights() public { - instance.uninstallModule(MODULE_TYPE_EXECUTOR, recoveryModuleAddress, ""); - vm.startPrank(accountAddress); - emailRecoveryModule.workaround_validatorsPush(accountAddress, validatorAddress); + instance1.uninstallModule(MODULE_TYPE_EXECUTOR, emailRecoveryModuleAddress, ""); + vm.startPrank(accountAddress1); + emailRecoveryModule.workaround_validatorsPush(accountAddress1, validatorAddress); uint256[] memory zeroGuardianWeights; vm.expectRevert( abi.encodeWithSelector( IGuardianManager.IncorrectNumberOfWeights.selector, - guardians.length, + guardians1.length, zeroGuardianWeights.length ) ); emailRecoveryModule.exposed_configureRecovery( - guardians, zeroGuardianWeights, threshold, delay, expiry + guardians1, zeroGuardianWeights, threshold, delay, expiry ); } function test_ConfigureRecovery_RevertWhen_ZeroThreshold() public { - instance.uninstallModule(MODULE_TYPE_EXECUTOR, recoveryModuleAddress, ""); - vm.startPrank(accountAddress); - emailRecoveryModule.workaround_validatorsPush(accountAddress, validatorAddress); + instance1.uninstallModule(MODULE_TYPE_EXECUTOR, emailRecoveryModuleAddress, ""); + vm.startPrank(accountAddress1); + emailRecoveryModule.workaround_validatorsPush(accountAddress1, validatorAddress); uint256 zeroThreshold = 0; vm.expectRevert(IGuardianManager.ThresholdCannotBeZero.selector); emailRecoveryModule.exposed_configureRecovery( - guardians, guardianWeights, zeroThreshold, delay, expiry + guardians1, guardianWeights, zeroThreshold, delay, expiry ); } function test_ConfigureRecovery_RevertWhen_NoGuardians() public { - instance.uninstallModule(MODULE_TYPE_EXECUTOR, recoveryModuleAddress, ""); - vm.startPrank(accountAddress); - emailRecoveryModule.workaround_validatorsPush(accountAddress, validatorAddress); + instance1.uninstallModule(MODULE_TYPE_EXECUTOR, emailRecoveryModuleAddress, ""); + vm.startPrank(accountAddress1); + emailRecoveryModule.workaround_validatorsPush(accountAddress1, validatorAddress); address[] memory zeroGuardians; uint256[] memory zeroGuardianWeights; diff --git a/test/unit/EmailRecoveryManager/constructor.t.sol b/test/unit/EmailRecoveryManager/constructor.t.sol index b47f1247..b8440b9e 100644 --- a/test/unit/EmailRecoveryManager/constructor.t.sol +++ b/test/unit/EmailRecoveryManager/constructor.t.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; import { UnitBase } from "../UnitBase.t.sol"; import { IEmailRecoveryManager } from "src/interfaces/IEmailRecoveryManager.sol"; import { UniversalEmailRecoveryModule } from "src/modules/UniversalEmailRecoveryModule.sol"; @@ -18,7 +17,9 @@ contract EmailRecoveryManager_constructor_Test is UnitBase { invalidVerifier, address(dkimRegistry), address(emailAuthImpl), - address(emailRecoveryHandler) + commandHandlerAddress, + minimumDelay, + killSwitchAuthorizer ); } @@ -26,7 +27,12 @@ contract EmailRecoveryManager_constructor_Test is UnitBase { address invalidDkim = address(0); vm.expectRevert(IEmailRecoveryManager.InvalidDkimRegistry.selector); new UniversalEmailRecoveryModule( - address(verifier), invalidDkim, address(emailAuthImpl), address(emailRecoveryHandler) + address(verifier), + invalidDkim, + address(emailAuthImpl), + commandHandlerAddress, + minimumDelay, + killSwitchAuthorizer ); } @@ -37,15 +43,22 @@ contract EmailRecoveryManager_constructor_Test is UnitBase { address(verifier), address(dkimRegistry), invalidEmailAuth, - address(emailRecoveryHandler) + commandHandlerAddress, + minimumDelay, + killSwitchAuthorizer ); } - function test_Constructor_RevertWhen_InvalidSubjectHandler() public { + function test_Constructor_RevertWhen_InvalidCommandHandler() public { address invalidHandler = address(0); - vm.expectRevert(IEmailRecoveryManager.InvalidSubjectHandler.selector); + vm.expectRevert(IEmailRecoveryManager.InvalidCommandHandler.selector); new UniversalEmailRecoveryModule( - address(verifier), address(dkimRegistry), address(emailAuthImpl), invalidHandler + address(verifier), + address(dkimRegistry), + address(emailAuthImpl), + invalidHandler, + minimumDelay, + killSwitchAuthorizer ); } @@ -54,12 +67,16 @@ contract EmailRecoveryManager_constructor_Test is UnitBase { address(verifier), address(dkimRegistry), address(emailAuthImpl), - address(emailRecoveryHandler) + commandHandlerAddress, + minimumDelay, + killSwitchAuthorizer ); assertEq(address(verifier), emailRecoveryModule.verifier()); assertEq(address(dkimRegistry), emailRecoveryModule.dkim()); assertEq(address(emailAuthImpl), emailRecoveryModule.emailAuthImplementation()); - assertEq(address(emailRecoveryHandler), emailRecoveryModule.subjectHandler()); + assertEq(commandHandlerAddress, emailRecoveryModule.commandHandler()); + assertEq(minimumDelay, emailRecoveryModule.minimumDelay()); + assertEq(killSwitchAuthorizer, emailRecoveryModule.owner()); } } diff --git a/test/unit/EmailRecoveryManager/deInitRecoveryModule.t.sol b/test/unit/EmailRecoveryManager/deInitRecoveryModule.t.sol index 09422827..c13f05ef 100644 --- a/test/unit/EmailRecoveryManager/deInitRecoveryModule.t.sol +++ b/test/unit/EmailRecoveryManager/deInitRecoveryModule.t.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; import { UnitBase } from "../UnitBase.t.sol"; import { IEmailRecoveryManager } from "src/interfaces/IEmailRecoveryManager.sol"; import { GuardianManager } from "src/GuardianManager.sol"; @@ -13,64 +12,150 @@ contract EmailRecoveryManager_deInitRecoveryModule_Test is UnitBase { super.setUp(); } + function test_DeInitRecoveryModule_RevertWhen_RecoveryInProcess() public { - acceptGuardian(accountSalt1); - acceptGuardian(accountSalt2); + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); vm.warp(12 seconds); - handleRecovery(recoveryDataHash, accountSalt1); + handleRecovery(accountAddress1, guardians1[0], recoveryDataHash, emailRecoveryModuleAddress); - vm.prank(accountAddress); + vm.prank(accountAddress1); vm.expectRevert(IGuardianManager.RecoveryInProcess.selector); emailRecoveryModule.exposed_deInitRecoveryModule(); } function test_DeInitRecoveryModule_Succeeds() public { - acceptGuardian(accountSalt1); - acceptGuardian(accountSalt2); + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); + + bool isActivated = emailRecoveryModule.isActivated(accountAddress1); + assertTrue(isActivated); + + vm.prank(accountAddress1); + vm.expectEmit(); + emit IEmailRecoveryManager.RecoveryDeInitialized(accountAddress1); + emailRecoveryModule.exposed_deInitRecoveryModule(); + + // assert that recovery config has been cleared successfully + IEmailRecoveryManager.RecoveryConfig memory recoveryConfig = + emailRecoveryModule.getRecoveryConfig(accountAddress1); + assertEq(recoveryConfig.delay, 0); + assertEq(recoveryConfig.expiry, 0); + + // assert that the recovery request has been cleared successfully + ( + uint256 executeAfter, + uint256 executeBefore, + uint256 currentWeight, + bytes32 _recoveryDataHash + ) = emailRecoveryModule.getRecoveryRequest(accountAddress1); + bool hasGuardian1Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[0]); + bool hasGuardian2Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[1]); + assertEq(executeAfter, 0); + assertEq(executeBefore, 0); + assertEq(currentWeight, 0); + assertEq(_recoveryDataHash, ""); + assertEq(hasGuardian1Voted, false); + assertEq(hasGuardian2Voted, false); + + IEmailRecoveryManager.PreviousRecoveryRequest memory previousRecoveryRequest = + emailRecoveryModule.getPreviousRecoveryRequest(accountAddress1); + assertEq(previousRecoveryRequest.previousGuardianInitiated, address(0)); + assertEq(previousRecoveryRequest.cancelRecoveryCooldown, 0); + + // assert that guardian storage has been cleared successfully for guardian 1 + GuardianStorage memory guardianStorage1 = + emailRecoveryModule.getGuardian(accountAddress1, guardians1[0]); + assertEq(uint256(guardianStorage1.status), uint256(GuardianStatus.NONE)); + assertEq(guardianStorage1.weight, uint256(0)); - bool isActivated = emailRecoveryModule.isActivated(accountAddress); + // assert that guardian storage has been cleared successfully for guardian 2 + GuardianStorage memory guardianStorage2 = + emailRecoveryModule.getGuardian(accountAddress1, guardians1[1]); + assertEq(uint256(guardianStorage2.status), uint256(GuardianStatus.NONE)); + assertEq(guardianStorage2.weight, uint256(0)); + + // assert that guardian config has been cleared successfully + GuardianManager.GuardianConfig memory guardianConfig = + emailRecoveryModule.getGuardianConfig(accountAddress1); + assertEq(guardianConfig.guardianCount, 0); + assertEq(guardianConfig.totalWeight, 0); + assertEq(guardianConfig.acceptedWeight, 0); + assertEq(guardianConfig.threshold, 0); + + isActivated = emailRecoveryModule.isActivated(accountAddress1); + assertFalse(isActivated); + } + + + function test_DeInitRecoveryModule_SucceedsWhen_KillSwitchEnabled() public { + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); + + bool isActivated = emailRecoveryModule.isActivated(accountAddress1); assertTrue(isActivated); - vm.prank(accountAddress); + vm.prank(killSwitchAuthorizer); + emailRecoveryModule.toggleKillSwitch(); + vm.stopPrank(); + + vm.prank(accountAddress1); vm.expectEmit(); - emit IEmailRecoveryManager.RecoveryDeInitialized(accountAddress); + emit IEmailRecoveryManager.RecoveryDeInitialized(accountAddress1); emailRecoveryModule.exposed_deInitRecoveryModule(); // assert that recovery config has been cleared successfully IEmailRecoveryManager.RecoveryConfig memory recoveryConfig = - emailRecoveryModule.getRecoveryConfig(accountAddress); + emailRecoveryModule.getRecoveryConfig(accountAddress1); assertEq(recoveryConfig.delay, 0); assertEq(recoveryConfig.expiry, 0); // assert that the recovery request has been cleared successfully - IEmailRecoveryManager.RecoveryRequest memory recoveryRequest = - emailRecoveryModule.getRecoveryRequest(accountAddress); - assertEq(recoveryRequest.executeAfter, 0); - assertEq(recoveryRequest.executeBefore, 0); - assertEq(recoveryRequest.currentWeight, 0); - assertEq(recoveryRequest.recoveryDataHash, ""); + ( + uint256 executeAfter, + uint256 executeBefore, + uint256 currentWeight, + bytes32 _recoveryDataHash + ) = emailRecoveryModule.getRecoveryRequest(accountAddress1); + bool hasGuardian1Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[0]); + bool hasGuardian2Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[1]); + assertEq(executeAfter, 0); + assertEq(executeBefore, 0); + assertEq(currentWeight, 0); + assertEq(_recoveryDataHash, ""); + assertEq(hasGuardian1Voted, false); + assertEq(hasGuardian2Voted, false); + + IEmailRecoveryManager.PreviousRecoveryRequest memory previousRecoveryRequest = + emailRecoveryModule.getPreviousRecoveryRequest(accountAddress1); + assertEq(previousRecoveryRequest.previousGuardianInitiated, address(0)); + assertEq(previousRecoveryRequest.cancelRecoveryCooldown, 0); // assert that guardian storage has been cleared successfully for guardian 1 GuardianStorage memory guardianStorage1 = - emailRecoveryModule.getGuardian(accountAddress, guardian1); + emailRecoveryModule.getGuardian(accountAddress1, guardians1[0]); assertEq(uint256(guardianStorage1.status), uint256(GuardianStatus.NONE)); assertEq(guardianStorage1.weight, uint256(0)); // assert that guardian storage has been cleared successfully for guardian 2 GuardianStorage memory guardianStorage2 = - emailRecoveryModule.getGuardian(accountAddress, guardian2); + emailRecoveryModule.getGuardian(accountAddress1, guardians1[1]); assertEq(uint256(guardianStorage2.status), uint256(GuardianStatus.NONE)); assertEq(guardianStorage2.weight, uint256(0)); // assert that guardian config has been cleared successfully GuardianManager.GuardianConfig memory guardianConfig = - emailRecoveryModule.getGuardianConfig(accountAddress); + emailRecoveryModule.getGuardianConfig(accountAddress1); assertEq(guardianConfig.guardianCount, 0); assertEq(guardianConfig.totalWeight, 0); assertEq(guardianConfig.acceptedWeight, 0); assertEq(guardianConfig.threshold, 0); - isActivated = emailRecoveryModule.isActivated(accountAddress); + isActivated = emailRecoveryModule.isActivated(accountAddress1); assertFalse(isActivated); } } diff --git a/test/unit/EmailRecoveryManager/deInitRecoveryModuleWithAddress.t.sol b/test/unit/EmailRecoveryManager/deInitRecoveryModuleWithAddress.t.sol index 203c5c5e..23bd81cf 100644 --- a/test/unit/EmailRecoveryManager/deInitRecoveryModuleWithAddress.t.sol +++ b/test/unit/EmailRecoveryManager/deInitRecoveryModuleWithAddress.t.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; import { UnitBase } from "../UnitBase.t.sol"; import { IEmailRecoveryManager } from "src/interfaces/IEmailRecoveryManager.sol"; import { GuardianManager } from "src/GuardianManager.sol"; @@ -14,63 +13,80 @@ contract EmailRecoveryManager_deInitRecoveryModuleWithAddress_Test is UnitBase { } function test_DeInitRecoveryModuleWithAddress_RevertWhen_RecoveryInProcess() public { - acceptGuardian(accountSalt1); - acceptGuardian(accountSalt2); + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); vm.warp(12 seconds); - handleRecovery(recoveryDataHash, accountSalt1); + handleRecovery(accountAddress1, guardians1[0], recoveryDataHash, emailRecoveryModuleAddress); - vm.prank(accountAddress); + vm.prank(accountAddress1); vm.expectRevert(IGuardianManager.RecoveryInProcess.selector); - emailRecoveryModule.exposed_deInitRecoveryModule(accountAddress); + emailRecoveryModule.exposed_deInitRecoveryModule(accountAddress1); } function test_DeInitRecoveryModuleWithAddress_Succeeds() public { - acceptGuardian(accountSalt1); - acceptGuardian(accountSalt2); + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); - bool isActivated = emailRecoveryModule.isActivated(accountAddress); + bool isActivated = emailRecoveryModule.isActivated(accountAddress1); assertTrue(isActivated); - vm.prank(accountAddress); + vm.prank(accountAddress1); vm.expectEmit(); - emit IEmailRecoveryManager.RecoveryDeInitialized(accountAddress); - emailRecoveryModule.exposed_deInitRecoveryModule(accountAddress); + emit IEmailRecoveryManager.RecoveryDeInitialized(accountAddress1); + emailRecoveryModule.exposed_deInitRecoveryModule(accountAddress1); // assert that recovery config has been cleared successfully IEmailRecoveryManager.RecoveryConfig memory recoveryConfig = - emailRecoveryModule.getRecoveryConfig(accountAddress); + emailRecoveryModule.getRecoveryConfig(accountAddress1); assertEq(recoveryConfig.delay, 0); assertEq(recoveryConfig.expiry, 0); // assert that the recovery request has been cleared successfully - IEmailRecoveryManager.RecoveryRequest memory recoveryRequest = - emailRecoveryModule.getRecoveryRequest(accountAddress); - assertEq(recoveryRequest.executeAfter, 0); - assertEq(recoveryRequest.executeBefore, 0); - assertEq(recoveryRequest.currentWeight, 0); - assertEq(recoveryRequest.recoveryDataHash, ""); + ( + uint256 executeAfter, + uint256 executeBefore, + uint256 currentWeight, + bytes32 _recoveryDataHash + ) = emailRecoveryModule.getRecoveryRequest(accountAddress1); + bool hasGuardian1Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[0]); + bool hasGuardian2Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[1]); + assertEq(executeAfter, 0); + assertEq(executeBefore, 0); + assertEq(currentWeight, 0); + assertEq(_recoveryDataHash, ""); + assertEq(hasGuardian1Voted, false); + assertEq(hasGuardian2Voted, false); + + IEmailRecoveryManager.PreviousRecoveryRequest memory previousRecoveryRequest = + emailRecoveryModule.getPreviousRecoveryRequest(accountAddress1); + assertEq(previousRecoveryRequest.previousGuardianInitiated, address(0)); + assertEq(previousRecoveryRequest.cancelRecoveryCooldown, 0); // assert that guardian storage has been cleared successfully for guardian 1 GuardianStorage memory guardianStorage1 = - emailRecoveryModule.getGuardian(accountAddress, guardian1); + emailRecoveryModule.getGuardian(accountAddress1, guardians1[0]); assertEq(uint256(guardianStorage1.status), uint256(GuardianStatus.NONE)); assertEq(guardianStorage1.weight, uint256(0)); // assert that guardian storage has been cleared successfully for guardian 2 GuardianStorage memory guardianStorage2 = - emailRecoveryModule.getGuardian(accountAddress, guardian2); + emailRecoveryModule.getGuardian(accountAddress1, guardians1[1]); assertEq(uint256(guardianStorage2.status), uint256(GuardianStatus.NONE)); assertEq(guardianStorage2.weight, uint256(0)); // assert that guardian config has been cleared successfully GuardianManager.GuardianConfig memory guardianConfig = - emailRecoveryModule.getGuardianConfig(accountAddress); + emailRecoveryModule.getGuardianConfig(accountAddress1); + hasGuardian1Voted = emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[0]); + hasGuardian2Voted = emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[1]); assertEq(guardianConfig.guardianCount, 0); assertEq(guardianConfig.totalWeight, 0); assertEq(guardianConfig.acceptedWeight, 0); assertEq(guardianConfig.threshold, 0); - isActivated = emailRecoveryModule.isActivated(accountAddress); + isActivated = emailRecoveryModule.isActivated(accountAddress1); assertFalse(isActivated); } } diff --git a/test/unit/EmailRecoveryManager/extractRecoveredAccountFromAcceptanceCommand.t.sol b/test/unit/EmailRecoveryManager/extractRecoveredAccountFromAcceptanceCommand.t.sol new file mode 100644 index 00000000..ffc6ccaf --- /dev/null +++ b/test/unit/EmailRecoveryManager/extractRecoveredAccountFromAcceptanceCommand.t.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { UnitBase } from "../UnitBase.t.sol"; +import { CommandHandlerType } from "../../Base.t.sol"; + +contract EmailRecoveryManager_extractRecoveredAccountFromAcceptanceCommand_Test is UnitBase { + function setUp() public override { + super.setUp(); + } + + function test_ExtractRecoveredAccountFromAcceptanceCommand_FailsWithAbiEncodePacked() public { + skipIfNotCommandHandlerType(CommandHandlerType.EmailRecoveryCommandHandler); + + address account = address(0x1234567890123456789012345678901234567890); + bytes[] memory commandParams = new bytes[](1); + commandParams[0] = abi.encodePacked(account); + + vm.expectRevert(); + emailRecoveryModule.extractRecoveredAccountFromAcceptanceCommand(commandParams, 0); + } + + function test_ExtractRecoveredAccountFromAcceptanceCommand_Succeeds() public { + skipIfNotCommandHandlerType(CommandHandlerType.EmailRecoveryCommandHandler); + + address expectedAccount = address(1); + bytes[] memory commandParams = new bytes[](1); + commandParams[0] = abi.encode(expectedAccount); + + address extractedAccount = + emailRecoveryModule.extractRecoveredAccountFromAcceptanceCommand(commandParams, 0); + assertEq( + extractedAccount, expectedAccount, "Extracted account should match expected account" + ); + } +} diff --git a/test/unit/EmailRecoveryManager/extractRecoveredAccountFromAcceptanceSubject.t.sol b/test/unit/EmailRecoveryManager/extractRecoveredAccountFromAcceptanceSubject.t.sol deleted file mode 100644 index b6f6d8f5..00000000 --- a/test/unit/EmailRecoveryManager/extractRecoveredAccountFromAcceptanceSubject.t.sol +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.25; - -import { console2 } from "forge-std/console2.sol"; -import { UnitBase } from "../UnitBase.t.sol"; - -contract EmailRecoveryManager_extractRecoveredAccountFromAcceptanceSubject_Test is UnitBase { - function setUp() public override { - super.setUp(); - } - - function test_ExtractRecoveredAccountFromAcceptanceSubject_Succeeds() public view { } -} diff --git a/test/unit/EmailRecoveryManager/extractRecoveredAccountFromRecoveryCommand.t.sol b/test/unit/EmailRecoveryManager/extractRecoveredAccountFromRecoveryCommand.t.sol new file mode 100644 index 00000000..dd303f86 --- /dev/null +++ b/test/unit/EmailRecoveryManager/extractRecoveredAccountFromRecoveryCommand.t.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { UnitBase } from "../UnitBase.t.sol"; +import { CommandHandlerType } from "../../Base.t.sol"; + +contract EmailRecoveryManager_extractRecoveredAccountFromRecoveryCommand_Test is UnitBase { + function setUp() public override { + super.setUp(); + } + + function test_ExtractRecoveredAccountFromRecoveryCommand_FailsWithAbiEncodePacked() public { + skipIfNotCommandHandlerType(CommandHandlerType.EmailRecoveryCommandHandler); + + address account = address(0x1234567890123456789012345678901234567890); + bytes[] memory commandParams = new bytes[](1); + commandParams[0] = abi.encodePacked(account); + + vm.expectRevert(); + emailRecoveryModule.extractRecoveredAccountFromRecoveryCommand(commandParams, 0); + } + + function test_ExtractRecoveredAccountFromRecoveryCommand_Succeeds() public { + skipIfNotCommandHandlerType(CommandHandlerType.EmailRecoveryCommandHandler); + + address expectedAccount = address(1); + bytes[] memory commandParams = new bytes[](1); + commandParams[0] = abi.encode(expectedAccount); + + address extractedAccount = + emailRecoveryModule.extractRecoveredAccountFromRecoveryCommand(commandParams, 0); + assertEq( + extractedAccount, expectedAccount, "Extracted account should match expected account" + ); + } +} diff --git a/test/unit/EmailRecoveryManager/extractRecoveredAccountFromRecoverySubject.t.sol b/test/unit/EmailRecoveryManager/extractRecoveredAccountFromRecoverySubject.t.sol deleted file mode 100644 index 1e37cf33..00000000 --- a/test/unit/EmailRecoveryManager/extractRecoveredAccountFromRecoverySubject.t.sol +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.25; - -import { console2 } from "forge-std/console2.sol"; -import { UnitBase } from "../UnitBase.t.sol"; - -contract EmailRecoveryManager_extractRecoveredAccountFromRecoverySubject_Test is UnitBase { - function setUp() public override { - super.setUp(); - } - - function test_ExtractRecoveredAccountFromRecoverySubject_Succeeds() public view { } -} diff --git a/test/unit/EmailRecoveryManager/getPreviousRecoveryRequest.t.sol b/test/unit/EmailRecoveryManager/getPreviousRecoveryRequest.t.sol new file mode 100644 index 00000000..1ce543a1 --- /dev/null +++ b/test/unit/EmailRecoveryManager/getPreviousRecoveryRequest.t.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { UnitBase } from "../UnitBase.t.sol"; +import { IEmailRecoveryManager } from "src/interfaces/IEmailRecoveryManager.sol"; + +contract EmailRecoveryManager_getPreviousRecoveryRequest_Test is UnitBase { + function setUp() public override { + super.setUp(); + } + + function test_GetPreviousRecoveryRequest_SucceedsAfterCompleteRecovery() public { + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); + vm.warp(12 seconds); + handleRecovery(accountAddress1, guardians1[0], recoveryDataHash, emailRecoveryModuleAddress); + handleRecovery(accountAddress1, guardians1[1], recoveryDataHash, emailRecoveryModuleAddress); + + vm.warp(block.timestamp + delay); + + emailRecoveryModule.completeRecovery(accountAddress1, recoveryData); + + IEmailRecoveryManager.PreviousRecoveryRequest memory previousRecoveryRequest = + emailRecoveryModule.getPreviousRecoveryRequest(accountAddress1); + assertEq(previousRecoveryRequest.previousGuardianInitiated, guardians1[0]); + assertEq(previousRecoveryRequest.cancelRecoveryCooldown, 0); + } + + function test_GetPreviousRecoveryRequest_SucceedsAfterCancelExpiredRecovery() public { + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + vm.warp(12 seconds); + handleRecovery(accountAddress1, guardians1[1], recoveryDataHash, emailRecoveryModuleAddress); + handleRecovery(accountAddress1, guardians1[0], recoveryDataHash, emailRecoveryModuleAddress); + + vm.warp(block.timestamp + expiry); + + emailRecoveryModule.cancelExpiredRecovery(accountAddress1); + + IEmailRecoveryManager.PreviousRecoveryRequest memory previousRecoveryRequest = + emailRecoveryModule.getPreviousRecoveryRequest(accountAddress1); + assertEq(previousRecoveryRequest.previousGuardianInitiated, guardians1[1]); + assertEq( + previousRecoveryRequest.cancelRecoveryCooldown, + block.timestamp + emailRecoveryModule.CANCEL_EXPIRED_RECOVERY_COOLDOWN() + ); + } +} diff --git a/test/unit/EmailRecoveryManager/getRecoveryConfig.t.sol b/test/unit/EmailRecoveryManager/getRecoveryConfig.t.sol index 77a13d29..4f73e5f4 100644 --- a/test/unit/EmailRecoveryManager/getRecoveryConfig.t.sol +++ b/test/unit/EmailRecoveryManager/getRecoveryConfig.t.sol @@ -1,14 +1,12 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; import { IEmailRecoveryManager } from "src/interfaces/IEmailRecoveryManager.sol"; -import { GuardianStorage, GuardianStatus } from "src/libraries/EnumerableGuardianMap.sol"; import { UnitBase } from "../UnitBase.t.sol"; contract EmailRecoveryManager_getRecoveryConfig_Test is UnitBase { - uint256 newDelay = 1 days; - uint256 newExpiry = 4 weeks; + uint256 public newDelay = 1 days; + uint256 public newExpiry = 4 weeks; function setUp() public override { super.setUp(); @@ -16,17 +14,17 @@ contract EmailRecoveryManager_getRecoveryConfig_Test is UnitBase { IEmailRecoveryManager.RecoveryConfig memory recoveryConfig = IEmailRecoveryManager.RecoveryConfig(newDelay, newExpiry); - vm.startPrank(accountAddress); + vm.startPrank(accountAddress1); emailRecoveryModule.updateRecoveryConfig(recoveryConfig); - recoveryConfig = emailRecoveryModule.getRecoveryConfig(accountAddress); + recoveryConfig = emailRecoveryModule.getRecoveryConfig(accountAddress1); assertEq(recoveryConfig.delay, newDelay); assertEq(recoveryConfig.expiry, newExpiry); } - function test_GetRecoveryConfig_Succeeds() public { + function test_GetRecoveryConfig_Succeeds() public view { IEmailRecoveryManager.RecoveryConfig memory result = - emailRecoveryModule.getRecoveryConfig(accountAddress); + emailRecoveryModule.getRecoveryConfig(accountAddress1); assertEq(result.delay, newDelay); assertEq(result.expiry, newExpiry); } diff --git a/test/unit/EmailRecoveryManager/getRecoveryRequest.t.sol b/test/unit/EmailRecoveryManager/getRecoveryRequest.t.sol index 63441768..a43a251b 100644 --- a/test/unit/EmailRecoveryManager/getRecoveryRequest.t.sol +++ b/test/unit/EmailRecoveryManager/getRecoveryRequest.t.sol @@ -1,9 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; -import { IEmailRecoveryManager } from "src/interfaces/IEmailRecoveryManager.sol"; -import { GuardianStorage, GuardianStatus } from "src/libraries/EnumerableGuardianMap.sol"; import { UnitBase } from "../UnitBase.t.sol"; contract EmailRecoveryManager_getRecoveryRequest_Test is UnitBase { @@ -12,16 +9,26 @@ contract EmailRecoveryManager_getRecoveryRequest_Test is UnitBase { } function test_GetRecoveryRequest_Succeeds() public { - acceptGuardian(accountSalt1); - acceptGuardian(accountSalt2); + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); vm.warp(12 seconds); - handleRecovery(recoveryDataHash, accountSalt1); + handleRecovery(accountAddress1, guardians1[0], recoveryDataHash, emailRecoveryModuleAddress); - IEmailRecoveryManager.RecoveryRequest memory recoveryRequest = - emailRecoveryModule.getRecoveryRequest(accountAddress); - assertEq(recoveryRequest.executeAfter, 0); - assertEq(recoveryRequest.executeBefore, block.timestamp + expiry); - assertEq(recoveryRequest.currentWeight, 1); - assertEq(recoveryRequest.recoveryDataHash, recoveryDataHash); + ( + uint256 executeAfter, + uint256 executeBefore, + uint256 currentWeight, + bytes32 _recoveryDataHash + ) = emailRecoveryModule.getRecoveryRequest(accountAddress1); + bool hasGuardian1Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[0]); + bool hasGuardian2Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[1]); + assertEq(executeAfter, 0); + assertEq(executeBefore, block.timestamp + expiry); + assertEq(currentWeight, 1); + assertEq(_recoveryDataHash, recoveryDataHash); + assertEq(hasGuardian1Voted, true); + assertEq(hasGuardian2Voted, false); } } diff --git a/test/unit/EmailRecoveryManager/hasGuardianVoted.t.sol b/test/unit/EmailRecoveryManager/hasGuardianVoted.t.sol new file mode 100644 index 00000000..43bd57f2 --- /dev/null +++ b/test/unit/EmailRecoveryManager/hasGuardianVoted.t.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { ModuleKitHelpers } from "modulekit/ModuleKit.sol"; +import { UnitBase } from "../UnitBase.t.sol"; + +contract EmailRecoveryManager_hasGuardianVoted_Test is UnitBase { + using ModuleKitHelpers for *; + + function setUp() public override { + super.setUp(); + } + + function test_hasGuardianVoted_ReturnsFalseWhenGuardianHasNotVoted() public view { + bool hasGuardianVoted = emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[0]); + assertFalse(hasGuardianVoted); + } + + function test_hasGuardianVoted_ReturnsFalseWhenGuardianHasVotedButWrongAccount() public { + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); + + vm.warp(12 seconds); + handleRecovery(accountAddress1, guardians1[0], recoveryDataHash, emailRecoveryModuleAddress); + + bool hasGuardianVoted = emailRecoveryModule.hasGuardianVoted(accountAddress2, guardians1[0]); + assertFalse(hasGuardianVoted); + } + + function test_hasGuardianVoted_ReturnsFalseWhenAccountCorrectButWrongGuardian() public { + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); + + vm.warp(12 seconds); + handleRecovery(accountAddress1, guardians1[0], recoveryDataHash, emailRecoveryModuleAddress); + + bool hasGuardianVoted = emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[1]); + assertFalse(hasGuardianVoted); + } + + function test_hasGuardianVoted_ReturnsTrueWhenGuardianHasVoted() public { + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); + + vm.warp(12 seconds); + handleRecovery(accountAddress1, guardians1[0], recoveryDataHash, emailRecoveryModuleAddress); + + bool hasGuardianVoted = emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[0]); + assertTrue(hasGuardianVoted); + } +} diff --git a/test/unit/EmailRecoveryManager/isActivated.t.sol b/test/unit/EmailRecoveryManager/isActivated.t.sol index eaf6b86b..217daf91 100644 --- a/test/unit/EmailRecoveryManager/isActivated.t.sol +++ b/test/unit/EmailRecoveryManager/isActivated.t.sol @@ -1,11 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; import { ModuleKitHelpers } from "modulekit/ModuleKit.sol"; import { MODULE_TYPE_EXECUTOR } from "modulekit/external/ERC7579.sol"; -import { IEmailRecoveryManager } from "src/interfaces/IEmailRecoveryManager.sol"; -import { GuardianStorage, GuardianStatus } from "src/libraries/EnumerableGuardianMap.sol"; import { UnitBase } from "../UnitBase.t.sol"; contract EmailRecoveryManager_isActivated_Test is UnitBase { @@ -15,15 +12,15 @@ contract EmailRecoveryManager_isActivated_Test is UnitBase { super.setUp(); } - function test_isActivated_ReturnsTrueWhenModuleIsInstalled() public { - bool isActivated = emailRecoveryModule.isActivated(accountAddress); + function test_isActivated_ReturnsTrueWhenModuleIsInstalled() public view { + bool isActivated = emailRecoveryModule.isActivated(accountAddress1); assertTrue(isActivated); } function test_isActivated_ReturnsFalseWhenModuleIsInstalled() public { - instance.uninstallModule(MODULE_TYPE_EXECUTOR, recoveryModuleAddress, ""); + instance1.uninstallModule(MODULE_TYPE_EXECUTOR, emailRecoveryModuleAddress, ""); - bool isActivated = emailRecoveryModule.isActivated(accountAddress); + bool isActivated = emailRecoveryModule.isActivated(accountAddress1); assertFalse(isActivated); } } diff --git a/test/unit/EmailRecoveryManager/processRecovery.t.sol b/test/unit/EmailRecoveryManager/processRecovery.t.sol index 5989c5af..b66dec48 100644 --- a/test/unit/EmailRecoveryManager/processRecovery.t.sol +++ b/test/unit/EmailRecoveryManager/processRecovery.t.sol @@ -1,38 +1,67 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; import { ModuleKitHelpers, ModuleKitUserOp } from "modulekit/ModuleKit.sol"; import { MODULE_TYPE_EXECUTOR } from "modulekit/external/ERC7579.sol"; import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; import { UnitBase } from "../UnitBase.t.sol"; +import { CommandHandlerType } from "../../Base.t.sol"; import { IEmailRecoveryManager } from "src/interfaces/IEmailRecoveryManager.sol"; -import { GuardianStorage, GuardianStatus } from "src/libraries/EnumerableGuardianMap.sol"; +import { IEmailRecoveryCommandHandler } from "src/interfaces/IEmailRecoveryCommandHandler.sol"; +import { GuardianStatus } from "src/libraries/EnumerableGuardianMap.sol"; +import { IGuardianManager } from "src/interfaces/IGuardianManager.sol"; contract EmailRecoveryManager_processRecovery_Test is UnitBase { using ModuleKitHelpers for *; using ModuleKitUserOp for *; using Strings for uint256; - string recoveryDataHashString; - bytes[] subjectParams; - bytes32 nullifier; + string public recoveryDataHashString; + bytes[] public commandParams; + bytes32 public nullifier; function setUp() public override { super.setUp(); - recoveryDataHashString = uint256(recoveryDataHash).toHexString(32); - subjectParams = new bytes[](2); - subjectParams[0] = abi.encode(accountAddress); - subjectParams[1] = abi.encode(recoveryDataHashString); + if (getCommandHandlerType() == CommandHandlerType.EmailRecoveryCommandHandler) { + recoveryDataHashString = uint256(recoveryDataHash).toHexString(32); + commandParams = new bytes[](2); + commandParams[0] = abi.encode(accountAddress1); + commandParams[1] = abi.encode(recoveryDataHashString); + } + if (getCommandHandlerType() == CommandHandlerType.AccountHidingRecoveryCommandHandler) { + recoveryDataHashString = uint256(recoveryDataHash).toHexString(32); + commandParams = new bytes[](2); + commandParams[0] = + abi.encode(uint256(keccak256(abi.encodePacked(accountAddress1))).toHexString(32)); + commandParams[1] = abi.encode(recoveryDataHashString); + } + if (getCommandHandlerType() == CommandHandlerType.SafeRecoveryCommandHandler) { + commandParams = new bytes[](3); + commandParams[0] = abi.encode(accountAddress1); + commandParams[1] = abi.encode(owner1); + commandParams[2] = abi.encode(newOwner1); + } + nullifier = keccak256(abi.encode("nullifier 1")); } + function test_ProcessRecovery_RevertWhen_KillSwitchEnabled() public { + vm.prank(killSwitchAuthorizer); + emailRecoveryModule.toggleKillSwitch(); + vm.stopPrank(); + + vm.expectRevert(IGuardianManager.KillSwitchEnabled.selector); + emailRecoveryModule.exposed_processRecovery( + guardians1[0], templateIdx, commandParams, nullifier + ); + } + function test_ProcessRecovery_RevertWhen_GuardianStatusIsNONE() public { address invalidGuardian = address(1); - acceptGuardian(accountSalt1); - acceptGuardian(accountSalt2); + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); // invalidGuardian has not been configured nor accepted, so the guardian status is NONE vm.expectRevert( @@ -43,13 +72,13 @@ contract EmailRecoveryManager_processRecovery_Test is UnitBase { ) ); emailRecoveryModule.exposed_processRecovery( - invalidGuardian, templateIdx, subjectParams, nullifier + invalidGuardian, templateIdx, commandParams, nullifier ); } function test_ProcessRecovery_RevertWhen_GuardianStatusIsREQUESTED() public { - acceptGuardian(accountSalt2); - acceptGuardian(accountSalt3); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[2], emailRecoveryModuleAddress); // Valid guardian but we haven't called acceptGuardian(), so the guardian // status is still REQUESTED @@ -61,18 +90,16 @@ contract EmailRecoveryManager_processRecovery_Test is UnitBase { ) ); emailRecoveryModule.exposed_processRecovery( - guardian1, templateIdx, subjectParams, nullifier + guardians1[0], templateIdx, commandParams, nullifier ); } function test_ProcessRecovery_RevertWhen_RecoveryModuleNotInstalled() public { - vm.prank(accountAddress); - instance.uninstallModule(MODULE_TYPE_EXECUTOR, recoveryModuleAddress, ""); - vm.stopPrank(); + instance1.uninstallModule(MODULE_TYPE_EXECUTOR, emailRecoveryModuleAddress, ""); vm.expectRevert(IEmailRecoveryManager.RecoveryIsNotActivated.selector); emailRecoveryModule.exposed_processRecovery( - guardian1, templateIdx, subjectParams, nullifier + guardians1[0], templateIdx, commandParams, nullifier ); } @@ -81,8 +108,8 @@ contract EmailRecoveryManager_processRecovery_Test is UnitBase { // threshold = 3 // useable weight from accepted guardians = 0 - acceptGuardian(accountSalt1); // weight = 1 - acceptGuardian(accountSalt2); // weight = 2 + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); // weight = 1 + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); // weight = 2 // total weight = 4 // threshold = 3 @@ -92,7 +119,7 @@ contract EmailRecoveryManager_processRecovery_Test is UnitBase { uint256 newWeight = 1; uint256 newThreshold = 5; - vm.startPrank(accountAddress); + vm.startPrank(accountAddress1); emailRecoveryModule.addGuardian(newGuardian, newWeight); emailRecoveryModule.changeThreshold(newThreshold); vm.stopPrank(); @@ -108,23 +135,44 @@ contract EmailRecoveryManager_processRecovery_Test is UnitBase { ) ); emailRecoveryModule.exposed_processRecovery( - guardian2, templateIdx, subjectParams, nullifier + guardians1[1], templateIdx, commandParams, nullifier ); } - function test_ProcessRecovery_RevertWhen_InvalidRecoveryDataHash() public { - bytes32 invalidRecoveryDataHash = keccak256(abi.encode("invalid hash")); - string memory invalidRecoveryDataHashString = - uint256(invalidRecoveryDataHash).toHexString(32); + function test_ProcessRecovery_RevertWhen_GuardianAlreadyVoted() public { + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); - acceptGuardian(accountSalt1); - acceptGuardian(accountSalt2); + emailRecoveryModule.exposed_processRecovery( + guardians1[0], templateIdx, commandParams, nullifier + ); + vm.expectRevert(IEmailRecoveryManager.GuardianAlreadyVoted.selector); emailRecoveryModule.exposed_processRecovery( - guardian1, templateIdx, subjectParams, nullifier + guardians1[0], templateIdx, commandParams, nullifier ); + } - subjectParams[1] = abi.encode(invalidRecoveryDataHashString); + function test_ProcessRecovery_RevertWhen_InvalidRecoveryDataHash() public { + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); + + emailRecoveryModule.exposed_processRecovery( + guardians1[0], templateIdx, commandParams, nullifier + ); + + bytes32 invalidRecoveryDataHash; + if (getCommandHandlerType() == CommandHandlerType.SafeRecoveryCommandHandler) { + address invalidOwner = address(1); + commandParams[2] = abi.encode(invalidOwner); + invalidRecoveryDataHash = IEmailRecoveryCommandHandler(commandHandlerAddress) + .parseRecoveryDataHash(templateIdx, commandParams); + } else { + invalidRecoveryDataHash = keccak256(abi.encode("invalid hash")); + string memory invalidRecoveryDataHashString = + uint256(invalidRecoveryDataHash).toHexString(32); + commandParams[1] = abi.encode(invalidRecoveryDataHashString); + } vm.expectRevert( abi.encodeWithSelector( @@ -134,56 +182,300 @@ contract EmailRecoveryManager_processRecovery_Test is UnitBase { ) ); emailRecoveryModule.exposed_processRecovery( - guardian1, templateIdx, subjectParams, nullifier + guardians1[1], templateIdx, commandParams, nullifier ); } + function test_ProcessRecovery_RevertWhen_GuardianMustWaitForCooldown() public { + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); + + emailRecoveryModule.exposed_processRecovery( + guardians1[0], templateIdx, commandParams, nullifier + ); + + vm.warp(block.timestamp + expiry); + emailRecoveryModule.cancelExpiredRecovery(accountAddress1); + + vm.expectRevert( + abi.encodeWithSelector( + IEmailRecoveryManager.GuardianMustWaitForCooldown.selector, guardians1[0] + ) + ); + emailRecoveryModule.exposed_processRecovery( + guardians1[0], templateIdx, commandParams, nullifier + ); + } + + function test_ProcessRecovery_RevertWhen_GuardianMustWaitForCooldown_GuardianCountIsTwo() + public + { + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); + + // remove guardian 3 + vm.startPrank(accountAddress1); + emailRecoveryModule.removeGuardian(guardians1[2]); + vm.stopPrank(); + + uint256 guardianCount = emailRecoveryModule.getGuardianConfig(accountAddress1).guardianCount; + assertEq(guardianCount, 2); + + emailRecoveryModule.exposed_processRecovery( + guardians1[0], templateIdx, commandParams, nullifier + ); + + vm.warp(block.timestamp + expiry); + emailRecoveryModule.cancelExpiredRecovery(accountAddress1); + + vm.expectRevert( + abi.encodeWithSelector( + IEmailRecoveryManager.GuardianMustWaitForCooldown.selector, guardians1[0] + ) + ); + emailRecoveryModule.exposed_processRecovery( + guardians1[0], templateIdx, commandParams, nullifier + ); + } + + function test_ProcessRecovery_RevertWhen_GuardianMustWaitForCooldown_CooldownOneSecondRemaining( + ) + public + { + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); + + emailRecoveryModule.exposed_processRecovery( + guardians1[0], templateIdx, commandParams, nullifier + ); + + vm.warp(block.timestamp + expiry); + emailRecoveryModule.cancelExpiredRecovery(accountAddress1); + + // warp to after cooldown has expired + vm.warp( + block.timestamp + emailRecoveryModule.CANCEL_EXPIRED_RECOVERY_COOLDOWN() - 1 seconds + ); + + vm.expectRevert( + abi.encodeWithSelector( + IEmailRecoveryManager.GuardianMustWaitForCooldown.selector, guardians1[0] + ) + ); + emailRecoveryModule.exposed_processRecovery( + guardians1[0], templateIdx, commandParams, nullifier + ); + } + + function test_ProcessRecovery_PreviousGuardianInitiatedButCooldownOver() public { + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); + + emailRecoveryModule.exposed_processRecovery( + guardians1[0], templateIdx, commandParams, nullifier + ); + + vm.warp(block.timestamp + expiry); + emailRecoveryModule.cancelExpiredRecovery(accountAddress1); + + // warp to after cooldown has expired + 1 seconds + vm.warp( + block.timestamp + emailRecoveryModule.CANCEL_EXPIRED_RECOVERY_COOLDOWN() + 1 seconds + ); + + emailRecoveryModule.exposed_processRecovery( + guardians1[0], templateIdx, commandParams, nullifier + ); + + ( + uint256 executeAfter, + uint256 executeBefore, + uint256 currentWeight, + bytes32 _recoveryDataHash + ) = emailRecoveryModule.getRecoveryRequest(accountAddress1); + IEmailRecoveryManager.PreviousRecoveryRequest memory previousRecoveryRequest = + emailRecoveryModule.getPreviousRecoveryRequest(accountAddress1); + bool hasGuardian1Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[0]); + assertEq(executeAfter, 0); + assertEq(executeBefore, block.timestamp + expiry); + assertEq(currentWeight, guardianWeights[0]); + assertEq(_recoveryDataHash, recoveryDataHash); + assertEq(previousRecoveryRequest.previousGuardianInitiated, guardians1[0]); + assertEq(previousRecoveryRequest.cancelRecoveryCooldown, block.timestamp - 1 seconds); + assertEq(hasGuardian1Voted, true); + } + + function test_ProcessRecovery_PreviousGuardianInitiatedButCooldownOver_CooldownIsEqual() + public + { + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); + + emailRecoveryModule.exposed_processRecovery( + guardians1[0], templateIdx, commandParams, nullifier + ); + + vm.warp(block.timestamp + expiry); + emailRecoveryModule.cancelExpiredRecovery(accountAddress1); + + // warp to after cooldown has expired - cooldown end is equal to timestamp + vm.warp(block.timestamp + emailRecoveryModule.CANCEL_EXPIRED_RECOVERY_COOLDOWN()); + + emailRecoveryModule.exposed_processRecovery( + guardians1[0], templateIdx, commandParams, nullifier + ); + + ( + uint256 executeAfter, + uint256 executeBefore, + uint256 currentWeight, + bytes32 _recoveryDataHash + ) = emailRecoveryModule.getRecoveryRequest(accountAddress1); + IEmailRecoveryManager.PreviousRecoveryRequest memory previousRecoveryRequest = + emailRecoveryModule.getPreviousRecoveryRequest(accountAddress1); + bool hasGuardian1Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[0]); + assertEq(executeAfter, 0); + assertEq(executeBefore, block.timestamp + expiry); + assertEq(currentWeight, guardianWeights[0]); + assertEq(_recoveryDataHash, recoveryDataHash); + assertEq(previousRecoveryRequest.previousGuardianInitiated, guardians1[0]); + assertEq(previousRecoveryRequest.cancelRecoveryCooldown, block.timestamp); + assertEq(hasGuardian1Voted, true); + } + + function test_ProcessRecovery_PreviousGuardianInitiatedButGuardianCountIsOne() public { + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); + + // remove guardians 1 & 3 + vm.startPrank(accountAddress1); + emailRecoveryModule.changeThreshold(2); + emailRecoveryModule.removeGuardian(guardians1[0]); + emailRecoveryModule.removeGuardian(guardians1[2]); + vm.stopPrank(); + + uint256 guardianCount = emailRecoveryModule.getGuardianConfig(accountAddress1).guardianCount; + assertEq(guardianCount, 1); + + emailRecoveryModule.exposed_processRecovery( + guardians1[1], templateIdx, commandParams, nullifier + ); + + vm.warp(block.timestamp + expiry); + emailRecoveryModule.cancelExpiredRecovery(accountAddress1); + + // guardian count is 1, so processRecovery can be executed subsequently by the same guardian + emailRecoveryModule.exposed_processRecovery( + guardians1[1], templateIdx, commandParams, nullifier + ); + + ( + uint256 executeAfter, + uint256 executeBefore, + uint256 currentWeight, + bytes32 _recoveryDataHash + ) = emailRecoveryModule.getRecoveryRequest(accountAddress1); + IEmailRecoveryManager.PreviousRecoveryRequest memory previousRecoveryRequest = + emailRecoveryModule.getPreviousRecoveryRequest(accountAddress1); + bool hasGuardian2Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[1]); + assertEq(executeAfter, block.timestamp + delay); + assertEq(executeBefore, block.timestamp + expiry); + assertEq(currentWeight, guardianWeights[1]); + assertEq(_recoveryDataHash, recoveryDataHash); + assertEq(previousRecoveryRequest.previousGuardianInitiated, guardians1[1]); + assertEq( + previousRecoveryRequest.cancelRecoveryCooldown, + block.timestamp + emailRecoveryModule.CANCEL_EXPIRED_RECOVERY_COOLDOWN() + ); + assertEq(hasGuardian2Voted, true); + } + function test_ProcessRecovery_IncreasesTotalWeight() public { uint256 guardian1Weight = guardianWeights[0]; - acceptGuardian(accountSalt1); - acceptGuardian(accountSalt2); + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); + vm.expectEmit(); + emit IEmailRecoveryManager.RecoveryRequestStarted( + accountAddress1, guardians1[0], block.timestamp + expiry, recoveryDataHash + ); emailRecoveryModule.exposed_processRecovery( - guardian1, templateIdx, subjectParams, nullifier + guardians1[0], templateIdx, commandParams, nullifier ); - IEmailRecoveryManager.RecoveryRequest memory recoveryRequest = - emailRecoveryModule.getRecoveryRequest(accountAddress); - assertEq(recoveryRequest.executeAfter, 0); - assertEq(recoveryRequest.executeBefore, block.timestamp + expiry); - assertEq(recoveryRequest.currentWeight, guardian1Weight); - assertEq(recoveryRequest.recoveryDataHash, recoveryDataHash); + ( + uint256 executeAfter, + uint256 executeBefore, + uint256 currentWeight, + bytes32 _recoveryDataHash + ) = emailRecoveryModule.getRecoveryRequest(accountAddress1); + IEmailRecoveryManager.PreviousRecoveryRequest memory previousRecoveryRequest = + emailRecoveryModule.getPreviousRecoveryRequest(accountAddress1); + bool hasGuardian1Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[0]); + bool hasGuardian2Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[1]); + assertEq(executeAfter, 0); + assertEq(executeBefore, block.timestamp + expiry); + assertEq(currentWeight, guardian1Weight); + assertEq(_recoveryDataHash, recoveryDataHash); + assertEq(previousRecoveryRequest.previousGuardianInitiated, guardians1[0]); + assertEq(hasGuardian1Voted, true); + assertEq(hasGuardian2Voted, false); } function test_ProcessRecovery_InitiatesRecovery() public { uint256 guardian1Weight = guardianWeights[0]; uint256 guardian2Weight = guardianWeights[1]; - acceptGuardian(accountSalt1); - acceptGuardian(accountSalt2); + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); vm.warp(12 seconds); // Call processRecovery - increases currentWeight to 1 so not >= threshold yet - handleRecovery(recoveryDataHash, accountSalt1); - // Call processRecovery with guardian2 which increases currentWeight to >= threshold vm.expectEmit(); - emit IEmailRecoveryManager.RecoveryProcessed( - accountAddress, - guardian2, + emit IEmailRecoveryManager.GuardianVoted( + accountAddress1, guardians1[0], guardian1Weight, guardian1Weight + ); + emailRecoveryModule.exposed_processRecovery( + guardians1[0], templateIdx, commandParams, nullifier + ); + + // Call processRecovery with guardians2 which increases currentWeight to >= threshold + vm.expectEmit(); + emit IEmailRecoveryManager.RecoveryRequestComplete( + accountAddress1, + guardians1[1], block.timestamp + delay, block.timestamp + expiry, recoveryDataHash ); emailRecoveryModule.exposed_processRecovery( - guardian2, templateIdx, subjectParams, nullifier + guardians1[1], templateIdx, commandParams, nullifier ); - IEmailRecoveryManager.RecoveryRequest memory recoveryRequest = - emailRecoveryModule.getRecoveryRequest(accountAddress); - assertEq(recoveryRequest.executeAfter, block.timestamp + delay); - assertEq(recoveryRequest.executeBefore, block.timestamp + expiry); - assertEq(recoveryRequest.currentWeight, guardian1Weight + guardian2Weight); - assertEq(recoveryRequest.recoveryDataHash, recoveryDataHash); + ( + uint256 executeAfter, + uint256 executeBefore, + uint256 currentWeight, + bytes32 _recoveryDataHash + ) = emailRecoveryModule.getRecoveryRequest(accountAddress1); + IEmailRecoveryManager.PreviousRecoveryRequest memory previousRecoveryRequest = + emailRecoveryModule.getPreviousRecoveryRequest(accountAddress1); + bool hasGuardian1Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[0]); + bool hasGuardian2Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[1]); + assertEq(executeAfter, block.timestamp + delay); + assertEq(executeBefore, block.timestamp + expiry); + assertEq(currentWeight, guardian1Weight + guardian2Weight); + assertEq(_recoveryDataHash, recoveryDataHash); + assertEq(previousRecoveryRequest.previousGuardianInitiated, guardians1[0]); + assertEq(hasGuardian1Voted, true); + assertEq(hasGuardian2Voted, true); } } diff --git a/test/unit/EmailRecoveryManager/recoverySubjectTemplates.t.sol b/test/unit/EmailRecoveryManager/recoveryCommandTemplates.t.sol similarity index 69% rename from test/unit/EmailRecoveryManager/recoverySubjectTemplates.t.sol rename to test/unit/EmailRecoveryManager/recoveryCommandTemplates.t.sol index e754b505..e9d5d54a 100644 --- a/test/unit/EmailRecoveryManager/recoverySubjectTemplates.t.sol +++ b/test/unit/EmailRecoveryManager/recoveryCommandTemplates.t.sol @@ -1,16 +1,17 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; import { UnitBase } from "../UnitBase.t.sol"; +import { CommandHandlerType } from "../../Base.t.sol"; -contract EmailRecoveryManager_recoverySubjectTemplates_Test is UnitBase { +contract EmailRecoveryManager_recoveryCommandTemplates_Test is UnitBase { function setUp() public override { super.setUp(); } - function test_RecoverySubjectTemplates_Succeeds() public view { - string[][] memory templates = emailRecoveryModule.recoverySubjectTemplates(); + function test_RecoveryCommandTemplates_Succeeds() public { + skipIfNotCommandHandlerType(CommandHandlerType.EmailRecoveryCommandHandler); + string[][] memory templates = emailRecoveryModule.recoveryCommandTemplates(); assertEq(templates.length, 1); assertEq(templates[0].length, 7); diff --git a/test/unit/EmailRecoveryManager/toggleKillSwitch.t.sol b/test/unit/EmailRecoveryManager/toggleKillSwitch.t.sol new file mode 100644 index 00000000..4a531628 --- /dev/null +++ b/test/unit/EmailRecoveryManager/toggleKillSwitch.t.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; +import { UnitBase } from "../UnitBase.t.sol"; + +contract EmailRecoveryManager_toggleKillSwitch_Test is UnitBase { + function setUp() public override { + super.setUp(); + } + + function test_ToggleKillSwitch_RevertWhen_NotOwner() public { + vm.prank(zkEmailDeployer); + vm.expectRevert( + abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, zkEmailDeployer) + ); + emailRecoveryModule.toggleKillSwitch(); + } + + function test_ToggleKillSwitch_Succeeds() public { + bool killSwitchEnabled = emailRecoveryModule.killSwitchEnabled(); + assertFalse(killSwitchEnabled); + + vm.prank(killSwitchAuthorizer); + emailRecoveryModule.toggleKillSwitch(); + vm.stopPrank(); + + killSwitchEnabled = emailRecoveryModule.killSwitchEnabled(); + assertTrue(killSwitchEnabled); + + vm.prank(killSwitchAuthorizer); + emailRecoveryModule.toggleKillSwitch(); + vm.stopPrank(); + + killSwitchEnabled = emailRecoveryModule.killSwitchEnabled(); + assertFalse(killSwitchEnabled); + } +} diff --git a/test/unit/EmailRecoveryManager/updateRecoveryConfig.t.sol b/test/unit/EmailRecoveryManager/updateRecoveryConfig.t.sol index b3647534..de7030e2 100644 --- a/test/unit/EmailRecoveryManager/updateRecoveryConfig.t.sol +++ b/test/unit/EmailRecoveryManager/updateRecoveryConfig.t.sol @@ -1,27 +1,44 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; +import { ModuleKitHelpers } from "modulekit/ModuleKit.sol"; +import { MODULE_TYPE_EXECUTOR } from "modulekit/external/ERC7579.sol"; import { UnitBase } from "../UnitBase.t.sol"; +import { UniversalEmailRecoveryModule } from "src/modules/UniversalEmailRecoveryModule.sol"; import { IEmailRecoveryManager } from "src/interfaces/IEmailRecoveryManager.sol"; import { IGuardianManager } from "src/interfaces/IGuardianManager.sol"; contract EmailRecoveryManager_updateRecoveryConfig_Test is UnitBase { + using ModuleKitHelpers for *; + function setUp() public override { super.setUp(); } + function test_UpdateRecoveryConfig_RevertWhen_KillSwitchEnabled() public { + IEmailRecoveryManager.RecoveryConfig memory recoveryConfig = + IEmailRecoveryManager.RecoveryConfig(delay, expiry); + + vm.prank(killSwitchAuthorizer); + emailRecoveryModule.toggleKillSwitch(); + vm.stopPrank(); + + vm.startPrank(accountAddress1); + vm.expectRevert(IGuardianManager.KillSwitchEnabled.selector); + emailRecoveryModule.updateRecoveryConfig(recoveryConfig); + } + function test_UpdateRecoveryConfig_RevertWhen_AlreadyRecovering() public { IEmailRecoveryManager.RecoveryConfig memory recoveryConfig = IEmailRecoveryManager.RecoveryConfig(delay, expiry); - acceptGuardian(accountSalt1); - acceptGuardian(accountSalt2); + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); vm.warp(12 seconds); - handleRecovery(recoveryDataHash, accountSalt1); - handleRecovery(recoveryDataHash, accountSalt2); + handleRecovery(accountAddress1, guardians1[0], recoveryDataHash, emailRecoveryModuleAddress); + handleRecovery(accountAddress1, guardians1[1], recoveryDataHash, emailRecoveryModuleAddress); - vm.startPrank(accountAddress); + vm.startPrank(accountAddress1); vm.expectRevert(IGuardianManager.RecoveryInProcess.selector); emailRecoveryModule.updateRecoveryConfig(recoveryConfig); } @@ -36,13 +53,47 @@ contract EmailRecoveryManager_updateRecoveryConfig_Test is UnitBase { emailRecoveryModule.updateRecoveryConfig(recoveryConfig); } + function test_UpdateRecoveryConfig_RevertWhen_DelayLessThanMinimumDelay_OneSecondOff() public { + uint256 invalidDelay = minimumDelay - 1 seconds; + + IEmailRecoveryManager.RecoveryConfig memory recoveryConfig = + IEmailRecoveryManager.RecoveryConfig(invalidDelay, expiry); + + vm.startPrank(accountAddress1); + vm.expectRevert( + abi.encodeWithSelector( + IEmailRecoveryManager.DelayLessThanMinimumDelay.selector, + invalidDelay, + emailRecoveryModule.minimumDelay() + ) + ); + emailRecoveryModule.updateRecoveryConfig(recoveryConfig); + } + + function test_UpdateRecoveryConfig_RevertWhen_DelayLessThanMinimumDelay_ZeroSeconds() public { + uint256 invalidDelay = 0 seconds; + + IEmailRecoveryManager.RecoveryConfig memory recoveryConfig = + IEmailRecoveryManager.RecoveryConfig(invalidDelay, expiry); + + vm.startPrank(accountAddress1); + vm.expectRevert( + abi.encodeWithSelector( + IEmailRecoveryManager.DelayLessThanMinimumDelay.selector, + invalidDelay, + emailRecoveryModule.minimumDelay() + ) + ); + emailRecoveryModule.updateRecoveryConfig(recoveryConfig); + } + function test_UpdateRecoveryConfig_RevertWhen_DelayMoreThanExpiry() public { uint256 invalidDelay = expiry + 1 seconds; IEmailRecoveryManager.RecoveryConfig memory recoveryConfig = IEmailRecoveryManager.RecoveryConfig(invalidDelay, expiry); - vm.startPrank(accountAddress); + vm.startPrank(accountAddress1); vm.expectRevert( abi.encodeWithSelector( IEmailRecoveryManager.DelayMoreThanExpiry.selector, invalidDelay, expiry @@ -58,7 +109,7 @@ contract EmailRecoveryManager_updateRecoveryConfig_Test is UnitBase { IEmailRecoveryManager.RecoveryConfig memory recoveryConfig = IEmailRecoveryManager.RecoveryConfig(newDelay, newExpiry); - vm.startPrank(accountAddress); + vm.startPrank(accountAddress1); vm.expectRevert( abi.encodeWithSelector( IEmailRecoveryManager.RecoveryWindowTooShort.selector, newExpiry - newDelay @@ -68,13 +119,13 @@ contract EmailRecoveryManager_updateRecoveryConfig_Test is UnitBase { } function test_UpdateRecoveryConfig_RevertWhen_RecoveryWindowTooShortByOneSecond() public { - uint256 newDelay = 1 seconds; - uint256 newExpiry = 2 days; + uint256 newDelay = 1 days + 1 seconds; + uint256 newExpiry = 3 days; IEmailRecoveryManager.RecoveryConfig memory recoveryConfig = IEmailRecoveryManager.RecoveryConfig(newDelay, newExpiry); - vm.startPrank(accountAddress); + vm.startPrank(accountAddress1); vm.expectRevert( abi.encodeWithSelector( IEmailRecoveryManager.RecoveryWindowTooShort.selector, newExpiry - newDelay @@ -83,19 +134,119 @@ contract EmailRecoveryManager_updateRecoveryConfig_Test is UnitBase { emailRecoveryModule.updateRecoveryConfig(recoveryConfig); } + function test_UpdateRecoveryConfig_Success_WithMinimumDelayOfZero() public { + uint256 newMinimumDelay = 0; + instance1.uninstallModule(MODULE_TYPE_EXECUTOR, emailRecoveryModuleAddress, ""); + + UniversalEmailRecoveryModule newEmailRecoveryModule = new UniversalEmailRecoveryModule( + address(verifier), + address(dkimRegistry), + address(emailAuthImpl), + address(4), + newMinimumDelay, + killSwitchAuthorizer + ); + + instance1.installModule({ + moduleTypeId: MODULE_TYPE_EXECUTOR, + module: address(newEmailRecoveryModule), + data: abi.encode( + validatorAddress, + isInstalledContext, + functionSelector, + guardians1, + guardianWeights, + threshold, + delay, + expiry + ) + }); + + uint256 newDelay = 0 seconds; + + IEmailRecoveryManager.RecoveryConfig memory recoveryConfig = + IEmailRecoveryManager.RecoveryConfig(newDelay, expiry); + + vm.startPrank(accountAddress1); + vm.expectEmit(); + emit IEmailRecoveryManager.RecoveryConfigUpdated( + accountAddress1, newDelay, recoveryConfig.expiry + ); + newEmailRecoveryModule.updateRecoveryConfig(recoveryConfig); + + recoveryConfig = newEmailRecoveryModule.getRecoveryConfig(accountAddress1); + assertEq(recoveryConfig.delay, newDelay); + assertEq(recoveryConfig.expiry, expiry); + } + + function test_UpdateRecoveryConfig_Success_WhenDelayOneSecondOffMinimum() public { + uint256 newDelay = minimumDelay + 1 seconds; + + IEmailRecoveryManager.RecoveryConfig memory recoveryConfig = + IEmailRecoveryManager.RecoveryConfig(newDelay, expiry); + + vm.startPrank(accountAddress1); + vm.expectEmit(); + emit IEmailRecoveryManager.RecoveryConfigUpdated( + accountAddress1, newDelay, recoveryConfig.expiry + ); + emailRecoveryModule.updateRecoveryConfig(recoveryConfig); + + recoveryConfig = emailRecoveryModule.getRecoveryConfig(accountAddress1); + assertEq(recoveryConfig.delay, newDelay); + assertEq(recoveryConfig.expiry, expiry); + } + + function test_UpdateRecoveryConfig_Success_WhenDelayEqualToMinimum() public { + uint256 newDelay = emailRecoveryModule.minimumDelay(); + + IEmailRecoveryManager.RecoveryConfig memory recoveryConfig = + IEmailRecoveryManager.RecoveryConfig(newDelay, expiry); + + vm.startPrank(accountAddress1); + vm.expectEmit(); + emit IEmailRecoveryManager.RecoveryConfigUpdated( + accountAddress1, newDelay, recoveryConfig.expiry + ); + emailRecoveryModule.updateRecoveryConfig(recoveryConfig); + + recoveryConfig = emailRecoveryModule.getRecoveryConfig(accountAddress1); + assertEq(recoveryConfig.delay, newDelay); + assertEq(recoveryConfig.expiry, expiry); + } + + function test_UpdateRecoveryConfig_Success_WhenLongDelayAndExpiry() public { + uint256 newDelay = 52 weeks; + uint256 newExpiry = 104 weeks; + + IEmailRecoveryManager.RecoveryConfig memory recoveryConfig = + IEmailRecoveryManager.RecoveryConfig(newDelay, newExpiry); + + vm.startPrank(accountAddress1); + vm.expectEmit(); + emit IEmailRecoveryManager.RecoveryConfigUpdated(accountAddress1, newDelay, newExpiry); + emailRecoveryModule.updateRecoveryConfig(recoveryConfig); + + recoveryConfig = emailRecoveryModule.getRecoveryConfig(accountAddress1); + assertEq(recoveryConfig.delay, newDelay); + assertEq(recoveryConfig.expiry, newExpiry); + } + function test_UpdateRecoveryConfig_SucceedsWhenRecoveryWindowEqualsMinimumRecoveryWindow() public { - uint256 newDelay = 0 seconds; - uint256 newExpiry = 2 days; + uint256 newDelay = 18 hours; + uint256 newExpiry = 2 days + 18 hours; IEmailRecoveryManager.RecoveryConfig memory recoveryConfig = IEmailRecoveryManager.RecoveryConfig(newDelay, newExpiry); - vm.startPrank(accountAddress); + vm.startPrank(accountAddress1); + vm.expectEmit(); + emit IEmailRecoveryManager.RecoveryConfigUpdated(accountAddress1, newDelay, newExpiry); emailRecoveryModule.updateRecoveryConfig(recoveryConfig); - recoveryConfig = emailRecoveryModule.getRecoveryConfig(accountAddress); + recoveryConfig = emailRecoveryModule.getRecoveryConfig(accountAddress1); assertEq(recoveryConfig.delay, newDelay); assertEq(recoveryConfig.expiry, newExpiry); } @@ -107,14 +258,14 @@ contract EmailRecoveryManager_updateRecoveryConfig_Test is UnitBase { IEmailRecoveryManager.RecoveryConfig memory recoveryConfig = IEmailRecoveryManager.RecoveryConfig(newDelay, newExpiry); - vm.startPrank(accountAddress); + vm.startPrank(accountAddress1); vm.expectEmit(); emit IEmailRecoveryManager.RecoveryConfigUpdated( - accountAddress, recoveryConfig.delay, recoveryConfig.expiry + accountAddress1, recoveryConfig.delay, recoveryConfig.expiry ); emailRecoveryModule.updateRecoveryConfig(recoveryConfig); - recoveryConfig = emailRecoveryModule.getRecoveryConfig(accountAddress); + recoveryConfig = emailRecoveryModule.getRecoveryConfig(accountAddress1); assertEq(recoveryConfig.delay, newDelay); assertEq(recoveryConfig.expiry, newExpiry); } diff --git a/test/unit/EmailRecoveryModuleHarness.sol b/test/unit/EmailRecoveryModuleHarness.sol index 4c4395c1..ec84c8c1 100644 --- a/test/unit/EmailRecoveryModuleHarness.sol +++ b/test/unit/EmailRecoveryModuleHarness.sol @@ -1,9 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; import { SentinelListLib } from "sentinellist/SentinelList.sol"; -import { EnumerableGuardianMap, GuardianStatus } from "src/libraries/EnumerableGuardianMap.sol"; import { EmailRecoveryModule } from "src/modules/EmailRecoveryModule.sol"; contract EmailRecoveryModuleHarness is EmailRecoveryModule { @@ -13,11 +11,22 @@ contract EmailRecoveryModuleHarness is EmailRecoveryModule { address verifier, address dkimRegistry, address emailAuthImpl, - address subjectHandler, + address commandHandler, + uint256 minimumDelay, + address killSwitchAuthorizer, address validator, bytes4 selector ) - EmailRecoveryModule(verifier, dkimRegistry, emailAuthImpl, subjectHandler, validator, selector) + EmailRecoveryModule( + verifier, + dkimRegistry, + emailAuthImpl, + commandHandler, + minimumDelay, + killSwitchAuthorizer, + validator, + selector + ) { } function exposed_recover(address account, bytes calldata recoveryData) external { diff --git a/test/unit/GuardianManager/addGuardian.t.sol b/test/unit/GuardianManager/addGuardian.t.sol index 36b81e80..de3d1aaf 100644 --- a/test/unit/GuardianManager/addGuardian.t.sol +++ b/test/unit/GuardianManager/addGuardian.t.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; import { ModuleKitHelpers } from "modulekit/ModuleKit.sol"; import { MODULE_TYPE_EXECUTOR } from "modulekit/external/ERC7579.sol"; import { UnitBase } from "../UnitBase.t.sol"; @@ -15,31 +14,39 @@ contract GuardianManager_addGuardian_Test is UnitBase { super.setUp(); } + function test_AddGuardian_RevertWhen_KillSwitchEnabled() public { + vm.prank(killSwitchAuthorizer); + emailRecoveryModule.toggleKillSwitch(); + vm.stopPrank(); + + vm.startPrank(accountAddress1); + vm.expectRevert(IGuardianManager.KillSwitchEnabled.selector); + emailRecoveryModule.addGuardian(guardians1[0], guardianWeights[0]); + } + function test_AddGuardian_RevertWhen_AlreadyRecovering() public { - acceptGuardian(accountSalt1); - acceptGuardian(accountSalt2); + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); vm.warp(12 seconds); - handleRecovery(recoveryDataHash, accountSalt1); + handleRecovery(accountAddress1, guardians1[0], recoveryDataHash, emailRecoveryModuleAddress); - vm.startPrank(accountAddress); + vm.startPrank(accountAddress1); vm.expectRevert(IGuardianManager.RecoveryInProcess.selector); - emailRecoveryModule.addGuardian(guardians[0], guardianWeights[0]); + emailRecoveryModule.addGuardian(guardians1[0], guardianWeights[0]); } function test_AddGuardian_RevertWhen_SetupNotCalled() public { - vm.prank(accountAddress); - instance.uninstallModule(MODULE_TYPE_EXECUTOR, recoveryModuleAddress, ""); - vm.stopPrank(); + instance1.uninstallModule(MODULE_TYPE_EXECUTOR, emailRecoveryModuleAddress, ""); - vm.startPrank(accountAddress); + vm.startPrank(accountAddress1); vm.expectRevert(IGuardianManager.SetupNotCalled.selector); - emailRecoveryModule.addGuardian(guardians[0], guardianWeights[0]); + emailRecoveryModule.addGuardian(guardians1[0], guardianWeights[0]); } function test_AddGuardian_RevertWhen_InvalidGuardianAddress() public { address invalidGuardianAddress = address(0); - vm.startPrank(accountAddress); + vm.startPrank(accountAddress1); vm.expectRevert( abi.encodeWithSelector( IGuardianManager.InvalidGuardianAddress.selector, invalidGuardianAddress @@ -49,9 +56,9 @@ contract GuardianManager_addGuardian_Test is UnitBase { } function test_AddGuardian_RevertWhen_GuardianAddressIsAccountAddress() public { - address invalidGuardianAddress = accountAddress; + address invalidGuardianAddress = accountAddress1; - vm.startPrank(accountAddress); + vm.startPrank(accountAddress1); vm.expectRevert( abi.encodeWithSelector( IGuardianManager.InvalidGuardianAddress.selector, invalidGuardianAddress @@ -61,16 +68,16 @@ contract GuardianManager_addGuardian_Test is UnitBase { } function test_AddGuardian_RevertWhen_AddressAlreadyGuardian() public { - vm.startPrank(accountAddress); + vm.startPrank(accountAddress1); vm.expectRevert(IGuardianManager.AddressAlreadyGuardian.selector); - emailRecoveryModule.addGuardian(guardians[0], guardianWeights[0]); + emailRecoveryModule.addGuardian(guardians1[0], guardianWeights[0]); } function test_AddGuardian_RevertWhen_InvalidGuardianWeight() public { address newGuardian = address(1); uint256 invalidGuardianWeight = 0; - vm.startPrank(accountAddress); + vm.startPrank(accountAddress1); vm.expectRevert(IGuardianManager.InvalidGuardianWeight.selector); emailRecoveryModule.addGuardian(newGuardian, invalidGuardianWeight); } @@ -79,23 +86,23 @@ contract GuardianManager_addGuardian_Test is UnitBase { address newGuardian = address(1); uint256 newGuardianWeight = 1; - uint256 expectedGuardianCount = guardians.length + 1; + uint256 expectedGuardianCount = guardians1.length + 1; uint256 expectedTotalWeight = totalWeight + newGuardianWeight; - uint256 expectedAcceptedWeight = 0; // no guardians accepted + uint256 expectedAcceptedWeight = 0; // no guardians1 accepted uint256 expectedThreshold = threshold; // same threshold - vm.startPrank(accountAddress); + vm.startPrank(accountAddress1); vm.expectEmit(); - emit IGuardianManager.AddedGuardian(accountAddress, newGuardian, newGuardianWeight); + emit IGuardianManager.AddedGuardian(accountAddress1, newGuardian, newGuardianWeight); emailRecoveryModule.addGuardian(newGuardian, newGuardianWeight); GuardianStorage memory guardianStorage = - emailRecoveryModule.getGuardian(accountAddress, newGuardian); + emailRecoveryModule.getGuardian(accountAddress1, newGuardian); assertEq(uint256(guardianStorage.status), uint256(GuardianStatus.REQUESTED)); assertEq(guardianStorage.weight, newGuardianWeight); IGuardianManager.GuardianConfig memory guardianConfig = - emailRecoveryModule.getGuardianConfig(accountAddress); + emailRecoveryModule.getGuardianConfig(accountAddress1); assertEq(guardianConfig.guardianCount, expectedGuardianCount); assertEq(guardianConfig.totalWeight, expectedTotalWeight); assertEq(guardianConfig.acceptedWeight, expectedAcceptedWeight); diff --git a/test/unit/GuardianManager/changeThreshold.t.sol b/test/unit/GuardianManager/changeThreshold.t.sol index e768fe69..f5f19038 100644 --- a/test/unit/GuardianManager/changeThreshold.t.sol +++ b/test/unit/GuardianManager/changeThreshold.t.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; import { UnitBase } from "../UnitBase.t.sol"; import { IGuardianManager } from "src/interfaces/IGuardianManager.sol"; @@ -10,13 +9,23 @@ contract GuardianManager_changeThreshold_Test is UnitBase { super.setUp(); } + function test_RevertWhen_KillSwitchEnabled() public { + vm.prank(killSwitchAuthorizer); + emailRecoveryModule.toggleKillSwitch(); + vm.stopPrank(); + + vm.startPrank(accountAddress1); + vm.expectRevert(IGuardianManager.KillSwitchEnabled.selector); + emailRecoveryModule.changeThreshold(threshold); + } + function test_RevertWhen_AlreadyRecovering() public { - acceptGuardian(accountSalt1); - acceptGuardian(accountSalt2); + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); vm.warp(12 seconds); - handleRecovery(recoveryDataHash, accountSalt1); + handleRecovery(accountAddress1, guardians1[0], recoveryDataHash, emailRecoveryModuleAddress); - vm.startPrank(accountAddress); + vm.startPrank(accountAddress1); vm.expectRevert(IGuardianManager.RecoveryInProcess.selector); emailRecoveryModule.changeThreshold(threshold); } @@ -29,7 +38,7 @@ contract GuardianManager_changeThreshold_Test is UnitBase { function test_RevertWhen_ThresholdExceedsTotalWeight() public { uint256 highThreshold = totalWeight + 1; - vm.startPrank(accountAddress); + vm.startPrank(accountAddress1); vm.expectRevert( abi.encodeWithSelector( IGuardianManager.ThresholdExceedsTotalWeight.selector, highThreshold, totalWeight @@ -41,7 +50,7 @@ contract GuardianManager_changeThreshold_Test is UnitBase { function test_RevertWhen_ThresholdIsZero() public { uint256 zeroThreshold = 0; - vm.startPrank(accountAddress); + vm.startPrank(accountAddress1); vm.expectRevert(IGuardianManager.ThresholdCannotBeZero.selector); emailRecoveryModule.changeThreshold(zeroThreshold); } @@ -49,28 +58,28 @@ contract GuardianManager_changeThreshold_Test is UnitBase { function test_ChangeThreshold_IncreaseThreshold() public { uint256 newThreshold = threshold + 1; - vm.startPrank(accountAddress); + vm.startPrank(accountAddress1); vm.expectEmit(); - emit IGuardianManager.ChangedThreshold(accountAddress, newThreshold); + emit IGuardianManager.ChangedThreshold(accountAddress1, newThreshold); emailRecoveryModule.changeThreshold(newThreshold); IGuardianManager.GuardianConfig memory guardianConfig = - emailRecoveryModule.getGuardianConfig(accountAddress); - assertEq(guardianConfig.guardianCount, guardians.length); + emailRecoveryModule.getGuardianConfig(accountAddress1); + assertEq(guardianConfig.guardianCount, guardians1.length); assertEq(guardianConfig.threshold, newThreshold); } function test_ChangeThreshold_DecreaseThreshold() public { uint256 newThreshold = threshold - 1; - vm.startPrank(accountAddress); + vm.startPrank(accountAddress1); vm.expectEmit(); - emit IGuardianManager.ChangedThreshold(accountAddress, newThreshold); + emit IGuardianManager.ChangedThreshold(accountAddress1, newThreshold); emailRecoveryModule.changeThreshold(newThreshold); IGuardianManager.GuardianConfig memory guardianConfig = - emailRecoveryModule.getGuardianConfig(accountAddress); - assertEq(guardianConfig.guardianCount, guardians.length); + emailRecoveryModule.getGuardianConfig(accountAddress1); + assertEq(guardianConfig.guardianCount, guardians1.length); assertEq(guardianConfig.threshold, newThreshold); } } diff --git a/test/unit/GuardianManager/getAllGuardians.t.sol b/test/unit/GuardianManager/getAllGuardians.t.sol new file mode 100644 index 00000000..11023baf --- /dev/null +++ b/test/unit/GuardianManager/getAllGuardians.t.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { UnitBase } from "../UnitBase.t.sol"; + +contract GuardianManager_getAllGuardians_Test is UnitBase { + function setUp() public override { + super.setUp(); + } + + function test_getAllGuardians_Succeeds() public view { + address[] memory guardians = emailRecoveryModule.getAllGuardians(accountAddress1); + assertEq(guardians.length, guardians1.length); + assertEq(guardians[0], guardians1[0]); + assertEq(guardians[1], guardians1[1]); + assertEq(guardians[2], guardians1[2]); + } +} diff --git a/test/unit/GuardianManager/getGuardian.t.sol b/test/unit/GuardianManager/getGuardian.t.sol index 41d9d410..e0f8ee34 100644 --- a/test/unit/GuardianManager/getGuardian.t.sol +++ b/test/unit/GuardianManager/getGuardian.t.sol @@ -1,25 +1,24 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; import { GuardianStorage, GuardianStatus } from "src/libraries/EnumerableGuardianMap.sol"; import { UnitBase } from "../UnitBase.t.sol"; contract GuardianManager_getGuardian_Test is UnitBase { - address newGuardian = address(1); - uint256 newGuardianWeight = 1; + address public newGuardian = address(1); + uint256 public newGuardianWeight = 1; function setUp() public override { super.setUp(); - vm.startPrank(accountAddress); + vm.startPrank(accountAddress1); emailRecoveryModule.addGuardian(newGuardian, newGuardianWeight); vm.stopPrank(); } - function test_GetGuardian_Succeeds() public { + function test_GetGuardian_Succeeds() public view { GuardianStorage memory guardianStorage = - emailRecoveryModule.getGuardian(accountAddress, newGuardian); + emailRecoveryModule.getGuardian(accountAddress1, newGuardian); assertEq(uint256(guardianStorage.status), uint256(GuardianStatus.REQUESTED)); assertEq(guardianStorage.weight, newGuardianWeight); } diff --git a/test/unit/GuardianManager/getGuardianConfig.t.sol b/test/unit/GuardianManager/getGuardianConfig.t.sol index d54ce60c..154acf22 100644 --- a/test/unit/GuardianManager/getGuardianConfig.t.sol +++ b/test/unit/GuardianManager/getGuardianConfig.t.sol @@ -1,36 +1,34 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; import { IGuardianManager } from "src/interfaces/IGuardianManager.sol"; -import { GuardianStorage, GuardianStatus } from "src/libraries/EnumerableGuardianMap.sol"; import { UnitBase } from "../UnitBase.t.sol"; contract EmailRecoveryManager_getGuardianConfig_Test is UnitBase { - address newGuardian = address(1); - uint256 newGuardianWeight = 1; + address public newGuardian = address(1); + uint256 public newGuardianWeight = 1; - uint256 expectedGuardianCount; - uint256 expectedTotalWeight; - uint256 expectedAcceptedWeight; - uint256 expectedThreshold; + uint256 public expectedGuardianCount; + uint256 public expectedTotalWeight; + uint256 public expectedAcceptedWeight; + uint256 public expectedThreshold; function setUp() public override { super.setUp(); - expectedGuardianCount = guardians.length + 1; + expectedGuardianCount = guardians1.length + 1; expectedTotalWeight = totalWeight + newGuardianWeight; expectedAcceptedWeight = 0; // no guardians accepted expectedThreshold = threshold; - vm.startPrank(accountAddress); + vm.startPrank(accountAddress1); emailRecoveryModule.addGuardian(newGuardian, newGuardianWeight); vm.stopPrank(); } - function test_GetGuardianConfig_Succeeds() public { + function test_GetGuardianConfig_Succeeds() public view { IGuardianManager.GuardianConfig memory guardianConfig = - emailRecoveryModule.getGuardianConfig(accountAddress); + emailRecoveryModule.getGuardianConfig(accountAddress1); assertEq(guardianConfig.guardianCount, expectedGuardianCount); assertEq(guardianConfig.totalWeight, expectedTotalWeight); assertEq(guardianConfig.acceptedWeight, expectedAcceptedWeight); diff --git a/test/unit/GuardianManager/removeAllGuardians.t.sol b/test/unit/GuardianManager/removeAllGuardians.t.sol index 21f70159..b76a5d39 100644 --- a/test/unit/GuardianManager/removeAllGuardians.t.sol +++ b/test/unit/GuardianManager/removeAllGuardians.t.sol @@ -1,13 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; import { UnitBase } from "../UnitBase.t.sol"; -import { - EnumerableGuardianMap, - GuardianStorage, - GuardianStatus -} from "src/libraries/EnumerableGuardianMap.sol"; +import { GuardianStorage, GuardianStatus } from "src/libraries/EnumerableGuardianMap.sol"; contract GuardianManager_removeAllGuardians_Test is UnitBase { function setUp() public override { @@ -15,24 +10,24 @@ contract GuardianManager_removeAllGuardians_Test is UnitBase { } function test_RemoveAllGuardians_Succeeds() public { - acceptGuardian(accountSalt1); - acceptGuardian(accountSalt2); - acceptGuardian(accountSalt3); + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[2], emailRecoveryModuleAddress); - emailRecoveryModule.exposed_removeAllGuardians(accountAddress); + emailRecoveryModule.exposed_removeAllGuardians(accountAddress1); GuardianStorage memory guardianStorage1 = - emailRecoveryModule.getGuardian(accountAddress, guardian1); + emailRecoveryModule.getGuardian(accountAddress1, guardians1[0]); assertEq(uint256(guardianStorage1.status), uint256(GuardianStatus.NONE)); assertEq(guardianStorage1.weight, 0); GuardianStorage memory guardianStorage2 = - emailRecoveryModule.getGuardian(accountAddress, guardian2); + emailRecoveryModule.getGuardian(accountAddress1, guardians1[1]); assertEq(uint256(guardianStorage2.status), uint256(GuardianStatus.NONE)); assertEq(guardianStorage2.weight, 0); GuardianStorage memory guardianStorage3 = - emailRecoveryModule.getGuardian(accountAddress, guardian3); + emailRecoveryModule.getGuardian(accountAddress1, guardians1[2]); assertEq(uint256(guardianStorage3.status), uint256(GuardianStatus.NONE)); assertEq(guardianStorage3.weight, 0); } diff --git a/test/unit/GuardianManager/removeGuardian.t.sol b/test/unit/GuardianManager/removeGuardian.t.sol index bbb606a9..7e26f320 100644 --- a/test/unit/GuardianManager/removeGuardian.t.sol +++ b/test/unit/GuardianManager/removeGuardian.t.sol @@ -1,8 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; - import { UnitBase } from "../UnitBase.t.sol"; import { GuardianStorage, GuardianStatus } from "src/libraries/EnumerableGuardianMap.sol"; import { IGuardianManager } from "src/interfaces/IGuardianManager.sol"; @@ -12,29 +10,41 @@ contract GuardianManager_removeGuardian_Test is UnitBase { super.setUp(); } + function test_RemoveGuardian_RevertWhen_KillSwitchEnabled() public { + address guardian = guardians1[0]; + + vm.prank(killSwitchAuthorizer); + emailRecoveryModule.toggleKillSwitch(); + vm.stopPrank(); + + vm.startPrank(accountAddress1); + vm.expectRevert(IGuardianManager.KillSwitchEnabled.selector); + emailRecoveryModule.removeGuardian(guardian); + } + function test_RemoveGuardian_RevertWhen_AlreadyRecovering() public { - address guardian = guardian1; + address guardian = guardians1[0]; - acceptGuardian(accountSalt1); - acceptGuardian(accountSalt2); + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); vm.warp(12 seconds); - handleRecovery(recoveryDataHash, accountSalt1); + handleRecovery(accountAddress1, guardians1[0], recoveryDataHash, emailRecoveryModuleAddress); - vm.startPrank(accountAddress); + vm.startPrank(accountAddress1); vm.expectRevert(IGuardianManager.RecoveryInProcess.selector); emailRecoveryModule.removeGuardian(guardian); } function test_RemoveGuardian_RevertWhen_AddressNotGuardianForAccount() public { - address unauthorizedAccount = guardian1; + address unauthorizedAccount = guardians1[0]; vm.startPrank(unauthorizedAccount); vm.expectRevert(IGuardianManager.AddressNotGuardianForAccount.selector); - emailRecoveryModule.removeGuardian(guardian1); + emailRecoveryModule.removeGuardian(guardians1[0]); } function test_RemoveGuardian_RevertWhen_ThresholdExceedsTotalWeight() public { - address guardian = guardian2; // guardian 2 weight is 2 + address guardian = guardians1[1]; // guardian 2 weight is 2 // threshold = 3 // totalWeight = 4 // weight = 2 @@ -43,9 +53,9 @@ contract GuardianManager_removeGuardian_Test is UnitBase { // (totalWeight - weight == 4 - 2) = 2 // (weight < threshold == 2 < 3) = fails - acceptGuardian(accountSalt1); + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); - vm.startPrank(accountAddress); + vm.startPrank(accountAddress1); vm.expectRevert( abi.encodeWithSelector( IGuardianManager.ThresholdExceedsTotalWeight.selector, @@ -57,7 +67,7 @@ contract GuardianManager_removeGuardian_Test is UnitBase { } function test_RemoveGuardian_Succeeds() public { - address guardian = guardian1; // guardian 1 weight is 1 + address guardian = guardians1[0]; // guardian 1 weight is 1 // threshold = 3 // totalWeight = 4 // weight = 1 @@ -66,19 +76,19 @@ contract GuardianManager_removeGuardian_Test is UnitBase { // (totalWeight - weight == 4 - 1) = 3 // (weight < threshold == 3 < 3) = succeeds - vm.startPrank(accountAddress); + vm.startPrank(accountAddress1); vm.expectEmit(); - emit IGuardianManager.RemovedGuardian(accountAddress, guardian, guardianWeights[0]); + emit IGuardianManager.RemovedGuardian(accountAddress1, guardian, guardianWeights[0]); emailRecoveryModule.removeGuardian(guardian); GuardianStorage memory guardianStorage = - emailRecoveryModule.getGuardian(accountAddress, guardian); + emailRecoveryModule.getGuardian(accountAddress1, guardian); assertEq(uint256(guardianStorage.status), uint256(GuardianStatus.NONE)); assertEq(guardianStorage.weight, 0); IGuardianManager.GuardianConfig memory guardianConfig = - emailRecoveryModule.getGuardianConfig(accountAddress); - assertEq(guardianConfig.guardianCount, guardians.length - 1); + emailRecoveryModule.getGuardianConfig(accountAddress1); + assertEq(guardianConfig.guardianCount, guardians1.length - 1); assertEq(guardianConfig.totalWeight, totalWeight - guardianWeights[0]); assertEq(guardianConfig.acceptedWeight, 0); // 1 - 1 = 0 @@ -86,7 +96,7 @@ contract GuardianManager_removeGuardian_Test is UnitBase { } function test_RemoveGuardian_SucceedsWithAcceptedGuardian() public { - address guardian = guardian1; // guardian 1 weight is 1 + address guardian = guardians1[0]; // guardian 1 weight is 1 // threshold = 3 // totalWeight = 4 // weight = 1 @@ -95,22 +105,22 @@ contract GuardianManager_removeGuardian_Test is UnitBase { // (totalWeight - weight == 4 - 1) = 3 // (weight < threshold == 3 < 3) = succeeds - acceptGuardian(accountSalt1); // weight = 1 - acceptGuardian(accountSalt2); // weight = 2 + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); // weight = 1 + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); // weight = 2 - vm.startPrank(accountAddress); + vm.startPrank(accountAddress1); vm.expectEmit(); - emit IGuardianManager.RemovedGuardian(accountAddress, guardian, guardianWeights[0]); + emit IGuardianManager.RemovedGuardian(accountAddress1, guardian, guardianWeights[0]); emailRecoveryModule.removeGuardian(guardian); GuardianStorage memory guardianStorage = - emailRecoveryModule.getGuardian(accountAddress, guardian); + emailRecoveryModule.getGuardian(accountAddress1, guardian); assertEq(uint256(guardianStorage.status), uint256(GuardianStatus.NONE)); assertEq(guardianStorage.weight, 0); IGuardianManager.GuardianConfig memory guardianConfig = - emailRecoveryModule.getGuardianConfig(accountAddress); - assertEq(guardianConfig.guardianCount, guardians.length - 1); + emailRecoveryModule.getGuardianConfig(accountAddress1); + assertEq(guardianConfig.guardianCount, guardians1.length - 1); assertEq(guardianConfig.totalWeight, totalWeight - guardianWeights[0]); // Accepted weight before guardian is removed = 3 diff --git a/test/unit/GuardianManager/setupGuardians.t.sol b/test/unit/GuardianManager/setupGuardians.t.sol index b9f22cc8..9d8083dd 100644 --- a/test/unit/GuardianManager/setupGuardians.t.sol +++ b/test/unit/GuardianManager/setupGuardians.t.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; import { ModuleKitHelpers } from "modulekit/ModuleKit.sol"; import { MODULE_TYPE_EXECUTOR } from "modulekit/external/ERC7579.sol"; import { UnitBase } from "../UnitBase.t.sol"; @@ -25,12 +24,12 @@ contract GuardianManager_setupGuardians_Test is UnitBase { vm.expectRevert( abi.encodeWithSelector( IGuardianManager.IncorrectNumberOfWeights.selector, - guardians.length, + guardians1.length, invalidGuardianWeights.length ) ); emailRecoveryModule.exposed_setupGuardians( - accountAddress, guardians, invalidGuardianWeights, threshold + accountAddress1, guardians1, invalidGuardianWeights, threshold ); } @@ -39,60 +38,56 @@ contract GuardianManager_setupGuardians_Test is UnitBase { vm.expectRevert(IGuardianManager.ThresholdCannotBeZero.selector); emailRecoveryModule.exposed_setupGuardians( - accountAddress, guardians, guardianWeights, zeroThreshold + accountAddress1, guardians1, guardianWeights, zeroThreshold ); } function test_SetupGuardians_RevertWhen_InvalidGuardianAddress() public { - guardians[0] = address(0); + guardians1[0] = address(0); vm.expectRevert( - abi.encodeWithSelector(IGuardianManager.InvalidGuardianAddress.selector, guardians[0]) + abi.encodeWithSelector(IGuardianManager.InvalidGuardianAddress.selector, guardians1[0]) ); emailRecoveryModule.exposed_setupGuardians( - accountAddress, guardians, guardianWeights, threshold + accountAddress1, guardians1, guardianWeights, threshold ); } function test_SetupGuardians_RevertWhen_GuardianAddressIsAccountAddress() public { - guardians[0] = accountAddress; + guardians1[0] = accountAddress1; vm.expectRevert( - abi.encodeWithSelector(IGuardianManager.InvalidGuardianAddress.selector, guardians[0]) + abi.encodeWithSelector(IGuardianManager.InvalidGuardianAddress.selector, guardians1[0]) ); emailRecoveryModule.exposed_setupGuardians( - accountAddress, guardians, guardianWeights, threshold + accountAddress1, guardians1, guardianWeights, threshold ); } function test_SetupGuardians_RevertWhen_InvalidGuardianWeight() public { - vm.prank(accountAddress); - instance.uninstallModule(MODULE_TYPE_EXECUTOR, recoveryModuleAddress, ""); - vm.stopPrank(); + instance1.uninstallModule(MODULE_TYPE_EXECUTOR, emailRecoveryModuleAddress, ""); guardianWeights[0] = 0; vm.expectRevert(IGuardianManager.InvalidGuardianWeight.selector); emailRecoveryModule.exposed_setupGuardians( - accountAddress, guardians, guardianWeights, threshold + accountAddress1, guardians1, guardianWeights, threshold ); } function test_SetupGuardians_RevertWhen_AddressAlreadyGuardian() public { - guardians[0] = guardians[1]; + guardians1[0] = guardians1[1]; vm.expectRevert(IGuardianManager.AddressAlreadyGuardian.selector); emailRecoveryModule.exposed_setupGuardians( - accountAddress, guardians, guardianWeights, threshold + accountAddress1, guardians1, guardianWeights, threshold ); } function test_SetupGuardians_RevertWhen_ThresholdExceedsTotalWeight() public { uint256 invalidThreshold = totalWeight + 1; - vm.prank(accountAddress); - instance.uninstallModule(MODULE_TYPE_EXECUTOR, recoveryModuleAddress, ""); - vm.stopPrank(); + instance1.uninstallModule(MODULE_TYPE_EXECUTOR, emailRecoveryModuleAddress, ""); vm.expectRevert( abi.encodeWithSelector( @@ -100,30 +95,28 @@ contract GuardianManager_setupGuardians_Test is UnitBase { ) ); emailRecoveryModule.exposed_setupGuardians( - accountAddress, guardians, guardianWeights, invalidThreshold + accountAddress1, guardians1, guardianWeights, invalidThreshold ); } function test_SetupGuardians_Succeeds() public { - uint256 expectedGuardianCount = guardians.length; + uint256 expectedGuardianCount = guardians1.length; uint256 expectedTotalWeight = totalWeight; uint256 expectedAcceptedWeight = 0; // no guardians accepted uint256 expectedThreshold = threshold; - vm.prank(accountAddress); - instance.uninstallModule(MODULE_TYPE_EXECUTOR, recoveryModuleAddress, ""); - vm.stopPrank(); + instance1.uninstallModule(MODULE_TYPE_EXECUTOR, emailRecoveryModuleAddress, ""); (uint256 guardianCount, uint256 totalWeight) = emailRecoveryModule.exposed_setupGuardians( - accountAddress, guardians, guardianWeights, threshold + accountAddress1, guardians1, guardianWeights, threshold ); GuardianStorage memory guardianStorage1 = - emailRecoveryModule.getGuardian(accountAddress, guardians[0]); + emailRecoveryModule.getGuardian(accountAddress1, guardians1[0]); GuardianStorage memory guardianStorage2 = - emailRecoveryModule.getGuardian(accountAddress, guardians[1]); + emailRecoveryModule.getGuardian(accountAddress1, guardians1[1]); GuardianStorage memory guardianStorage3 = - emailRecoveryModule.getGuardian(accountAddress, guardians[2]); + emailRecoveryModule.getGuardian(accountAddress1, guardians1[2]); assertEq(uint256(guardianStorage1.status), uint256(GuardianStatus.REQUESTED)); assertEq(guardianStorage1.weight, guardianWeights[0]); assertEq(uint256(guardianStorage2.status), uint256(GuardianStatus.REQUESTED)); @@ -135,7 +128,7 @@ contract GuardianManager_setupGuardians_Test is UnitBase { assertEq(totalWeight, expectedTotalWeight); IGuardianManager.GuardianConfig memory guardianConfig = - emailRecoveryModule.getGuardianConfig(accountAddress); + emailRecoveryModule.getGuardianConfig(accountAddress1); assertEq(guardianConfig.guardianCount, expectedGuardianCount); assertEq(guardianConfig.totalWeight, expectedTotalWeight); assertEq(guardianConfig.acceptedWeight, expectedAcceptedWeight); diff --git a/test/unit/GuardianManager/updateGuardianStatus.t.sol b/test/unit/GuardianManager/updateGuardianStatus.t.sol index 8a75bc58..bb62269c 100644 --- a/test/unit/GuardianManager/updateGuardianStatus.t.sol +++ b/test/unit/GuardianManager/updateGuardianStatus.t.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; import { ModuleKitHelpers } from "modulekit/ModuleKit.sol"; import { MODULE_TYPE_EXECUTOR } from "modulekit/external/ERC7579.sol"; import { GuardianStorage, GuardianStatus } from "src/libraries/EnumerableGuardianMap.sol"; @@ -14,9 +13,7 @@ contract GuardianManager_updateGuardianStatus_Test is UnitBase { function setUp() public override { super.setUp(); - vm.prank(accountAddress); - instance.uninstallModule(MODULE_TYPE_EXECUTOR, recoveryModuleAddress, ""); - vm.stopPrank(); + instance1.uninstallModule(MODULE_TYPE_EXECUTOR, emailRecoveryModuleAddress, ""); } function test_UpdateGuardianStatus_RevertWhen_StatusIsAlreadyNONE() public { @@ -27,46 +24,46 @@ contract GuardianManager_updateGuardianStatus_Test is UnitBase { IGuardianManager.StatusCannotBeTheSame.selector, uint256(newStatus) ) ); - emailRecoveryModule.exposed_updateGuardianStatus(accountAddress, guardian1, newStatus); + emailRecoveryModule.exposed_updateGuardianStatus(accountAddress1, guardians1[0], newStatus); } function test_UpdateGuardianStatus_RevertWhen_StatusIsAlreadyREQUESTED() public { GuardianStatus newStatus = GuardianStatus.REQUESTED; - emailRecoveryModule.exposed_updateGuardianStatus(accountAddress, guardian1, newStatus); + emailRecoveryModule.exposed_updateGuardianStatus(accountAddress1, guardians1[0], newStatus); vm.expectRevert( abi.encodeWithSelector( IGuardianManager.StatusCannotBeTheSame.selector, uint256(newStatus) ) ); - emailRecoveryModule.exposed_updateGuardianStatus(accountAddress, guardian1, newStatus); + emailRecoveryModule.exposed_updateGuardianStatus(accountAddress1, guardians1[0], newStatus); } function test_UpdateGuardianStatus_RevertWhen_StatusIsAlreadyACCEPTED() public { GuardianStatus newStatus = GuardianStatus.ACCEPTED; - emailRecoveryModule.exposed_updateGuardianStatus(accountAddress, guardian1, newStatus); + emailRecoveryModule.exposed_updateGuardianStatus(accountAddress1, guardians1[0], newStatus); vm.expectRevert( abi.encodeWithSelector( IGuardianManager.StatusCannotBeTheSame.selector, uint256(newStatus) ) ); - emailRecoveryModule.exposed_updateGuardianStatus(accountAddress, guardian1, newStatus); + emailRecoveryModule.exposed_updateGuardianStatus(accountAddress1, guardians1[0], newStatus); } function test_UpdateGuardianStatus_UpdatesStatusToNONE() public { GuardianStatus newStatus = GuardianStatus.NONE; emailRecoveryModule.exposed_updateGuardianStatus( - accountAddress, guardian1, GuardianStatus.REQUESTED + accountAddress1, guardians1[0], GuardianStatus.REQUESTED ); - emailRecoveryModule.exposed_updateGuardianStatus(accountAddress, guardian1, newStatus); + emailRecoveryModule.exposed_updateGuardianStatus(accountAddress1, guardians1[0], newStatus); GuardianStorage memory guardianStorage = - emailRecoveryModule.getGuardian(accountAddress, guardian1); + emailRecoveryModule.getGuardian(accountAddress1, guardians1[0]); assertEq(uint256(guardianStorage.status), uint256(newStatus)); assertEq(guardianStorage.weight, 0); } @@ -74,10 +71,10 @@ contract GuardianManager_updateGuardianStatus_Test is UnitBase { function test_UpdateGuardianStatus_UpdatesStatusToREQUESTED() public { GuardianStatus newStatus = GuardianStatus.REQUESTED; - emailRecoveryModule.exposed_updateGuardianStatus(accountAddress, guardian1, newStatus); + emailRecoveryModule.exposed_updateGuardianStatus(accountAddress1, guardians1[0], newStatus); GuardianStorage memory guardianStorage = - emailRecoveryModule.getGuardian(accountAddress, guardian1); + emailRecoveryModule.getGuardian(accountAddress1, guardians1[0]); assertEq(uint256(guardianStorage.status), uint256(newStatus)); assertEq(guardianStorage.weight, 0); } @@ -86,11 +83,11 @@ contract GuardianManager_updateGuardianStatus_Test is UnitBase { GuardianStatus newStatus = GuardianStatus.ACCEPTED; vm.expectEmit(); - emit IGuardianManager.GuardianStatusUpdated(accountAddress, guardian1, newStatus); - emailRecoveryModule.exposed_updateGuardianStatus(accountAddress, guardian1, newStatus); + emit IGuardianManager.GuardianStatusUpdated(accountAddress1, guardians1[0], newStatus); + emailRecoveryModule.exposed_updateGuardianStatus(accountAddress1, guardians1[0], newStatus); GuardianStorage memory guardianStorage = - emailRecoveryModule.getGuardian(accountAddress, guardian1); + emailRecoveryModule.getGuardian(accountAddress1, guardians1[0]); assertEq(uint256(guardianStorage.status), uint256(newStatus)); assertEq(guardianStorage.weight, 0); } diff --git a/test/unit/SafeEmailRecoveryModuleHarness.sol b/test/unit/SafeEmailRecoveryModuleHarness.sol new file mode 100644 index 00000000..26abc774 --- /dev/null +++ b/test/unit/SafeEmailRecoveryModuleHarness.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { SafeEmailRecoveryModule } from "src/modules/SafeEmailRecoveryModule.sol"; + +contract SafeEmailRecoveryModuleHarness is SafeEmailRecoveryModule { + constructor( + address verifier, + address dkimRegistry, + address emailAuthImpl, + address commandHandler, + uint256 minimumDelay, + address killSwitchAuthorizer + ) + SafeEmailRecoveryModule( + verifier, + dkimRegistry, + emailAuthImpl, + commandHandler, + minimumDelay, + killSwitchAuthorizer + ) + { } + + function exposed_recover(address account, bytes calldata recoveryData) external { + recover(account, recoveryData); + } +} diff --git a/test/unit/SafeRecoverySubjectHandlerHarness.sol b/test/unit/SafeRecoveryCommandHandlerHarness.sol similarity index 52% rename from test/unit/SafeRecoverySubjectHandlerHarness.sol rename to test/unit/SafeRecoveryCommandHandlerHarness.sol index a925ed78..95a3f9af 100644 --- a/test/unit/SafeRecoverySubjectHandlerHarness.sol +++ b/test/unit/SafeRecoveryCommandHandlerHarness.sol @@ -1,11 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; -import { SafeRecoverySubjectHandler } from "src/handlers/SafeRecoverySubjectHandler.sol"; +import { SafeRecoveryCommandHandler } from "src/handlers/SafeRecoveryCommandHandler.sol"; -contract SafeRecoverySubjectHandlerHarness is SafeRecoverySubjectHandler { - constructor() SafeRecoverySubjectHandler() { } +contract SafeRecoveryCommandHandlerHarness is SafeRecoveryCommandHandler { + constructor() SafeRecoveryCommandHandler() { } function exposed_getPreviousOwnerInLinkedList( address safe, diff --git a/test/unit/SafeUnitBase.t.sol b/test/unit/SafeUnitBase.t.sol index 8eac547f..2daf4c18 100644 --- a/test/unit/SafeUnitBase.t.sol +++ b/test/unit/SafeUnitBase.t.sol @@ -1,52 +1,25 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; import { ModuleKitHelpers } from "modulekit/ModuleKit.sol"; import { MODULE_TYPE_EXECUTOR } from "modulekit/external/ERC7579.sol"; -import { EmailAuthMsg, EmailProof } from "ether-email-auth/packages/contracts/src/EmailAuth.sol"; -import { SubjectUtils } from "ether-email-auth/packages/contracts/src/libraries/SubjectUtils.sol"; import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; +import { Create2 } from "@openzeppelin/contracts/utils/Create2.sol"; -import { EmailRecoveryManager } from "src/EmailRecoveryManager.sol"; +import { AccountHidingRecoveryCommandHandler } from + "src/handlers/AccountHidingRecoveryCommandHandler.sol"; import { UniversalEmailRecoveryModule } from "src/modules/UniversalEmailRecoveryModule.sol"; -import { SafeRecoverySubjectHandlerHarness } from "./SafeRecoverySubjectHandlerHarness.sol"; import { EmailRecoveryFactory } from "src/factories/EmailRecoveryFactory.sol"; -import { IntegrationBase } from "../integration/IntegrationBase.t.sol"; +import { BaseTest, CommandHandlerType } from "../Base.t.sol"; -abstract contract SafeUnitBase is IntegrationBase { +abstract contract SafeUnitBase is BaseTest { using ModuleKitHelpers for *; using Strings for uint256; - EmailRecoveryFactory emailRecoveryFactory; - SafeRecoverySubjectHandlerHarness safeRecoverySubjectHandler; - UniversalEmailRecoveryModule emailRecoveryModule; - address recoveryModuleAddress; - - bytes4 functionSelector; - bytes recoveryData; - bytes32 recoveryDataHash; - bytes isInstalledContext; - - /** - * Helper function to return if current account type is safe or not - */ - function isAccountTypeSafe() public returns (bool) { - string memory currentAccountType = vm.envOr("ACCOUNT_TYPE", string("")); - if (Strings.equal(currentAccountType, "SAFE")) { - return true; - } else { - return false; - } - } - - function skipIfNotSafeAccountType() public { - if (isAccountTypeSafe()) { - vm.skip(false); - } else { - vm.skip(true); - } - } + EmailRecoveryFactory public emailRecoveryFactory; + address public commandHandlerAddress; + UniversalEmailRecoveryModule public emailRecoveryModule; + address public emailRecoveryModuleAddress; function setUp() public virtual override { if (!isAccountTypeSafe()) { @@ -54,38 +27,9 @@ abstract contract SafeUnitBase is IntegrationBase { } super.setUp(); - // Deploy handler, manager and module - safeRecoverySubjectHandler = new SafeRecoverySubjectHandlerHarness(); - emailRecoveryFactory = new EmailRecoveryFactory(address(verifier), address(emailAuthImpl)); - - emailRecoveryModule = new UniversalEmailRecoveryModule( - address(verifier), - address(dkimRegistry), - address(emailAuthImpl), - address(safeRecoverySubjectHandler) - ); - recoveryModuleAddress = address(emailRecoveryModule); - - functionSelector = bytes4(keccak256(bytes("swapOwner(address,address,address)"))); - address previousOwnerInLinkedList = address(1); - // address previousOwnerInLinkedList = - // safeRecoverySubjectHandler.previousOwnerInLinkedList(accountAddress, owner); - bytes memory swapOwnerCalldata = abi.encodeWithSignature( - "swapOwner(address,address,address)", previousOwnerInLinkedList, owner1, newOwner1 - ); - bytes memory recoveryData = abi.encode(accountAddress1, swapOwnerCalldata); - recoveryDataHash = keccak256(recoveryData); - isInstalledContext = bytes("0"); - - // Compute guardian addresses - guardians1 = new address[](3); - guardians1[0] = emailRecoveryModule.computeEmailAuthAddress(instance1.account, accountSalt1); - guardians1[1] = emailRecoveryModule.computeEmailAuthAddress(instance1.account, accountSalt2); - guardians1[2] = emailRecoveryModule.computeEmailAuthAddress(instance1.account, accountSalt3); - instance1.installModule({ moduleTypeId: MODULE_TYPE_EXECUTOR, - module: recoveryModuleAddress, + module: emailRecoveryModuleAddress, data: abi.encode( accountAddress1, isInstalledContext, @@ -99,74 +43,43 @@ abstract contract SafeUnitBase is IntegrationBase { }); } - function generateMockEmailProof( - string memory subject, - bytes32 nullifier, + function computeEmailAuthAddress( + address account, bytes32 accountSalt ) public view - returns (EmailProof memory) + override + returns (address) { - EmailProof memory emailProof; - emailProof.domainName = "gmail.com"; - emailProof.publicKeyHash = bytes32( - vm.parseUint( - "6632353713085157925504008443078919716322386156160602218536961028046468237192" - ) - ); - emailProof.timestamp = block.timestamp; - emailProof.maskedSubject = subject; - emailProof.emailNullifier = nullifier; - emailProof.accountSalt = accountSalt; - emailProof.isCodeExist = true; - emailProof.proof = bytes("0"); - - return emailProof; + return emailRecoveryModule.computeEmailAuthAddress(account, accountSalt); } - function acceptGuardian(address account, bytes32 accountSalt) public { - string memory accountString = SubjectUtils.addressToChecksumHexString(account); - string memory subject = string.concat("Accept guardian request for ", accountString); + function deployModule(bytes memory handlerBytecode) public override { + bytes32 commandHandlerSalt = bytes32(uint256(0)); + commandHandlerAddress = Create2.deploy(0, commandHandlerSalt, handlerBytecode); - bytes32 nullifier = keccak256(abi.encode("nullifier 1")); - uint256 templateIdx = 0; - EmailProof memory emailProof = generateMockEmailProof(subject, nullifier, accountSalt); - - bytes[] memory subjectParamsForAcceptance = new bytes[](1); - subjectParamsForAcceptance[0] = abi.encode(account); + emailRecoveryModule = new UniversalEmailRecoveryModule( + address(verifier), + address(dkimRegistry), + address(emailAuthImpl), + commandHandlerAddress, + minimumDelay, + killSwitchAuthorizer + ); + emailRecoveryModuleAddress = address(emailRecoveryModule); - EmailAuthMsg memory emailAuthMsg = EmailAuthMsg({ - templateId: emailRecoveryModule.computeAcceptanceTemplateId(templateIdx), - subjectParams: subjectParamsForAcceptance, - skipedSubjectPrefix: 0, - proof: emailProof - }); - emailRecoveryModule.handleAcceptance(emailAuthMsg, templateIdx); + if (getCommandHandlerType() == CommandHandlerType.AccountHidingRecoveryCommandHandler) { + AccountHidingRecoveryCommandHandler(commandHandlerAddress).storeAccountHash( + accountAddress1 + ); + } } - function handleRecovery(address account, bytes32 accountSalt) public { - string memory accountString = SubjectUtils.addressToChecksumHexString(account); - string memory recoveryDataHashString = uint256(recoveryDataHash).toHexString(32); - - string memory subjectPart1 = string.concat("Recover account ", accountString); - string memory subjectPart2 = string.concat(" using recovery hash ", recoveryDataHashString); - string memory subject = string.concat(subjectPart1, subjectPart2); - bytes32 nullifier = keccak256(abi.encode("nullifier 2")); - uint256 templateIdx = 0; - - EmailProof memory emailProof = generateMockEmailProof(subject, nullifier, accountSalt); - - bytes[] memory subjectParamsForRecovery = new bytes[](2); - subjectParamsForRecovery[0] = abi.encode(account); - subjectParamsForRecovery[1] = abi.encode(recoveryDataHashString); - - EmailAuthMsg memory emailAuthMsg = EmailAuthMsg({ - templateId: emailRecoveryModule.computeRecoveryTemplateId(templateIdx), - subjectParams: subjectParamsForRecovery, - skipedSubjectPrefix: 0, - proof: emailProof - }); - emailRecoveryModule.handleRecovery(emailAuthMsg, templateIdx); + function setRecoveryData() public override { + functionSelector = bytes4(keccak256(bytes("swapOwner(address,address,address)"))); + recoveryCalldata = abi.encodeWithSelector(functionSelector, address(1), owner1, newOwner1); + recoveryData = abi.encode(accountAddress1, recoveryCalldata); + recoveryDataHash = keccak256(recoveryData); } } diff --git a/test/unit/UnitBase.t.sol b/test/unit/UnitBase.t.sol index 778afcca..ae8fcd59 100644 --- a/test/unit/UnitBase.t.sol +++ b/test/unit/UnitBase.t.sol @@ -1,185 +1,53 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; -import { Test } from "forge-std/Test.sol"; -import { - RhinestoneModuleKit, - AccountInstance, - ModuleKitHelpers, - ModuleKitUserOp -} from "modulekit/ModuleKit.sol"; +import { ModuleKitHelpers, ModuleKitUserOp } from "modulekit/ModuleKit.sol"; import { MODULE_TYPE_EXECUTOR, MODULE_TYPE_VALIDATOR } from "modulekit/external/ERC7579.sol"; -import { ECDSAOwnedDKIMRegistry } from - "ether-email-auth/packages/contracts/src/utils/ECDSAOwnedDKIMRegistry.sol"; -import { SubjectUtils } from "ether-email-auth/packages/contracts/src/libraries/SubjectUtils.sol"; -import { - EmailAuth, - EmailAuthMsg, - EmailProof -} from "ether-email-auth/packages/contracts/src/EmailAuth.sol"; import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; -import { ECDSA } from "solady/utils/ECDSA.sol"; +import { Create2 } from "@openzeppelin/contracts/utils/Create2.sol"; -import { EmailRecoverySubjectHandler } from "src/handlers/EmailRecoverySubjectHandler.sol"; +import { BaseTest, CommandHandlerType } from "test/Base.t.sol"; +import { AccountHidingRecoveryCommandHandler } from + "src/handlers/AccountHidingRecoveryCommandHandler.sol"; import { UniversalEmailRecoveryModuleHarness } from "./UniversalEmailRecoveryModuleHarness.sol"; -import { EmailRecoveryManager } from "src/EmailRecoveryManager.sol"; import { EmailRecoveryFactory } from "src/factories/EmailRecoveryFactory.sol"; import { EmailRecoveryUniversalFactory } from "src/factories/EmailRecoveryUniversalFactory.sol"; -import { OwnableValidator } from "src/test/OwnableValidator.sol"; -import { MockGroth16Verifier } from "src/test/MockGroth16Verifier.sol"; -import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; -abstract contract UnitBase is RhinestoneModuleKit, Test { +abstract contract UnitBase is BaseTest { using ModuleKitHelpers for *; using ModuleKitUserOp for *; using Strings for uint256; - // ZK Email contracts and variables - address zkEmailDeployer = vm.addr(1); - ECDSAOwnedDKIMRegistry dkimRegistry; - MockGroth16Verifier verifier; - EmailAuth emailAuthImpl; + EmailRecoveryFactory public emailRecoveryFactory; + EmailRecoveryUniversalFactory public emailRecoveryUniversalFactory; + address public commandHandlerAddress; + UniversalEmailRecoveryModuleHarness public emailRecoveryModule; - EmailRecoveryFactory emailRecoveryFactory; - EmailRecoveryUniversalFactory emailRecoveryUniversalFactory; - EmailRecoverySubjectHandler emailRecoveryHandler; - UniversalEmailRecoveryModuleHarness emailRecoveryModule; + address public emailRecoveryModuleAddress; - address recoveryModuleAddress; - address validatorAddress; - - OwnableValidator validator; - bytes isInstalledContext; - bytes4 functionSelector; - bytes recoveryData; - bytes32 recoveryDataHash; - - // account and owners - AccountInstance instance; - address accountAddress; - address owner; - address newOwner; - - // recovery config - address[] guardians; - address guardian1; - address guardian2; - address guardian3; - uint256[] guardianWeights; - uint256 totalWeight; - uint256 delay; - uint256 expiry; - uint256 threshold; - uint256 templateIdx; - - // Account salts - bytes32 accountSalt1; - bytes32 accountSalt2; - bytes32 accountSalt3; - - string selector = "12345"; - string domainName = "gmail.com"; - bytes32 publicKeyHash = 0x0ea9c777dc7110e5a9e89b13f0cfc540e3845ba120b2b6dc24024d61488d4788; - - function setUp() public virtual { - init(); - - // Create ZK Email contracts - vm.startPrank(zkEmailDeployer); - { - ECDSAOwnedDKIMRegistry dkimImpl = new ECDSAOwnedDKIMRegistry(); - ERC1967Proxy dkimProxy = new ERC1967Proxy( - address(dkimImpl), - abi.encodeCall(dkimImpl.initialize, (zkEmailDeployer, zkEmailDeployer)) - ); - dkimRegistry = ECDSAOwnedDKIMRegistry(address(dkimProxy)); - } - string memory signedMsg = dkimRegistry.computeSignedMsg( - dkimRegistry.SET_PREFIX(), selector, domainName, publicKeyHash - ); - bytes32 digest = ECDSA.toEthSignedMessageHash(bytes(signedMsg)); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(1, digest); - bytes memory signature = abi.encodePacked(r, s, v); - dkimRegistry.setDKIMPublicKeyHash(selector, domainName, publicKeyHash, signature); - - verifier = new MockGroth16Verifier(); - emailAuthImpl = new EmailAuth(); - vm.stopPrank(); - - // create owners - owner = vm.createWallet("owner").addr; - newOwner = vm.createWallet("newOwner").addr; - address[] memory owners = new address[](1); - owners[0] = owner; - - // Deploy handler, manager and module - emailRecoveryHandler = new EmailRecoverySubjectHandler(); - emailRecoveryFactory = new EmailRecoveryFactory(address(verifier), address(emailAuthImpl)); - emailRecoveryUniversalFactory = - new EmailRecoveryUniversalFactory(address(verifier), address(emailAuthImpl)); - - emailRecoveryModule = new UniversalEmailRecoveryModuleHarness( - address(verifier), - address(dkimRegistry), - address(emailAuthImpl), - address(emailRecoveryHandler) - ); - recoveryModuleAddress = address(emailRecoveryModule); - - // Deploy and fund the account - instance = makeAccountInstance("account"); - accountAddress = instance.account; - vm.deal(address(instance.account), 10 ether); - - accountSalt1 = keccak256(abi.encode("account salt 1")); - accountSalt2 = keccak256(abi.encode("account salt 2")); - accountSalt3 = keccak256(abi.encode("account salt 3")); - - // Compute guardian addresses - guardian1 = emailRecoveryModule.computeEmailAuthAddress(instance.account, accountSalt1); - guardian2 = emailRecoveryModule.computeEmailAuthAddress(instance.account, accountSalt2); - guardian3 = emailRecoveryModule.computeEmailAuthAddress(instance.account, accountSalt3); - - guardians = new address[](3); - guardians[0] = guardian1; - guardians[1] = guardian2; - guardians[2] = guardian3; - - // Set recovery config variables - guardianWeights = new uint256[](3); - guardianWeights[0] = 1; - guardianWeights[1] = 2; - guardianWeights[2] = 1; - totalWeight = 4; - delay = 1 seconds; - expiry = 2 weeks; - threshold = 3; - templateIdx = 0; - - // Deploy validator to be recovered - validator = new OwnableValidator(); - validatorAddress = address(validator); - isInstalledContext = bytes("0"); - functionSelector = bytes4(keccak256(bytes("changeOwner(address)"))); - bytes memory changeOwnerCalldata = abi.encodeWithSelector(functionSelector, newOwner); - recoveryData = abi.encode(validatorAddress, changeOwnerCalldata); - recoveryDataHash = keccak256(recoveryData); + function setUp() public virtual override { + super.setUp(); // Install modules - instance.installModule({ + instance1.installModule({ moduleTypeId: MODULE_TYPE_VALIDATOR, module: validatorAddress, - data: abi.encode(owner) + data: abi.encode(owner1) }); - instance.installModule({ + + // + if (isAccountTypeSafe()) { + validatorAddress = accountAddress1; + } + + instance1.installModule({ moduleTypeId: MODULE_TYPE_EXECUTOR, - module: recoveryModuleAddress, + module: emailRecoveryModuleAddress, data: abi.encode( validatorAddress, isInstalledContext, functionSelector, - guardians, + guardians1, guardianWeights, threshold, delay, @@ -190,97 +58,55 @@ abstract contract UnitBase is RhinestoneModuleKit, Test { // Helper functions - function acceptanceSubjectTemplates() public pure returns (string[][] memory) { - string[][] memory templates = new string[][](1); - templates[0] = new string[](5); - templates[0][0] = "Accept"; - templates[0][1] = "guardian"; - templates[0][2] = "request"; - templates[0][3] = "for"; - templates[0][4] = "{ethAddr}"; - return templates; - } - - function recoverySubjectTemplates() public pure returns (string[][] memory) { - string[][] memory templates = new string[][](1); - templates[0] = new string[](7); - templates[0][0] = "Recover"; - templates[0][1] = "account"; - templates[0][2] = "{ethAddr}"; - templates[0][3] = "using"; - templates[0][4] = "recovery"; - templates[0][5] = "hash"; - templates[0][6] = "{string}"; - return templates; - } - - function generateMockEmailProof( - string memory subject, - bytes32 nullifier, + function computeEmailAuthAddress( + address account, bytes32 accountSalt ) public view - returns (EmailProof memory) + override + returns (address) { - EmailProof memory emailProof; - emailProof.domainName = "gmail.com"; - emailProof.publicKeyHash = bytes32( - vm.parseUint( - "6632353713085157925504008443078919716322386156160602218536961028046468237192" - ) - ); - emailProof.timestamp = block.timestamp; - emailProof.maskedSubject = subject; - emailProof.emailNullifier = nullifier; - emailProof.accountSalt = accountSalt; - emailProof.isCodeExist = true; - emailProof.proof = bytes("0"); - - return emailProof; + return emailRecoveryModule.computeEmailAuthAddress(account, accountSalt); } - function acceptGuardian(bytes32 accountSalt) public { - string memory accountString = SubjectUtils.addressToChecksumHexString(accountAddress); - string memory subject = string.concat("Accept guardian request for ", accountString); - - bytes32 nullifier = keccak256(abi.encode("nullifier 1")); + function deployModule(bytes memory handlerBytecode) public override { + emailRecoveryFactory = new EmailRecoveryFactory(address(verifier), address(emailAuthImpl)); + emailRecoveryUniversalFactory = + new EmailRecoveryUniversalFactory(address(verifier), address(emailAuthImpl)); - EmailProof memory emailProof = generateMockEmailProof(subject, nullifier, accountSalt); + bytes32 commandHandlerSalt = bytes32(uint256(0)); + commandHandlerAddress = Create2.deploy(0, commandHandlerSalt, handlerBytecode); - bytes[] memory subjectParamsForAcceptance = new bytes[](1); - subjectParamsForAcceptance[0] = abi.encode(accountAddress); - EmailAuthMsg memory emailAuthMsg = EmailAuthMsg({ - templateId: emailRecoveryModule.computeAcceptanceTemplateId(templateIdx), - subjectParams: subjectParamsForAcceptance, - skipedSubjectPrefix: 0, - proof: emailProof - }); + emailRecoveryModule = new UniversalEmailRecoveryModuleHarness( + address(verifier), + address(dkimRegistry), + address(emailAuthImpl), + commandHandlerAddress, + minimumDelay, + killSwitchAuthorizer + ); + emailRecoveryModuleAddress = address(emailRecoveryModule); - emailRecoveryModule.handleAcceptance(emailAuthMsg, templateIdx); + if (getCommandHandlerType() == CommandHandlerType.AccountHidingRecoveryCommandHandler) { + AccountHidingRecoveryCommandHandler(commandHandlerAddress).storeAccountHash( + accountAddress1 + ); + } } - function handleRecovery(bytes32 recoveryDataHash, bytes32 accountSalt) public { - string memory accountString = SubjectUtils.addressToChecksumHexString(accountAddress); - string memory recoveryDataHashString = uint256(recoveryDataHash).toHexString(32); - - string memory subjectPart1 = string.concat("Recover account ", accountString); - string memory subjectPart2 = string.concat(" using recovery hash ", recoveryDataHashString); - string memory subject = string.concat(subjectPart1, subjectPart2); - - bytes32 nullifier = keccak256(abi.encode("nullifier 2")); - EmailProof memory emailProof = generateMockEmailProof(subject, nullifier, accountSalt); - - bytes[] memory subjectParamsForRecovery = new bytes[](2); - subjectParamsForRecovery[0] = abi.encode(accountAddress); - subjectParamsForRecovery[1] = abi.encode(recoveryDataHashString); - - EmailAuthMsg memory emailAuthMsg = EmailAuthMsg({ - templateId: emailRecoveryModule.computeRecoveryTemplateId(templateIdx), - subjectParams: subjectParamsForRecovery, - skipedSubjectPrefix: 0, - proof: emailProof - }); - emailRecoveryModule.handleRecovery(emailAuthMsg, templateIdx); + function setRecoveryData() public override { + if (isAccountTypeSafe()) { + functionSelector = bytes4(keccak256(bytes("swapOwner(address,address,address)"))); + recoveryCalldata = + abi.encodeWithSelector(functionSelector, address(1), owner1, newOwner1); + recoveryData = abi.encode(accountAddress1, recoveryCalldata); + recoveryDataHash = keccak256(recoveryData); + } else { + functionSelector = bytes4(keccak256(bytes("changeOwner(address)"))); + recoveryCalldata = abi.encodeWithSelector(functionSelector, newOwner1); + recoveryData = abi.encode(validatorAddress, recoveryCalldata); + recoveryDataHash = keccak256(recoveryData); + } } } diff --git a/test/unit/UniversalEmailRecoveryModuleHarness.sol b/test/unit/UniversalEmailRecoveryModuleHarness.sol index 196ad843..a3317ef3 100644 --- a/test/unit/UniversalEmailRecoveryModuleHarness.sol +++ b/test/unit/UniversalEmailRecoveryModuleHarness.sol @@ -1,21 +1,31 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; import { SentinelListLib } from "sentinellist/SentinelList.sol"; -import { EnumerableGuardianMap, GuardianStatus } from "src/libraries/EnumerableGuardianMap.sol"; +import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import { GuardianStatus } from "src/libraries/EnumerableGuardianMap.sol"; import { UniversalEmailRecoveryModule } from "src/modules/UniversalEmailRecoveryModule.sol"; contract UniversalEmailRecoveryModuleHarness is UniversalEmailRecoveryModule { using SentinelListLib for SentinelListLib.SentinelList; + using EnumerableSet for EnumerableSet.AddressSet; constructor( address verifier, address dkimRegistry, address emailAuthImpl, - address subjectHandler + address commandHandler, + uint256 minimumDelay, + address killSwitchAuthorizer ) - UniversalEmailRecoveryModule(verifier, dkimRegistry, emailAuthImpl, subjectHandler) + UniversalEmailRecoveryModule( + verifier, + dkimRegistry, + emailAuthImpl, + commandHandler, + minimumDelay, + killSwitchAuthorizer + ) { } function exposed_configureRecovery( @@ -33,23 +43,23 @@ contract UniversalEmailRecoveryModuleHarness is UniversalEmailRecoveryModule { function exposed_acceptGuardian( address guardian, uint256 templateIdx, - bytes[] memory subjectParams, + bytes[] memory commandParams, bytes32 nullifier ) external { - acceptGuardian(guardian, templateIdx, subjectParams, nullifier); + acceptGuardian(guardian, templateIdx, commandParams, nullifier); } function exposed_processRecovery( address guardian, uint256 templateIdx, - bytes[] memory subjectParams, + bytes[] memory commandParams, bytes32 nullifier ) external { - processRecovery(guardian, templateIdx, subjectParams, nullifier); + processRecovery(guardian, templateIdx, commandParams, nullifier); } function exposed_recover(address account, bytes calldata recoveryData) external { @@ -100,6 +110,7 @@ contract UniversalEmailRecoveryModuleHarness is UniversalEmailRecoveryModule { address validator ) external + view returns (bool) { return validators[account].contains(validator); @@ -115,4 +126,12 @@ contract UniversalEmailRecoveryModuleHarness is UniversalEmailRecoveryModule { { return allowedSelectors[validator][account]; } + + function exposed_clearRecoveryRequest(address account) external { + return clearRecoveryRequest(account); + } + + function workaround_getVoteCount(address account) external view returns (uint256) { + return recoveryRequests[account].guardianVoted.values().length; + } } diff --git a/test/unit/assertErrorSelectors.t.sol b/test/unit/assertErrorSelectors.t.sol index f6ba593a..5c3a1a56 100644 --- a/test/unit/assertErrorSelectors.t.sol +++ b/test/unit/assertErrorSelectors.t.sol @@ -2,11 +2,11 @@ pragma solidity ^0.8.25; import { Test } from "forge-std/Test.sol"; -import { console2 } from "forge-std/console2.sol"; -import { AccountHidingRecoverySubjectHandler } from - "src/handlers/AccountHidingRecoverySubjectHandler.sol"; -import { EmailRecoverySubjectHandler } from "src/handlers/EmailRecoverySubjectHandler.sol"; -import { SafeRecoverySubjectHandler } from "src/handlers/SafeRecoverySubjectHandler.sol"; + +import { AccountHidingRecoveryCommandHandler } from + "src/handlers/AccountHidingRecoveryCommandHandler.sol"; +import { EmailRecoveryCommandHandler } from "src/handlers/EmailRecoveryCommandHandler.sol"; +import { SafeRecoveryCommandHandler } from "src/handlers/SafeRecoveryCommandHandler.sol"; import { IEmailRecoveryManager } from "src/interfaces/IEmailRecoveryManager.sol"; import { IGuardianManager } from "src/interfaces/IGuardianManager.sol"; import { EmailRecoveryUniversalFactory } from "src/factories/EmailRecoveryUniversalFactory.sol"; @@ -20,44 +20,50 @@ import { EnumerableGuardianMap } from "src/libraries/EnumerableGuardianMap.sol"; // in the contracts but this method reduces human error from copying values and also when updating // errors contract LogErrorSelectors_Test is Test { - function test_AccountHidingRecoverySubjectHandler_AssertSelectors() public { + function test_AccountHidingRecoveryCommandHandler_AssertSelectors() public pure { assertEq( - AccountHidingRecoverySubjectHandler.InvalidTemplateIndex.selector, bytes4(0x5be77855) + AccountHidingRecoveryCommandHandler.InvalidTemplateIndex.selector, bytes4(0x5be77855) ); assertEq( - AccountHidingRecoverySubjectHandler.InvalidSubjectParams.selector, bytes4(0x9c6fa025) + AccountHidingRecoveryCommandHandler.InvalidCommandParams.selector, bytes4(0x9648bb3c) ); - assertEq(AccountHidingRecoverySubjectHandler.InvalidAccount.selector, bytes4(0x6d187b28)); + assertEq(AccountHidingRecoveryCommandHandler.InvalidAccount.selector, bytes4(0x6d187b28)); assertEq( - AccountHidingRecoverySubjectHandler.ExistingStoredAccountHash.selector, + AccountHidingRecoveryCommandHandler.ExistingStoredAccountHash.selector, bytes4(0xd0c8a06b) ); } - function test_EmailRecoverySubjectHandler_AssertSelectors() public { - assertEq(EmailRecoverySubjectHandler.InvalidTemplateIndex.selector, bytes4(0x5be77855)); - assertEq(EmailRecoverySubjectHandler.InvalidSubjectParams.selector, bytes4(0x9c6fa025)); - assertEq(EmailRecoverySubjectHandler.InvalidAccount.selector, bytes4(0x6d187b28)); + function test_EmailRecoveryCommandHandler_AssertSelectors() public pure { + assertEq(EmailRecoveryCommandHandler.InvalidTemplateIndex.selector, bytes4(0x5be77855)); + assertEq(EmailRecoveryCommandHandler.InvalidCommandParams.selector, bytes4(0x9648bb3c)); + assertEq(EmailRecoveryCommandHandler.InvalidAccount.selector, bytes4(0x6d187b28)); } - function test_SafeRecoverySubjectHandler_AssertSelectors() public { - assertEq(SafeRecoverySubjectHandler.InvalidTemplateIndex.selector, bytes4(0x5be77855)); - assertEq(SafeRecoverySubjectHandler.InvalidSubjectParams.selector, bytes4(0x9c6fa025)); - assertEq(SafeRecoverySubjectHandler.InvalidOldOwner.selector, bytes4(0x377abe51)); - assertEq(SafeRecoverySubjectHandler.InvalidNewOwner.selector, bytes4(0x896d9ad0)); + function test_SafeRecoveryCommandHandler_AssertSelectors() public pure { + assertEq(SafeRecoveryCommandHandler.InvalidTemplateIndex.selector, bytes4(0x5be77855)); + assertEq(SafeRecoveryCommandHandler.InvalidCommandParams.selector, bytes4(0x9648bb3c)); + assertEq(SafeRecoveryCommandHandler.InvalidOldOwner.selector, bytes4(0x377abe51)); + assertEq(SafeRecoveryCommandHandler.InvalidNewOwner.selector, bytes4(0x896d9ad0)); } - function test_IEmailRecoveryManager_AssertSelectors() public { + function test_IEmailRecoveryManager_AssertSelectors() public pure { assertEq(IEmailRecoveryManager.InvalidVerifier.selector, bytes4(0xbaa3de5f)); assertEq(IEmailRecoveryManager.InvalidDkimRegistry.selector, bytes4(0x260ce05b)); assertEq(IEmailRecoveryManager.InvalidEmailAuthImpl.selector, bytes4(0xe98100fb)); - assertEq(IEmailRecoveryManager.InvalidSubjectHandler.selector, bytes4(0x436dcac5)); + assertEq(IEmailRecoveryManager.InvalidCommandHandler.selector, bytes4(0xfce1ed6f)); + assertEq(IEmailRecoveryManager.InvalidKillSwitchAuthorizer.selector, bytes4(0x0a19006f)); + assertEq(IEmailRecoveryManager.InvalidFactory.selector, bytes4(0x7a44db95)); + assertEq(IEmailRecoveryManager.InvalidProxyBytecodeHash.selector, bytes4(0xa65b3f53)); assertEq(IEmailRecoveryManager.SetupAlreadyCalled.selector, bytes4(0xb3af5593)); assertEq(IEmailRecoveryManager.AccountNotConfigured.selector, bytes4(0x66ecbd6d)); + assertEq(IEmailRecoveryManager.DelayLessThanMinimumDelay.selector, bytes4(0x59780056)); assertEq(IEmailRecoveryManager.DelayMoreThanExpiry.selector, bytes4(0xb742a43c)); assertEq(IEmailRecoveryManager.RecoveryWindowTooShort.selector, bytes4(0x50799cce)); assertEq(IEmailRecoveryManager.ThresholdExceedsAcceptedWeight.selector, bytes4(0x7c3e983c)); assertEq(IEmailRecoveryManager.InvalidGuardianStatus.selector, bytes4(0x5689b51a)); + assertEq(IEmailRecoveryManager.GuardianAlreadyVoted.selector, bytes4(0xe11487a7)); + assertEq(IEmailRecoveryManager.GuardianMustWaitForCooldown.selector, bytes4(0x8035b5fd)); assertEq(IEmailRecoveryManager.InvalidAccountAddress.selector, bytes4(0x401b6ade)); assertEq(IEmailRecoveryManager.NoRecoveryConfigured.selector, bytes4(0xa66e66b6)); assertEq(IEmailRecoveryManager.NotEnoughApprovals.selector, bytes4(0x443282f5)); @@ -69,23 +75,23 @@ contract LogErrorSelectors_Test is Test { assertEq(IEmailRecoveryManager.RecoveryIsNotActivated.selector, bytes4(0xc737140f)); } - function test_EmailRecoveryUniversalFactory_AssertSelectors() public { + function test_EmailRecoveryUniversalFactory_AssertSelectors() public pure { assertEq(EmailRecoveryUniversalFactory.InvalidVerifier.selector, bytes4(0xbaa3de5f)); assertEq(EmailRecoveryUniversalFactory.InvalidEmailAuthImpl.selector, bytes4(0xe98100fb)); } - function test_EmailRecoveryFactory_AssertSelectors() public { + function test_EmailRecoveryFactory_AssertSelectors() public pure { assertEq(EmailRecoveryFactory.InvalidVerifier.selector, bytes4(0xbaa3de5f)); assertEq(EmailRecoveryFactory.InvalidEmailAuthImpl.selector, bytes4(0xe98100fb)); } - function test_EmailRecoveryModule_AssertSelectors() public { + function test_EmailRecoveryModule_AssertSelectors() public pure { assertEq(EmailRecoveryModule.InvalidSelector.selector, bytes4(0x12ba286f)); assertEq(EmailRecoveryModule.InvalidOnInstallData.selector, bytes4(0x5c223882)); assertEq(EmailRecoveryModule.InvalidValidator.selector, bytes4(0x11d5c560)); } - function test_UniversalEmailRecoveryModule_AssertSelectors() public { + function test_UniversalEmailRecoveryModule_AssertSelectors() public pure { assertEq(UniversalEmailRecoveryModule.InvalidSelector.selector, bytes4(0x12ba286f)); assertEq( UniversalEmailRecoveryModule.RecoveryModuleNotInitialized.selector, bytes4(0x0b088c23) @@ -95,7 +101,7 @@ contract LogErrorSelectors_Test is Test { assertEq(UniversalEmailRecoveryModule.MaxValidatorsReached.selector, bytes4(0xed7948d6)); } - function test_SafeEmailRecoveryModule_AssertSelectors() public { + function test_SafeEmailRecoveryModule_AssertSelectors() public pure { assertEq(SafeEmailRecoveryModule.ModuleNotInstalled.selector, bytes4(0x026d9639)); assertEq(SafeEmailRecoveryModule.InvalidAccount.selector, bytes4(0x4b579b22)); assertEq(SafeEmailRecoveryModule.InvalidSelector.selector, bytes4(0x12ba286f)); @@ -103,13 +109,14 @@ contract LogErrorSelectors_Test is Test { assertEq(SafeEmailRecoveryModule.ResetFailed.selector, bytes4(0x8078983d)); } - function test_EnumerableGuardianMap_AssertSelectors() public { + function test_EnumerableGuardianMap_AssertSelectors() public pure { assertEq(EnumerableGuardianMap.MaxNumberOfGuardiansReached.selector, bytes4(0xbb6c1e93)); assertEq(EnumerableGuardianMap.TooManyValuesToRemove.selector, bytes4(0xe023c211)); } - function test_IGuardianManager_AssertSelectors() public { + function test_IGuardianManager_AssertSelectors() public pure { assertEq(IGuardianManager.RecoveryInProcess.selector, bytes4(0xf90ea6fc)); + assertEq(IGuardianManager.KillSwitchEnabled.selector, bytes4(0xec7fb2a0)); assertEq(IGuardianManager.IncorrectNumberOfWeights.selector, bytes4(0x166e79bd)); assertEq(IGuardianManager.ThresholdCannotBeZero.selector, bytes4(0xf4124166)); assertEq(IGuardianManager.InvalidGuardianAddress.selector, bytes4(0x1af74975)); diff --git a/test/unit/factories/EmailRecoveryFactory/constructor.t.sol b/test/unit/factories/EmailRecoveryFactory/constructor.t.sol index 383750e2..edb02ac5 100644 --- a/test/unit/factories/EmailRecoveryFactory/constructor.t.sol +++ b/test/unit/factories/EmailRecoveryFactory/constructor.t.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; import { UnitBase } from "../../UnitBase.t.sol"; import { EmailRecoveryFactory } from "src/factories/EmailRecoveryFactory.sol"; diff --git a/test/unit/factories/EmailRecoveryFactory/deployEmailRecoveryModule.t.sol b/test/unit/factories/EmailRecoveryFactory/deployEmailRecoveryModule.t.sol index fb2052e9..ebc16c5f 100644 --- a/test/unit/factories/EmailRecoveryFactory/deployEmailRecoveryModule.t.sol +++ b/test/unit/factories/EmailRecoveryFactory/deployEmailRecoveryModule.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; import { Create2 } from "@openzeppelin/contracts/utils/Create2.sol"; import { UnitBase } from "../../UnitBase.t.sol"; import { EmailRecoveryFactory } from "src/factories/EmailRecoveryFactory.sol"; -import { EmailRecoveryUniversalFactory } from "src/factories/EmailRecoveryUniversalFactory.sol"; -import { EmailRecoverySubjectHandler } from "src/handlers/EmailRecoverySubjectHandler.sol"; -import { EmailRecoveryManager } from "src/EmailRecoveryManager.sol"; +import { EmailRecoveryCommandHandler } from "src/handlers/EmailRecoveryCommandHandler.sol"; import { EmailRecoveryModule } from "src/modules/EmailRecoveryModule.sol"; contract EmailRecoveryFactory_deployAll_Test is UnitBase { @@ -17,11 +14,11 @@ contract EmailRecoveryFactory_deployAll_Test is UnitBase { function test_DeployEmailRecoveryModule_Succeeds() public { bytes32 recoveryModuleSalt = bytes32(uint256(0)); - bytes32 subjectHandlerSalt = bytes32(uint256(0)); + bytes32 commandHandlerSalt = bytes32(uint256(0)); - bytes memory subjectHandlerBytecode = type(EmailRecoverySubjectHandler).creationCode; - address expectedSubjectHandler = Create2.computeAddress( - subjectHandlerSalt, keccak256(subjectHandlerBytecode), address(emailRecoveryFactory) + bytes memory commandHandlerBytecode = type(EmailRecoveryCommandHandler).creationCode; + address expectedCommandHandler = Create2.computeAddress( + commandHandlerSalt, keccak256(commandHandlerBytecode), address(emailRecoveryFactory) ); bytes memory recoveryModuleBytecode = abi.encodePacked( @@ -30,7 +27,9 @@ contract EmailRecoveryFactory_deployAll_Test is UnitBase { address(verifier), address(dkimRegistry), address(emailAuthImpl), - expectedSubjectHandler, + expectedCommandHandler, + minimumDelay, + killSwitchAuthorizer, validatorAddress, functionSelector ) @@ -41,19 +40,21 @@ contract EmailRecoveryFactory_deployAll_Test is UnitBase { vm.expectEmit(); emit EmailRecoveryFactory.EmailRecoveryModuleDeployed( - expectedModule, expectedSubjectHandler, validatorAddress, functionSelector + expectedModule, expectedCommandHandler, validatorAddress, functionSelector ); - (address emailRecoveryModule, address subjectHandler) = emailRecoveryFactory + (address emailRecoveryModule, address commandHandler) = emailRecoveryFactory .deployEmailRecoveryModule( - subjectHandlerSalt, + commandHandlerSalt, recoveryModuleSalt, - subjectHandlerBytecode, + commandHandlerBytecode, + minimumDelay, + killSwitchAuthorizer, address(dkimRegistry), validatorAddress, functionSelector ); assertEq(emailRecoveryModule, expectedModule); - assertEq(subjectHandler, expectedSubjectHandler); + assertEq(commandHandler, expectedCommandHandler); } } diff --git a/test/unit/factories/EmailRecoveryUniversalFactory/constructor.t.sol b/test/unit/factories/EmailRecoveryUniversalFactory/constructor.t.sol index d22b47b6..cb1fd295 100644 --- a/test/unit/factories/EmailRecoveryUniversalFactory/constructor.t.sol +++ b/test/unit/factories/EmailRecoveryUniversalFactory/constructor.t.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; import { UnitBase } from "../../UnitBase.t.sol"; import { EmailRecoveryUniversalFactory } from "src/factories/EmailRecoveryUniversalFactory.sol"; diff --git a/test/unit/factories/EmailRecoveryUniversalFactory/deployUniversalEmailRecoveryModule.t.sol b/test/unit/factories/EmailRecoveryUniversalFactory/deployUniversalEmailRecoveryModule.t.sol index b899e508..8e67e434 100644 --- a/test/unit/factories/EmailRecoveryUniversalFactory/deployUniversalEmailRecoveryModule.t.sol +++ b/test/unit/factories/EmailRecoveryUniversalFactory/deployUniversalEmailRecoveryModule.t.sol @@ -1,12 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; import { Create2 } from "@openzeppelin/contracts/utils/Create2.sol"; import { UnitBase } from "../../UnitBase.t.sol"; -import { EmailRecoveryFactory } from "src/factories/EmailRecoveryFactory.sol"; import { EmailRecoveryUniversalFactory } from "src/factories/EmailRecoveryUniversalFactory.sol"; -import { EmailRecoverySubjectHandler } from "src/handlers/EmailRecoverySubjectHandler.sol"; +import { EmailRecoveryCommandHandler } from "src/handlers/EmailRecoveryCommandHandler.sol"; import { UniversalEmailRecoveryModule } from "src/modules/UniversalEmailRecoveryModule.sol"; contract EmailRecoveryUniversalFactory_deployUniversalEmailRecoveryModule_Test is UnitBase { @@ -16,12 +14,12 @@ contract EmailRecoveryUniversalFactory_deployUniversalEmailRecoveryModule_Test i function test_DeployUniversalEmailRecoveryModule_Succeeds() public { bytes32 recoveryModuleSalt = bytes32(uint256(0)); - bytes32 subjectHandlerSalt = bytes32(uint256(0)); + bytes32 commandHandlerSalt = bytes32(uint256(0)); - bytes memory subjectHandlerBytecode = type(EmailRecoverySubjectHandler).creationCode; - address expectedSubjectHandler = Create2.computeAddress( - subjectHandlerSalt, - keccak256(subjectHandlerBytecode), + bytes memory commandHandlerBytecode = type(EmailRecoveryCommandHandler).creationCode; + address expectedCommandHandler = Create2.computeAddress( + commandHandlerSalt, + keccak256(commandHandlerBytecode), address(emailRecoveryUniversalFactory) ); @@ -31,7 +29,9 @@ contract EmailRecoveryUniversalFactory_deployUniversalEmailRecoveryModule_Test i address(verifier), address(dkimRegistry), address(emailAuthImpl), - expectedSubjectHandler + expectedCommandHandler, + minimumDelay, + killSwitchAuthorizer ) ); address expectedModule = Create2.computeAddress( @@ -42,14 +42,19 @@ contract EmailRecoveryUniversalFactory_deployUniversalEmailRecoveryModule_Test i vm.expectEmit(); emit EmailRecoveryUniversalFactory.UniversalEmailRecoveryModuleDeployed( - expectedModule, expectedSubjectHandler + expectedModule, expectedCommandHandler ); - (address emailRecoveryModule, address subjectHandler) = emailRecoveryUniversalFactory + (address emailRecoveryModule, address commandHandler) = emailRecoveryUniversalFactory .deployUniversalEmailRecoveryModule( - subjectHandlerSalt, recoveryModuleSalt, subjectHandlerBytecode, address(dkimRegistry) + commandHandlerSalt, + recoveryModuleSalt, + commandHandlerBytecode, + minimumDelay, + killSwitchAuthorizer, + address(dkimRegistry) ); assertEq(emailRecoveryModule, expectedModule); - assertEq(subjectHandler, expectedSubjectHandler); + assertEq(commandHandler, expectedCommandHandler); } } diff --git a/test/unit/handlers/AccountHidingRecoveryCommandHandler/acceptanceCommandTemplates.t.sol b/test/unit/handlers/AccountHidingRecoveryCommandHandler/acceptanceCommandTemplates.t.sol new file mode 100644 index 00000000..de4ffa79 --- /dev/null +++ b/test/unit/handlers/AccountHidingRecoveryCommandHandler/acceptanceCommandTemplates.t.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { UnitBase } from "../../UnitBase.t.sol"; +import { AccountHidingRecoveryCommandHandler } from + "src/handlers/AccountHidingRecoveryCommandHandler.sol"; + +contract AccountHidingRecoveryCommandHandler_acceptanceCommandTemplates_Test is UnitBase { + AccountHidingRecoveryCommandHandler public accountHidingRecoveryCommandHandler; + + function setUp() public override { + super.setUp(); + accountHidingRecoveryCommandHandler = new AccountHidingRecoveryCommandHandler(); + } + + function test_AcceptanceCommandTemplates_Succeeds() public view { + string[][] memory templates = + accountHidingRecoveryCommandHandler.acceptanceCommandTemplates(); + + assertEq(templates.length, 1); + assertEq(templates[0].length, 5); + assertEq(templates[0][0], "Accept"); + assertEq(templates[0][1], "guardian"); + assertEq(templates[0][2], "request"); + assertEq(templates[0][3], "for"); + assertEq(templates[0][4], "{string}"); + } +} diff --git a/test/unit/handlers/AccountHidingRecoveryCommandHandler/extractRecoveredAccountFromAcceptanceCommand.t.sol b/test/unit/handlers/AccountHidingRecoveryCommandHandler/extractRecoveredAccountFromAcceptanceCommand.t.sol new file mode 100644 index 00000000..b7af858e --- /dev/null +++ b/test/unit/handlers/AccountHidingRecoveryCommandHandler/extractRecoveredAccountFromAcceptanceCommand.t.sol @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; +import { UnitBase } from "../../UnitBase.t.sol"; +import { AccountHidingRecoveryCommandHandler } from + "src/handlers/AccountHidingRecoveryCommandHandler.sol"; + +contract AccountHidingRecoveryCommandHandler_extractRecoveredAccountFromAcceptanceCommand_Test is + UnitBase +{ + using Strings for uint256; + + AccountHidingRecoveryCommandHandler public accountHidingRecoveryCommandHandler; + + function setUp() public override { + super.setUp(); + accountHidingRecoveryCommandHandler = new AccountHidingRecoveryCommandHandler(); + } + + function test_ExtractRecoveredAccountFromAcceptanceCommand_FailsWhenHashNotStored() + public + view + { + bytes32 accountHash = keccak256(abi.encodePacked(accountAddress1)); + + bytes[] memory commandParams = new bytes[](1); + commandParams[0] = abi.encode(uint256(accountHash).toHexString(32)); + + address extractedAccount = accountHidingRecoveryCommandHandler + .extractRecoveredAccountFromAcceptanceCommand(commandParams, templateIdx); + + assertEq(extractedAccount, address(0)); + } + + function test_ExtractRecoveredAccountFromAcceptanceCommand_FailsWhenUsingAbiEncode() public { + bytes32 accountHash = keccak256(abi.encode(accountAddress1)); + + bytes[] memory commandParams = new bytes[](1); + commandParams[0] = abi.encode(uint256(accountHash).toHexString(32)); + + accountHidingRecoveryCommandHandler.storeAccountHash(accountAddress1); + + address extractedAccount = accountHidingRecoveryCommandHandler + .extractRecoveredAccountFromAcceptanceCommand(commandParams, templateIdx); + + assertEq(extractedAccount, address(0)); + } + + function test_ExtractRecoveredAccountFromAcceptanceCommand_Succeeds() public { + bytes32 accountHash = keccak256(abi.encodePacked(accountAddress1)); + + bytes[] memory commandParams = new bytes[](1); + commandParams[0] = abi.encode(uint256(accountHash).toHexString(32)); + + accountHidingRecoveryCommandHandler.storeAccountHash(accountAddress1); + + address extractedAccount = accountHidingRecoveryCommandHandler + .extractRecoveredAccountFromAcceptanceCommand(commandParams, templateIdx); + + assertEq(extractedAccount, accountAddress1); + } +} diff --git a/test/unit/handlers/AccountHidingRecoveryCommandHandler/extractRecoveredAccountFromRecoveryCommand.t.sol b/test/unit/handlers/AccountHidingRecoveryCommandHandler/extractRecoveredAccountFromRecoveryCommand.t.sol new file mode 100644 index 00000000..f42ce985 --- /dev/null +++ b/test/unit/handlers/AccountHidingRecoveryCommandHandler/extractRecoveredAccountFromRecoveryCommand.t.sol @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; +import { UnitBase } from "../../UnitBase.t.sol"; +import { AccountHidingRecoveryCommandHandler } from + "src/handlers/AccountHidingRecoveryCommandHandler.sol"; + +contract AccountHidingRecoveryCommandHandler_extractRecoveredAccountFromRecoveryCommand_Test is + UnitBase +{ + using Strings for uint256; + + AccountHidingRecoveryCommandHandler public accountHidingRecoveryCommandHandler; + + function setUp() public override { + super.setUp(); + accountHidingRecoveryCommandHandler = new AccountHidingRecoveryCommandHandler(); + } + + function test_ExtractRecoveredAccountFromRecoveryCommand_FailsWhenHashNotStored() public { + bytes32 accountHash = keccak256(abi.encodePacked(accountAddress1)); + string memory accountHashString = uint256(accountHash).toHexString(32); + string memory recoveryDataHashString = uint256(recoveryDataHash).toHexString(32); + + bytes[] memory commandParams = new bytes[](2); + commandParams[0] = abi.encode(accountHashString); + commandParams[1] = abi.encode(recoveryDataHashString); + + address extractedAccount = accountHidingRecoveryCommandHandler + .extractRecoveredAccountFromAcceptanceCommand(commandParams, templateIdx); + + assertEq(extractedAccount, address(0)); + } + + function test_ExtractRecoveredAccountFromRecoveryCommand_FailsWhenUsingAbiEncode() public { + bytes32 accountHash = keccak256(abi.encode(accountAddress1)); + string memory accountHashString = uint256(accountHash).toHexString(32); + string memory recoveryDataHashString = uint256(recoveryDataHash).toHexString(32); + + bytes[] memory commandParams = new bytes[](2); + commandParams[0] = abi.encode(accountHashString); + commandParams[1] = abi.encode(recoveryDataHashString); + + accountHidingRecoveryCommandHandler.storeAccountHash(accountAddress1); + + address extractedAccount = accountHidingRecoveryCommandHandler + .extractRecoveredAccountFromAcceptanceCommand(commandParams, templateIdx); + + assertEq(extractedAccount, address(0)); + } + + function test_ExtractRecoveredAccountFromRecoveryCommand_Succeeds() public { + bytes32 accountHash = keccak256(abi.encodePacked(accountAddress1)); + string memory accountHashString = uint256(accountHash).toHexString(32); + string memory recoveryDataHashString = uint256(recoveryDataHash).toHexString(32); + + bytes[] memory commandParams = new bytes[](2); + commandParams[0] = abi.encode(accountHashString); + commandParams[1] = abi.encode(recoveryDataHashString); + + accountHidingRecoveryCommandHandler.storeAccountHash(accountAddress1); + + address extractedAccount = accountHidingRecoveryCommandHandler + .extractRecoveredAccountFromRecoveryCommand(commandParams, templateIdx); + assertEq(extractedAccount, accountAddress1); + } +} diff --git a/test/unit/handlers/AccountHidingRecoveryCommandHandler/parseRecoveryCalldataHash.t.sol b/test/unit/handlers/AccountHidingRecoveryCommandHandler/parseRecoveryCalldataHash.t.sol new file mode 100644 index 00000000..e951cec4 --- /dev/null +++ b/test/unit/handlers/AccountHidingRecoveryCommandHandler/parseRecoveryCalldataHash.t.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; +import { UnitBase } from "../../UnitBase.t.sol"; +import { AccountHidingRecoveryCommandHandler } from + "src/handlers/AccountHidingRecoveryCommandHandler.sol"; + +contract AccountHidingRecoveryCommandHandler_parseRecoveryDataHash_Test is UnitBase { + using Strings for uint256; + + AccountHidingRecoveryCommandHandler public accountHidingRecoveryCommandHandler; + string public accountHashString; + string public recoveryDataHashString; + bytes[] public commandParams; + + function setUp() public override { + super.setUp(); + accountHidingRecoveryCommandHandler = new AccountHidingRecoveryCommandHandler(); + + bytes32 accountHash = keccak256(abi.encodePacked(accountAddress1)); + accountHashString = uint256(accountHash).toHexString(32); + recoveryDataHashString = uint256(recoveryDataHash).toHexString(32); + + commandParams = new bytes[](2); + commandParams[0] = abi.encode(accountHashString); + commandParams[1] = abi.encode(recoveryDataHashString); + } + + function test_ParseRecoveryDataHash_RevertWhen_InvalidTemplateIndex() public { + uint256 invalidTemplateIdx = 1; + vm.expectRevert( + abi.encodeWithSelector( + AccountHidingRecoveryCommandHandler.InvalidTemplateIndex.selector, + invalidTemplateIdx, + 0 + ) + ); + accountHidingRecoveryCommandHandler.parseRecoveryDataHash(invalidTemplateIdx, commandParams); + } + + function test_ParseRecoveryDataHash_Succeeds() public view { + bytes32 expectedRecoveryDataHash = keccak256(recoveryData); + + bytes32 _recoveryDataHash = + accountHidingRecoveryCommandHandler.parseRecoveryDataHash(templateIdx, commandParams); + + assertEq(_recoveryDataHash, expectedRecoveryDataHash); + } +} diff --git a/test/unit/handlers/AccountHidingRecoveryCommandHandler/recoveryCommandTemplates.t.sol b/test/unit/handlers/AccountHidingRecoveryCommandHandler/recoveryCommandTemplates.t.sol new file mode 100644 index 00000000..86b941cb --- /dev/null +++ b/test/unit/handlers/AccountHidingRecoveryCommandHandler/recoveryCommandTemplates.t.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { UnitBase } from "../../UnitBase.t.sol"; +import { AccountHidingRecoveryCommandHandler } from + "src/handlers/AccountHidingRecoveryCommandHandler.sol"; + +contract AccountHidingRecoveryCommandHandler_recoveryCommandTemplates_Test is UnitBase { + AccountHidingRecoveryCommandHandler public accountHidingRecoveryCommandHandler; + + function setUp() public override { + super.setUp(); + accountHidingRecoveryCommandHandler = new AccountHidingRecoveryCommandHandler(); + } + + function test_RecoveryCommandTemplates_Succeeds() public view { + string[][] memory templates = accountHidingRecoveryCommandHandler.recoveryCommandTemplates(); + + assertEq(templates.length, 1); + assertEq(templates[0].length, 7); + assertEq(templates[0][0], "Recover"); + assertEq(templates[0][1], "account"); + assertEq(templates[0][2], "{string}"); + assertEq(templates[0][3], "using"); + assertEq(templates[0][4], "recovery"); + assertEq(templates[0][5], "hash"); + assertEq(templates[0][6], "{string}"); + } +} diff --git a/test/unit/handlers/AccountHidingRecoveryCommandHandler/storeAccountHash.t.sol b/test/unit/handlers/AccountHidingRecoveryCommandHandler/storeAccountHash.t.sol new file mode 100644 index 00000000..ae949ced --- /dev/null +++ b/test/unit/handlers/AccountHidingRecoveryCommandHandler/storeAccountHash.t.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; +import { UnitBase } from "../../UnitBase.t.sol"; +import { AccountHidingRecoveryCommandHandler } from + "src/handlers/AccountHidingRecoveryCommandHandler.sol"; + +contract AccountHidingRecoveryCommandHandler_storeAccountHash_Test is UnitBase { + using Strings for uint256; + + AccountHidingRecoveryCommandHandler public accountHidingRecoveryCommandHandler; + string public accountHashString; + string public recoveryDataHashString; + bytes[] public commandParams; + + function setUp() public override { + super.setUp(); + accountHidingRecoveryCommandHandler = new AccountHidingRecoveryCommandHandler(); + + bytes32 accountHash = keccak256(abi.encodePacked(accountAddress1)); + accountHashString = uint256(accountHash).toHexString(32); + recoveryDataHashString = uint256(recoveryDataHash).toHexString(32); + + commandParams = new bytes[](2); + commandParams[0] = abi.encode(accountHashString); + commandParams[1] = abi.encode(recoveryDataHashString); + } + + function test_StoreAccountHash_RevertWhen_ExistingStoredAccountHash() public { + accountHidingRecoveryCommandHandler.storeAccountHash(accountAddress1); + + vm.expectRevert( + abi.encodeWithSelector( + AccountHidingRecoveryCommandHandler.ExistingStoredAccountHash.selector, + accountAddress1 + ) + ); + accountHidingRecoveryCommandHandler.storeAccountHash(accountAddress1); + } + + function test_StoreAccountHash_StoresZeroAccountHash() public { + address zeroAddress = address(0); + bytes32 accountHash = keccak256(abi.encodePacked(zeroAddress)); + + accountHidingRecoveryCommandHandler.storeAccountHash(zeroAddress); + + address storedAccount = accountHidingRecoveryCommandHandler.accountHashes(accountHash); + assertEq(zeroAddress, storedAccount); + } + + function test_StoreAccountHash_DoesNotFindAccountForNonPackedEncodedAddress() public { + bytes32 accountHash = keccak256(abi.encode(accountAddress1)); + + accountHidingRecoveryCommandHandler.storeAccountHash(accountAddress1); + + address storedAccount = accountHidingRecoveryCommandHandler.accountHashes(accountHash); + assertEq(address(0), storedAccount); + } + + function test_StoreAccountHash_Succeeds() public { + bytes32 accountHash = keccak256(abi.encodePacked(accountAddress1)); + + accountHidingRecoveryCommandHandler.storeAccountHash(accountAddress1); + + address storedAccount = accountHidingRecoveryCommandHandler.accountHashes(accountHash); + assertEq(accountAddress1, storedAccount); + } +} diff --git a/test/unit/handlers/AccountHidingRecoveryCommandHandler/validateAcceptanceCommand.t.sol b/test/unit/handlers/AccountHidingRecoveryCommandHandler/validateAcceptanceCommand.t.sol new file mode 100644 index 00000000..e94be149 --- /dev/null +++ b/test/unit/handlers/AccountHidingRecoveryCommandHandler/validateAcceptanceCommand.t.sol @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; +import { UnitBase } from "../../UnitBase.t.sol"; +import { AccountHidingRecoveryCommandHandler } from + "src/handlers/AccountHidingRecoveryCommandHandler.sol"; + +contract AccountHidingRecoveryCommandHandler_validateAcceptanceCommand_Test is UnitBase { + using Strings for uint256; + + AccountHidingRecoveryCommandHandler public accountHidingRecoveryCommandHandler; + + function setUp() public override { + super.setUp(); + accountHidingRecoveryCommandHandler = new AccountHidingRecoveryCommandHandler(); + } + + function test_ValidateAcceptanceCommand_RevertWhen_InvalidTemplateIndex() public { + bytes32 accountHash = keccak256(abi.encodePacked(accountAddress1)); + string memory accountHashString = uint256(accountHash).toHexString(32); + + bytes[] memory commandParams = new bytes[](1); + commandParams[0] = abi.encode(accountHashString); + uint256 invalidTemplateIdx = 1; + + vm.expectRevert( + abi.encodeWithSelector( + AccountHidingRecoveryCommandHandler.InvalidTemplateIndex.selector, + invalidTemplateIdx, + 0 + ) + ); + accountHidingRecoveryCommandHandler.validateAcceptanceCommand( + invalidTemplateIdx, commandParams + ); + } + + function test_ValidateAcceptanceCommand_RevertWhen_NoCommandParams() public { + bytes[] memory emptyCommandParams; + + vm.expectRevert( + abi.encodeWithSelector( + AccountHidingRecoveryCommandHandler.InvalidCommandParams.selector, + emptyCommandParams.length, + 1 + ) + ); + accountHidingRecoveryCommandHandler.validateAcceptanceCommand( + templateIdx, emptyCommandParams + ); + } + + function test_ValidateAcceptanceCommand_RevertWhen_TooManyCommandParams() public { + bytes32 accountHash = keccak256(abi.encodePacked(accountAddress1)); + string memory accountHashString = uint256(accountHash).toHexString(32); + + bytes[] memory commandParams = new bytes[](2); + commandParams[0] = abi.encode(accountHashString); + commandParams[1] = abi.encode("extra param"); + + vm.expectRevert( + abi.encodeWithSelector( + AccountHidingRecoveryCommandHandler.InvalidCommandParams.selector, + commandParams.length, + 1 + ) + ); + accountHidingRecoveryCommandHandler.validateAcceptanceCommand(templateIdx, commandParams); + } + + function test_ValidateAcceptanceCommand_Succeeds() public { + bytes32 accountHash = keccak256(abi.encodePacked(accountAddress1)); + string memory accountHashString = uint256(accountHash).toHexString(32); + + bytes[] memory commandParams = new bytes[](1); + commandParams[0] = abi.encode(accountHashString); + + accountHidingRecoveryCommandHandler.storeAccountHash(accountAddress1); + + address account = accountHidingRecoveryCommandHandler.validateAcceptanceCommand( + templateIdx, commandParams + ); + assertEq(account, accountAddress1); + } +} diff --git a/test/unit/handlers/AccountHidingRecoveryCommandHandler/validateRecoveryCommand.t.sol b/test/unit/handlers/AccountHidingRecoveryCommandHandler/validateRecoveryCommand.t.sol new file mode 100644 index 00000000..44894d63 --- /dev/null +++ b/test/unit/handlers/AccountHidingRecoveryCommandHandler/validateRecoveryCommand.t.sol @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; +import { UnitBase } from "../../UnitBase.t.sol"; +import { AccountHidingRecoveryCommandHandler } from + "src/handlers/AccountHidingRecoveryCommandHandler.sol"; + +contract AccountHidingRecoveryCommandHandler_validateRecoveryCommand_Test is UnitBase { + using Strings for uint256; + + AccountHidingRecoveryCommandHandler public accountHidingRecoveryCommandHandler; + string public accountHashString; + string public recoveryDataHashString; + bytes[] public commandParams; + + function setUp() public override { + super.setUp(); + accountHidingRecoveryCommandHandler = new AccountHidingRecoveryCommandHandler(); + + bytes32 accountHash = keccak256(abi.encodePacked(accountAddress1)); + accountHashString = uint256(accountHash).toHexString(32); + recoveryDataHashString = uint256(recoveryDataHash).toHexString(32); + + commandParams = new bytes[](2); + commandParams[0] = abi.encode(accountHashString); + commandParams[1] = abi.encode(recoveryDataHashString); + } + + function test_ValidateRecoveryCommand_RevertWhen_InvalidTemplateIndex() public { + uint256 invalidTemplateIdx = 1; + + vm.expectRevert( + abi.encodeWithSelector( + AccountHidingRecoveryCommandHandler.InvalidTemplateIndex.selector, + invalidTemplateIdx, + 0 + ) + ); + accountHidingRecoveryCommandHandler.validateRecoveryCommand( + invalidTemplateIdx, commandParams + ); + } + + function test_ValidateRecoveryCommand_RevertWhen_NoCommandParams() public { + bytes[] memory emptyCommandParams; + + vm.expectRevert( + abi.encodeWithSelector( + AccountHidingRecoveryCommandHandler.InvalidCommandParams.selector, + emptyCommandParams.length, + 2 + ) + ); + accountHidingRecoveryCommandHandler.validateRecoveryCommand(templateIdx, emptyCommandParams); + } + + function test_ValidateRecoveryCommand_RevertWhen_TooManyCommandParams() public { + bytes[] memory longCommandParams = new bytes[](3); + longCommandParams[0] = abi.encode(accountHashString); + longCommandParams[1] = abi.encode(recoveryDataHashString); + longCommandParams[2] = abi.encode("extra param"); + + vm.expectRevert( + abi.encodeWithSelector( + AccountHidingRecoveryCommandHandler.InvalidCommandParams.selector, + longCommandParams.length, + 2 + ) + ); + accountHidingRecoveryCommandHandler.validateRecoveryCommand(templateIdx, longCommandParams); + } + + function test_ValidateRecoveryCommand_RevertWhen_InvalidAccount() public { + bytes32 zeroAccountHash = keccak256(abi.encodePacked(address(0))); + string memory zeroAccountHashString = uint256(zeroAccountHash).toHexString(32); + commandParams[0] = abi.encode(zeroAccountHashString); + + vm.expectRevert(AccountHidingRecoveryCommandHandler.InvalidAccount.selector); + accountHidingRecoveryCommandHandler.validateRecoveryCommand(templateIdx, commandParams); + } + + function test_ValidateRecoveryCommand_RevertWhen_ZeroRecoveryDataHash() public { + commandParams[1] = abi.encode(bytes32(0)); + + accountHidingRecoveryCommandHandler.storeAccountHash(accountAddress1); + + vm.expectRevert("invalid hex prefix"); + accountHidingRecoveryCommandHandler.validateRecoveryCommand(templateIdx, commandParams); + } + + function test_ValidateRecoveryCommand_RevertWhen_InvalidHashLength() public { + commandParams[1] = abi.encode(uint256(recoveryDataHash).toHexString(33)); + + accountHidingRecoveryCommandHandler.storeAccountHash(accountAddress1); + + vm.expectRevert("bytes length is not 32"); + accountHidingRecoveryCommandHandler.validateRecoveryCommand(templateIdx, commandParams); + } + + function test_ValidateRecoveryCommand_Succeeds() public { + accountHidingRecoveryCommandHandler.storeAccountHash(accountAddress1); + address accountFromEmail = + accountHidingRecoveryCommandHandler.validateRecoveryCommand(templateIdx, commandParams); + assertEq(accountFromEmail, accountAddress1); + } +} diff --git a/test/unit/handlers/EmailRecoverySubjectHandler/acceptanceSubjectTemplates.t.sol b/test/unit/handlers/EmailRecoveryCommandHandler/acceptanceCommandTemplates.t.sol similarity index 53% rename from test/unit/handlers/EmailRecoverySubjectHandler/acceptanceSubjectTemplates.t.sol rename to test/unit/handlers/EmailRecoveryCommandHandler/acceptanceCommandTemplates.t.sol index 96584dba..8495d5b1 100644 --- a/test/unit/handlers/EmailRecoverySubjectHandler/acceptanceSubjectTemplates.t.sol +++ b/test/unit/handlers/EmailRecoveryCommandHandler/acceptanceCommandTemplates.t.sol @@ -1,16 +1,19 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; import { UnitBase } from "../../UnitBase.t.sol"; +import { EmailRecoveryCommandHandler } from "src/handlers/EmailRecoveryCommandHandler.sol"; + +contract EmailRecoveryCommandHandler_acceptanceCommandTemplates_Test is UnitBase { + EmailRecoveryCommandHandler public emailRecoveryCommandHandler; -contract EmailRecoverySubjectHandler_acceptanceSubjectTemplates_Test is UnitBase { function setUp() public override { super.setUp(); + emailRecoveryCommandHandler = new EmailRecoveryCommandHandler(); } - function test_AcceptanceSubjectTemplates_Succeeds() public view { - string[][] memory templates = emailRecoveryHandler.acceptanceSubjectTemplates(); + function test_AcceptanceCommandTemplates_Succeeds() public view { + string[][] memory templates = emailRecoveryCommandHandler.acceptanceCommandTemplates(); assertEq(templates.length, 1); assertEq(templates[0].length, 5); diff --git a/test/unit/handlers/EmailRecoveryCommandHandler/extractRecoveredAccountFromAcceptanceCommand.t.sol b/test/unit/handlers/EmailRecoveryCommandHandler/extractRecoveredAccountFromAcceptanceCommand.t.sol new file mode 100644 index 00000000..a0c79d16 --- /dev/null +++ b/test/unit/handlers/EmailRecoveryCommandHandler/extractRecoveredAccountFromAcceptanceCommand.t.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { UnitBase } from "../../UnitBase.t.sol"; +import { EmailRecoveryCommandHandler } from "src/handlers/EmailRecoveryCommandHandler.sol"; + +contract EmailRecoveryCommandHandler_extractRecoveredAccountFromAcceptanceCommand_Test is + UnitBase +{ + EmailRecoveryCommandHandler public emailRecoveryCommandHandler; + + function setUp() public override { + super.setUp(); + emailRecoveryCommandHandler = new EmailRecoveryCommandHandler(); + } + + function test_ExtractRecoveredAccountFromAcceptanceCommand_Succeeds() public view { + bytes[] memory commandParams = new bytes[](1); + commandParams[0] = abi.encode(accountAddress1); + + address extractedAccount = emailRecoveryCommandHandler + .extractRecoveredAccountFromAcceptanceCommand(commandParams, templateIdx); + assertEq(extractedAccount, accountAddress1); + } +} diff --git a/test/unit/handlers/EmailRecoveryCommandHandler/extractRecoveredAccountFromRecoveryCommand.t.sol b/test/unit/handlers/EmailRecoveryCommandHandler/extractRecoveredAccountFromRecoveryCommand.t.sol new file mode 100644 index 00000000..5cc72d4c --- /dev/null +++ b/test/unit/handlers/EmailRecoveryCommandHandler/extractRecoveredAccountFromRecoveryCommand.t.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; +import { UnitBase } from "../../UnitBase.t.sol"; +import { EmailRecoveryCommandHandler } from "src/handlers/EmailRecoveryCommandHandler.sol"; + +contract EmailRecoveryCommandHandler_extractRecoveredAccountFromRecoveryCommand_Test is UnitBase { + using Strings for uint256; + + EmailRecoveryCommandHandler public emailRecoveryCommandHandler; + + function setUp() public override { + super.setUp(); + emailRecoveryCommandHandler = new EmailRecoveryCommandHandler(); + } + + function test_ExtractRecoveredAccountFromRecoveryCommand_Succeeds() public view { + string memory recoveryDataHashString = uint256(recoveryDataHash).toHexString(32); + + bytes[] memory commandParams = new bytes[](2); + commandParams[0] = abi.encode(accountAddress1); + commandParams[1] = abi.encode(recoveryDataHashString); + + address extractedAccount = emailRecoveryCommandHandler + .extractRecoveredAccountFromRecoveryCommand(commandParams, templateIdx); + assertEq(extractedAccount, accountAddress1); + } +} diff --git a/test/unit/handlers/EmailRecoveryCommandHandler/parseRecoveryCalldataHash.t.sol b/test/unit/handlers/EmailRecoveryCommandHandler/parseRecoveryCalldataHash.t.sol new file mode 100644 index 00000000..132bb86d --- /dev/null +++ b/test/unit/handlers/EmailRecoveryCommandHandler/parseRecoveryCalldataHash.t.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; +import { UnitBase } from "../../UnitBase.t.sol"; +import { EmailRecoveryCommandHandler } from "src/handlers/EmailRecoveryCommandHandler.sol"; + +contract EmailRecoveryCommandHandler_parseRecoveryDataHash_Test is UnitBase { + using Strings for uint256; + + EmailRecoveryCommandHandler public emailRecoveryCommandHandler; + string public recoveryDataHashString; + bytes[] public commandParams; + + function setUp() public override { + super.setUp(); + emailRecoveryCommandHandler = new EmailRecoveryCommandHandler(); + recoveryDataHashString = uint256(recoveryDataHash).toHexString(32); + + commandParams = new bytes[](2); + commandParams[0] = abi.encode(accountAddress1); + commandParams[1] = abi.encode(recoveryDataHashString); + } + + function test_ParseRecoveryDataHash_RevertWhen_InvalidTemplateIndex() public { + uint256 invalidTemplateIdx = 1; + vm.expectRevert( + abi.encodeWithSelector( + EmailRecoveryCommandHandler.InvalidTemplateIndex.selector, invalidTemplateIdx, 0 + ) + ); + emailRecoveryCommandHandler.parseRecoveryDataHash(invalidTemplateIdx, commandParams); + } + + function test_ParseRecoveryDataHash_Succeeds() public view { + bytes32 expectedRecoveryDataHash = keccak256(recoveryData); + + bytes32 _recoveryDataHash = + emailRecoveryCommandHandler.parseRecoveryDataHash(templateIdx, commandParams); + + assertEq(_recoveryDataHash, expectedRecoveryDataHash); + } +} diff --git a/test/unit/handlers/EmailRecoverySubjectHandler/recoverySubjectTemplates.t.sol b/test/unit/handlers/EmailRecoveryCommandHandler/recoveryCommandTemplates.t.sol similarity index 57% rename from test/unit/handlers/EmailRecoverySubjectHandler/recoverySubjectTemplates.t.sol rename to test/unit/handlers/EmailRecoveryCommandHandler/recoveryCommandTemplates.t.sol index f08915ba..d09d4a4b 100644 --- a/test/unit/handlers/EmailRecoverySubjectHandler/recoverySubjectTemplates.t.sol +++ b/test/unit/handlers/EmailRecoveryCommandHandler/recoveryCommandTemplates.t.sol @@ -1,16 +1,19 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; import { UnitBase } from "../../UnitBase.t.sol"; +import { EmailRecoveryCommandHandler } from "src/handlers/EmailRecoveryCommandHandler.sol"; + +contract EmailRecoveryCommandHandler_recoveryCommandTemplates_Test is UnitBase { + EmailRecoveryCommandHandler public emailRecoveryCommandHandler; -contract EmailRecoverySubjectHandler_recoverySubjectTemplates_Test is UnitBase { function setUp() public override { super.setUp(); + emailRecoveryCommandHandler = new EmailRecoveryCommandHandler(); } - function test_RecoverySubjectTemplates_Succeeds() public view { - string[][] memory templates = emailRecoveryHandler.recoverySubjectTemplates(); + function test_RecoveryCommandTemplates_Succeeds() public view { + string[][] memory templates = emailRecoveryCommandHandler.recoveryCommandTemplates(); assertEq(templates.length, 1); assertEq(templates[0].length, 7); diff --git a/test/unit/handlers/EmailRecoveryCommandHandler/validateAcceptanceCommand.t.sol b/test/unit/handlers/EmailRecoveryCommandHandler/validateAcceptanceCommand.t.sol new file mode 100644 index 00000000..69e2308f --- /dev/null +++ b/test/unit/handlers/EmailRecoveryCommandHandler/validateAcceptanceCommand.t.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { UnitBase } from "../../UnitBase.t.sol"; +import { EmailRecoveryCommandHandler } from "src/handlers/EmailRecoveryCommandHandler.sol"; + +contract EmailRecoveryCommandHandler_validateAcceptanceCommand_Test is UnitBase { + EmailRecoveryCommandHandler public emailRecoveryCommandHandler; + + function setUp() public override { + super.setUp(); + emailRecoveryCommandHandler = new EmailRecoveryCommandHandler(); + } + + function test_ValidateAcceptanceCommand_RevertWhen_InvalidTemplateIndex() public { + bytes[] memory commandParams = new bytes[](1); + commandParams[0] = abi.encode(accountAddress1); + uint256 invalidTemplateIdx = 1; + + vm.expectRevert( + abi.encodeWithSelector( + EmailRecoveryCommandHandler.InvalidTemplateIndex.selector, invalidTemplateIdx, 0 + ) + ); + emailRecoveryCommandHandler.validateAcceptanceCommand(invalidTemplateIdx, commandParams); + } + + function test_ValidateAcceptanceCommand_RevertWhen_NoCommandParams() public { + bytes[] memory emptyCommandParams; + + vm.expectRevert( + abi.encodeWithSelector( + EmailRecoveryCommandHandler.InvalidCommandParams.selector, + emptyCommandParams.length, + 1 + ) + ); + emailRecoveryCommandHandler.validateAcceptanceCommand(templateIdx, emptyCommandParams); + } + + function test_ValidateAcceptanceCommand_RevertWhen_TooManyCommandParams() public { + bytes[] memory commandParams = new bytes[](2); + commandParams[0] = abi.encode(accountAddress1); + commandParams[1] = abi.encode("extra param"); + + vm.expectRevert( + abi.encodeWithSelector( + EmailRecoveryCommandHandler.InvalidCommandParams.selector, commandParams.length, 1 + ) + ); + emailRecoveryCommandHandler.validateAcceptanceCommand(templateIdx, commandParams); + } + + function test_ValidateAcceptanceCommand_Succeeds() public view { + bytes[] memory commandParams = new bytes[](1); + commandParams[0] = abi.encode(accountAddress1); + + address account = + emailRecoveryCommandHandler.validateAcceptanceCommand(templateIdx, commandParams); + assertEq(account, accountAddress1); + } +} diff --git a/test/unit/handlers/EmailRecoveryCommandHandler/validateRecoveryCommand.t.sol b/test/unit/handlers/EmailRecoveryCommandHandler/validateRecoveryCommand.t.sol new file mode 100644 index 00000000..a375f34a --- /dev/null +++ b/test/unit/handlers/EmailRecoveryCommandHandler/validateRecoveryCommand.t.sol @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; +import { UnitBase } from "../../UnitBase.t.sol"; +import { EmailRecoveryCommandHandler } from "src/handlers/EmailRecoveryCommandHandler.sol"; + +contract EmailRecoveryCommandHandler_validateRecoveryCommand_Test is UnitBase { + using Strings for uint256; + + EmailRecoveryCommandHandler public emailRecoveryCommandHandler; + string public recoveryDataHashString; + bytes[] public commandParams; + + function setUp() public override { + super.setUp(); + emailRecoveryCommandHandler = new EmailRecoveryCommandHandler(); + recoveryDataHashString = uint256(recoveryDataHash).toHexString(32); + + commandParams = new bytes[](2); + commandParams[0] = abi.encode(accountAddress1); + commandParams[1] = abi.encode(recoveryDataHashString); + } + + function test_ValidateRecoveryCommand_RevertWhen_InvalidTemplateIndex() public { + uint256 invalidTemplateIdx = 1; + + vm.expectRevert( + abi.encodeWithSelector( + EmailRecoveryCommandHandler.InvalidTemplateIndex.selector, invalidTemplateIdx, 0 + ) + ); + emailRecoveryCommandHandler.validateRecoveryCommand(invalidTemplateIdx, commandParams); + } + + function test_ValidateRecoveryCommand_RevertWhen_NoCommandParams() public { + bytes[] memory emptyCommandParams; + + vm.expectRevert( + abi.encodeWithSelector( + EmailRecoveryCommandHandler.InvalidCommandParams.selector, + emptyCommandParams.length, + 2 + ) + ); + emailRecoveryCommandHandler.validateRecoveryCommand(templateIdx, emptyCommandParams); + } + + function test_ValidateRecoveryCommand_RevertWhen_TooManyCommandParams() public { + bytes[] memory longCommandParams = new bytes[](3); + longCommandParams[0] = abi.encode(accountAddress1); + longCommandParams[1] = abi.encode(recoveryDataHashString); + longCommandParams[2] = abi.encode("extra param"); + + vm.expectRevert( + abi.encodeWithSelector( + EmailRecoveryCommandHandler.InvalidCommandParams.selector, + longCommandParams.length, + 2 + ) + ); + emailRecoveryCommandHandler.validateRecoveryCommand(templateIdx, longCommandParams); + } + + function test_ValidateRecoveryCommand_RevertWhen_InvalidAccount() public { + commandParams[0] = abi.encode(address(0)); + + vm.expectRevert(EmailRecoveryCommandHandler.InvalidAccount.selector); + emailRecoveryCommandHandler.validateRecoveryCommand(templateIdx, commandParams); + } + + function test_ValidateRecoveryCommand_RevertWhen_ZeroRecoveryDataHash() public { + commandParams[1] = abi.encode(bytes32(0)); + + vm.expectRevert("invalid hex prefix"); + emailRecoveryCommandHandler.validateRecoveryCommand(templateIdx, commandParams); + } + + function test_ValidateRecoveryCommand_RevertWhen_InvalidHashLength() public { + commandParams[1] = abi.encode(uint256(recoveryDataHash).toHexString(33)); + + vm.expectRevert("bytes length is not 32"); + emailRecoveryCommandHandler.validateRecoveryCommand(templateIdx, commandParams); + } + + function test_ValidateRecoveryCommand_Succeeds() public view { + address accountFromEmail = + emailRecoveryCommandHandler.validateRecoveryCommand(templateIdx, commandParams); + assertEq(accountFromEmail, accountAddress1); + } +} diff --git a/test/unit/handlers/EmailRecoverySubjectHandler/extractRecoveredAccountFromAcceptanceSubject.t.sol b/test/unit/handlers/EmailRecoverySubjectHandler/extractRecoveredAccountFromAcceptanceSubject.t.sol deleted file mode 100644 index 381d47d5..00000000 --- a/test/unit/handlers/EmailRecoverySubjectHandler/extractRecoveredAccountFromAcceptanceSubject.t.sol +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.25; - -import { console2 } from "forge-std/console2.sol"; -import { UnitBase } from "../../UnitBase.t.sol"; - -contract EmailRecoverySubjectHandler_extractRecoveredAccountFromAcceptanceSubject_Test is - UnitBase -{ - function setUp() public override { - super.setUp(); - } - - function test_ExtractRecoveredAccountFromAcceptanceSubject_Succeeds() public view { - bytes[] memory subjectParams = new bytes[](1); - subjectParams[0] = abi.encode(accountAddress); - - address extractedAccount = emailRecoveryHandler.extractRecoveredAccountFromAcceptanceSubject( - subjectParams, templateIdx - ); - assertEq(extractedAccount, accountAddress); - } -} diff --git a/test/unit/handlers/EmailRecoverySubjectHandler/extractRecoveredAccountFromRecoverySubject.t.sol b/test/unit/handlers/EmailRecoverySubjectHandler/extractRecoveredAccountFromRecoverySubject.t.sol deleted file mode 100644 index f20c16dd..00000000 --- a/test/unit/handlers/EmailRecoverySubjectHandler/extractRecoveredAccountFromRecoverySubject.t.sol +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.25; - -import { console2 } from "forge-std/console2.sol"; -import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; -import { UnitBase } from "../../UnitBase.t.sol"; - -contract EmailRecoverySubjectHandler_extractRecoveredAccountFromRecoverySubject_Test is UnitBase { - using Strings for uint256; - - function setUp() public override { - super.setUp(); - } - - function test_ExtractRecoveredAccountFromRecoverySubject_Succeeds() public view { - string memory recoveryDataHashString = uint256(recoveryDataHash).toHexString(32); - - bytes[] memory subjectParams = new bytes[](3); - subjectParams[0] = abi.encode(accountAddress); - subjectParams[1] = abi.encode(recoveryModuleAddress); - subjectParams[2] = abi.encode(recoveryDataHashString); - - address extractedAccount = emailRecoveryHandler.extractRecoveredAccountFromRecoverySubject( - subjectParams, templateIdx - ); - assertEq(extractedAccount, accountAddress); - } -} diff --git a/test/unit/handlers/EmailRecoverySubjectHandler/parseRecoveryCalldataHash.t.sol b/test/unit/handlers/EmailRecoverySubjectHandler/parseRecoveryCalldataHash.t.sol deleted file mode 100644 index 0f20ab43..00000000 --- a/test/unit/handlers/EmailRecoverySubjectHandler/parseRecoveryCalldataHash.t.sol +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.25; - -import { console2 } from "forge-std/console2.sol"; -import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; -import { UnitBase } from "../../UnitBase.t.sol"; -import { EmailRecoverySubjectHandler } from "src/handlers/EmailRecoverySubjectHandler.sol"; - -contract EmailRecoverySubjectHandler_parseRecoveryDataHash_Test is UnitBase { - using Strings for uint256; - - string recoveryDataHashString; - bytes[] subjectParams; - - function setUp() public override { - super.setUp(); - - recoveryDataHashString = uint256(recoveryDataHash).toHexString(32); - - subjectParams = new bytes[](2); - subjectParams[0] = abi.encode(accountAddress); - subjectParams[1] = abi.encode(recoveryDataHashString); - } - - function test_ParseRecoveryDataHash_RevertWhen_InvalidTemplateIndex() public { - uint256 invalidTemplateIdx = 1; - vm.expectRevert( - abi.encodeWithSelector( - EmailRecoverySubjectHandler.InvalidTemplateIndex.selector, invalidTemplateIdx, 0 - ) - ); - emailRecoveryHandler.parseRecoveryDataHash(invalidTemplateIdx, subjectParams); - } - - function test_ParseRecoveryDataHash_Succeeds() public { - bytes32 expectedRecoveryDataHash = keccak256(recoveryData); - - bytes32 recoveryDataHash = - emailRecoveryHandler.parseRecoveryDataHash(templateIdx, subjectParams); - - assertEq(recoveryDataHash, expectedRecoveryDataHash); - } -} diff --git a/test/unit/handlers/EmailRecoverySubjectHandler/validateAcceptanceSubject.t.sol b/test/unit/handlers/EmailRecoverySubjectHandler/validateAcceptanceSubject.t.sol deleted file mode 100644 index 499ed75c..00000000 --- a/test/unit/handlers/EmailRecoverySubjectHandler/validateAcceptanceSubject.t.sol +++ /dev/null @@ -1,59 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.25; - -import { console2 } from "forge-std/console2.sol"; -import { EmailRecoverySubjectHandler } from "src/handlers/EmailRecoverySubjectHandler.sol"; -import { UnitBase } from "../../UnitBase.t.sol"; - -contract EmailRecoverySubjectHandler_validateAcceptanceSubject_Test is UnitBase { - function setUp() public override { - super.setUp(); - } - - function test_ValidateAcceptanceSubject_RevertWhen_InvalidTemplateIndex() public { - bytes[] memory subjectParams = new bytes[](1); - subjectParams[0] = abi.encode(accountAddress); - uint256 invalidTemplateIdx = 1; - - vm.expectRevert( - abi.encodeWithSelector( - EmailRecoverySubjectHandler.InvalidTemplateIndex.selector, invalidTemplateIdx, 0 - ) - ); - emailRecoveryHandler.validateAcceptanceSubject(invalidTemplateIdx, subjectParams); - } - - function test_ValidateAcceptanceSubject_RevertWhen_NoSubjectParams() public { - bytes[] memory emptySubjectParams; - - vm.expectRevert( - abi.encodeWithSelector( - EmailRecoverySubjectHandler.InvalidSubjectParams.selector, - emptySubjectParams.length, - 1 - ) - ); - emailRecoveryHandler.validateAcceptanceSubject(templateIdx, emptySubjectParams); - } - - function test_ValidateAcceptanceSubject_RevertWhen_TooManySubjectParams() public { - bytes[] memory subjectParams = new bytes[](2); - subjectParams[0] = abi.encode(accountAddress); - subjectParams[1] = abi.encode("extra param"); - - vm.expectRevert( - abi.encodeWithSelector( - EmailRecoverySubjectHandler.InvalidSubjectParams.selector, subjectParams.length, 1 - ) - ); - emailRecoveryHandler.validateAcceptanceSubject(templateIdx, subjectParams); - } - - function test_ValidateAcceptanceSubject_Succeeds() public view { - bytes[] memory subjectParams = new bytes[](1); - subjectParams[0] = abi.encode(accountAddress); - - address account = emailRecoveryHandler.validateAcceptanceSubject(templateIdx, subjectParams); - assertEq(account, accountAddress); - } -} diff --git a/test/unit/handlers/EmailRecoverySubjectHandler/validateRecoverySubject.t.sol b/test/unit/handlers/EmailRecoverySubjectHandler/validateRecoverySubject.t.sol deleted file mode 100644 index b8a842c5..00000000 --- a/test/unit/handlers/EmailRecoverySubjectHandler/validateRecoverySubject.t.sol +++ /dev/null @@ -1,91 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.25; - -import { console2 } from "forge-std/console2.sol"; -import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; -import { UnitBase } from "../../UnitBase.t.sol"; -import { EmailRecoverySubjectHandler } from "src/handlers/EmailRecoverySubjectHandler.sol"; - -contract EmailRecoverySubjectHandler_validateRecoverySubject_Test is UnitBase { - using Strings for uint256; - - string recoveryDataHashString; - bytes[] subjectParams; - - function setUp() public override { - super.setUp(); - - recoveryDataHashString = uint256(recoveryDataHash).toHexString(32); - - subjectParams = new bytes[](2); - subjectParams[0] = abi.encode(accountAddress); - subjectParams[1] = abi.encode(recoveryDataHashString); - } - - function test_ValidateRecoverySubject_RevertWhen_InvalidTemplateIndex() public { - uint256 invalidTemplateIdx = 1; - - vm.expectRevert( - abi.encodeWithSelector( - EmailRecoverySubjectHandler.InvalidTemplateIndex.selector, invalidTemplateIdx, 0 - ) - ); - emailRecoveryHandler.validateRecoverySubject(invalidTemplateIdx, subjectParams); - } - - function test_ValidateRecoverySubject_RevertWhen_NoSubjectParams() public { - bytes[] memory emptySubjectParams; - - vm.expectRevert( - abi.encodeWithSelector( - EmailRecoverySubjectHandler.InvalidSubjectParams.selector, - emptySubjectParams.length, - 2 - ) - ); - emailRecoveryHandler.validateRecoverySubject(templateIdx, emptySubjectParams); - } - - function test_ValidateRecoverySubject_RevertWhen_TooManySubjectParams() public { - bytes[] memory longSubjectParams = new bytes[](3); - longSubjectParams[0] = abi.encode(accountAddress); - longSubjectParams[1] = abi.encode(recoveryDataHashString); - longSubjectParams[2] = abi.encode("extra param"); - - vm.expectRevert( - abi.encodeWithSelector( - EmailRecoverySubjectHandler.InvalidSubjectParams.selector, - longSubjectParams.length, - 2 - ) - ); - emailRecoveryHandler.validateRecoverySubject(templateIdx, longSubjectParams); - } - - function test_ValidateRecoverySubject_RevertWhen_InvalidAccount() public { - subjectParams[0] = abi.encode(address(0)); - - vm.expectRevert(EmailRecoverySubjectHandler.InvalidAccount.selector); - emailRecoveryHandler.validateRecoverySubject(templateIdx, subjectParams); - } - - function test_ValidateRecoverySubject_RevertWhen_ZeroRecoveryDataHash() public { - subjectParams[1] = abi.encode(bytes32(0)); - - vm.expectRevert("invalid hex prefix"); - emailRecoveryHandler.validateRecoverySubject(templateIdx, subjectParams); - } - - function test_ValidateRecoverySubject_RevertWhen_InvalidHashLength() public { - subjectParams[1] = abi.encode(uint256(recoveryDataHash).toHexString(33)); - - vm.expectRevert("invalid hex string length"); - emailRecoveryHandler.validateRecoverySubject(templateIdx, subjectParams); - } - - function test_ValidateRecoverySubject_Succeeds() public view { - address accountFromEmail = - emailRecoveryHandler.validateRecoverySubject(templateIdx, subjectParams); - assertEq(accountFromEmail, accountAddress); - } -} diff --git a/test/unit/handlers/SafeRecoverySubjectHandler/acceptanceSubjectTemplates.t.sol b/test/unit/handlers/SafeRecoveryCommandHandler/acceptanceCommandTemplates.t.sol similarity index 55% rename from test/unit/handlers/SafeRecoverySubjectHandler/acceptanceSubjectTemplates.t.sol rename to test/unit/handlers/SafeRecoveryCommandHandler/acceptanceCommandTemplates.t.sol index 6b3bc442..8098e078 100644 --- a/test/unit/handlers/SafeRecoverySubjectHandler/acceptanceSubjectTemplates.t.sol +++ b/test/unit/handlers/SafeRecoveryCommandHandler/acceptanceCommandTemplates.t.sol @@ -1,18 +1,20 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; -import { SafeRecoverySubjectHandler } from "src/handlers/SafeRecoverySubjectHandler.sol"; import { SafeUnitBase } from "../../SafeUnitBase.t.sol"; +import { SafeRecoveryCommandHandler } from "src/handlers/SafeRecoveryCommandHandler.sol"; + +contract SafeRecoveryCommandHandler_acceptanceCommandTemplates_Test is SafeUnitBase { + SafeRecoveryCommandHandler public safeRecoveryCommandHandler; -contract SafeRecoverySubjectHandler_acceptanceSubjectTemplates_Test is SafeUnitBase { function setUp() public override { super.setUp(); + safeRecoveryCommandHandler = new SafeRecoveryCommandHandler(); } - function test_AcceptanceSubjectTemplates_Succeeds() public { + function test_AcceptanceCommandTemplates_Succeeds() public { skipIfNotSafeAccountType(); - string[][] memory templates = safeRecoverySubjectHandler.acceptanceSubjectTemplates(); + string[][] memory templates = safeRecoveryCommandHandler.acceptanceCommandTemplates(); assertEq(templates.length, 1); assertEq(templates[0].length, 5); diff --git a/test/unit/handlers/SafeRecoveryCommandHandler/extractRecoveredAccountFromAcceptanceCommand.t.sol b/test/unit/handlers/SafeRecoveryCommandHandler/extractRecoveredAccountFromAcceptanceCommand.t.sol new file mode 100644 index 00000000..9cfa7432 --- /dev/null +++ b/test/unit/handlers/SafeRecoveryCommandHandler/extractRecoveredAccountFromAcceptanceCommand.t.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { SafeUnitBase } from "../../SafeUnitBase.t.sol"; +import { SafeRecoveryCommandHandler } from "src/handlers/SafeRecoveryCommandHandler.sol"; + +contract SafeRecoveryCommandHandler_extractRecoveredAccountFromAcceptanceCommand_Test is + SafeUnitBase +{ + SafeRecoveryCommandHandler public safeRecoveryCommandHandler; + + function setUp() public override { + super.setUp(); + safeRecoveryCommandHandler = new SafeRecoveryCommandHandler(); + } + + function test_ExtractRecoveredAccountFromAcceptanceCommand_Succeeds() public { + skipIfNotSafeAccountType(); + bytes[] memory commandParams = new bytes[](1); + commandParams[0] = abi.encode(accountAddress1); + + address extractedAccount = safeRecoveryCommandHandler + .extractRecoveredAccountFromAcceptanceCommand(commandParams, templateIdx); + assertEq(extractedAccount, accountAddress1); + } +} diff --git a/test/unit/handlers/SafeRecoveryCommandHandler/extractRecoveredAccountFromRecoveryCommand.t.sol b/test/unit/handlers/SafeRecoveryCommandHandler/extractRecoveredAccountFromRecoveryCommand.t.sol new file mode 100644 index 00000000..cab6023d --- /dev/null +++ b/test/unit/handlers/SafeRecoveryCommandHandler/extractRecoveredAccountFromRecoveryCommand.t.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { SafeUnitBase } from "../../SafeUnitBase.t.sol"; +import { SafeRecoveryCommandHandler } from "src/handlers/SafeRecoveryCommandHandler.sol"; + +contract SafeRecoveryCommandHandler_extractRecoveredAccountFromRecoveryCommand_Test is + SafeUnitBase +{ + SafeRecoveryCommandHandler public safeRecoveryCommandHandler; + + function setUp() public override { + super.setUp(); + safeRecoveryCommandHandler = new SafeRecoveryCommandHandler(); + } + + function test_ExtractRecoveredAccountFromRecoveryCommand_Succeeds() public { + skipIfNotSafeAccountType(); + bytes[] memory commandParams = new bytes[](3); + commandParams[0] = abi.encode(accountAddress1); + commandParams[1] = abi.encode(newOwner1); + commandParams[2] = abi.encode(emailRecoveryModuleAddress); + + address extractedAccount = safeRecoveryCommandHandler + .extractRecoveredAccountFromRecoveryCommand(commandParams, templateIdx); + assertEq(extractedAccount, accountAddress1); + } +} diff --git a/test/unit/handlers/SafeRecoveryCommandHandler/fuzz/getPreviousOwnerInLinkedList.t.sol b/test/unit/handlers/SafeRecoveryCommandHandler/fuzz/getPreviousOwnerInLinkedList.t.sol new file mode 100644 index 00000000..f6ef8c25 --- /dev/null +++ b/test/unit/handlers/SafeRecoveryCommandHandler/fuzz/getPreviousOwnerInLinkedList.t.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { SafeNativeIntegrationBase } from + "../../../../integration/SafeRecovery/SafeNativeIntegrationBase.t.sol"; +import { SafeRecoveryCommandHandlerHarness } from "../../../SafeRecoveryCommandHandlerHarness.sol"; + +interface ISafe { + function addOwnerWithThreshold(address owner, uint256 _threshold) external; + function getOwners() external view returns (address[] memory); + function isOwner(address owner) external view returns (bool); +} + +contract SafeRecoveryCommandHandler_getPreviousOwnerInLinkedList_Fuzz_Test is + SafeNativeIntegrationBase +{ + address internal constant SENTINEL_OWNERS = address(0x1); + + SafeRecoveryCommandHandlerHarness public safeRecoveryCommandHandler; + + function setUp() public override { + super.setUp(); + safeRecoveryCommandHandler = new SafeRecoveryCommandHandlerHarness(); + } + + function testFuzz_GetPreviousOwnerInLinkedList_Succeeds(address[] memory owners) public { + skipIfNotSafeAccountType(); + + vm.startPrank(accountAddress1); + for (uint256 i = 0; i < owners.length; i++) { + if (ISafe(accountAddress1).isOwner(owners[i])) { + break; + } + vm.assume(owners[i] != address(0)); + vm.assume(owners[i] != address(1)); + vm.assume(owners[i] != accountAddress1); + ISafe(accountAddress1).addOwnerWithThreshold(owners[i], 1); + } + vm.stopPrank(); + + address[] memory ownersArr = ISafe(accountAddress1).getOwners(); + + address expectedPreviousOwner; + for (uint256 i = 1; i < ownersArr.length; i++) { + address previousOwner = safeRecoveryCommandHandler.exposed_getPreviousOwnerInLinkedList( + accountAddress1, ownersArr[i] + ); + if (i == 0) { + assertEq(previousOwner, SENTINEL_OWNERS); + } + + uint256 prevOwnerIndex = i - 1; + expectedPreviousOwner = ownersArr[prevOwnerIndex]; + + assertEq(expectedPreviousOwner, previousOwner); + } + } +} diff --git a/test/unit/handlers/SafeRecoverySubjectHandler/getPreviousOwnerInLinkedList.t.sol b/test/unit/handlers/SafeRecoveryCommandHandler/getPreviousOwnerInLinkedList.t.sol similarity index 72% rename from test/unit/handlers/SafeRecoverySubjectHandler/getPreviousOwnerInLinkedList.t.sol rename to test/unit/handlers/SafeRecoveryCommandHandler/getPreviousOwnerInLinkedList.t.sol index ad57d96b..b3986c51 100644 --- a/test/unit/handlers/SafeRecoverySubjectHandler/getPreviousOwnerInLinkedList.t.sol +++ b/test/unit/handlers/SafeRecoveryCommandHandler/getPreviousOwnerInLinkedList.t.sol @@ -1,21 +1,24 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; import { SafeUnitBase } from "../../SafeUnitBase.t.sol"; +import { SafeRecoveryCommandHandlerHarness } from "../../SafeRecoveryCommandHandlerHarness.sol"; -contract SafeRecoverySubjectHandler_getPreviousOwnerInLinkedList_Test is SafeUnitBase { +contract SafeRecoveryCommandHandler_getPreviousOwnerInLinkedList_Test is SafeUnitBase { address internal constant SENTINEL_OWNERS = address(0x1); + SafeRecoveryCommandHandlerHarness public safeRecoveryCommandHandler; + function setUp() public override { super.setUp(); + safeRecoveryCommandHandler = new SafeRecoveryCommandHandlerHarness(); } function test_GetPreviousOwnerInLinkedList_InvalidOwner_ReturnsSentinel() public { skipIfNotSafeAccountType(); address invalidOwner = address(0); - address previousOwner = safeRecoverySubjectHandler.exposed_getPreviousOwnerInLinkedList( + address previousOwner = safeRecoveryCommandHandler.exposed_getPreviousOwnerInLinkedList( accountAddress1, invalidOwner ); @@ -26,7 +29,7 @@ contract SafeRecoverySubjectHandler_getPreviousOwnerInLinkedList_Test is SafeUni skipIfNotSafeAccountType(); address invalidOwner = SENTINEL_OWNERS; - address previousOwner = safeRecoverySubjectHandler.exposed_getPreviousOwnerInLinkedList( + address previousOwner = safeRecoveryCommandHandler.exposed_getPreviousOwnerInLinkedList( accountAddress1, invalidOwner ); @@ -38,14 +41,14 @@ contract SafeRecoverySubjectHandler_getPreviousOwnerInLinkedList_Test is SafeUni address invalidAccount = address(0); vm.expectRevert(); - safeRecoverySubjectHandler.exposed_getPreviousOwnerInLinkedList(invalidAccount, owner1); + safeRecoveryCommandHandler.exposed_getPreviousOwnerInLinkedList(invalidAccount, owner1); } function test_GetPreviousOwnerInLinkedList_Succeeds() public { skipIfNotSafeAccountType(); address expectedPreviousOwner = address(1); address previousOwner = - safeRecoverySubjectHandler.exposed_getPreviousOwnerInLinkedList(accountAddress1, owner1); + safeRecoveryCommandHandler.exposed_getPreviousOwnerInLinkedList(accountAddress1, owner1); assertEq(expectedPreviousOwner, previousOwner); } @@ -54,11 +57,11 @@ contract SafeRecoverySubjectHandler_getPreviousOwnerInLinkedList_Test is SafeUni skipIfNotSafeAccountType(); address expectedPreviousOwner = address(1); address previousOwner = - safeRecoverySubjectHandler.exposed_getPreviousOwnerInLinkedList(accountAddress1, owner1); + safeRecoveryCommandHandler.exposed_getPreviousOwnerInLinkedList(accountAddress1, owner1); assertEq(expectedPreviousOwner, previousOwner); previousOwner = - safeRecoverySubjectHandler.exposed_getPreviousOwnerInLinkedList(accountAddress1, owner2); + safeRecoveryCommandHandler.exposed_getPreviousOwnerInLinkedList(accountAddress1, owner2); assertEq(expectedPreviousOwner, previousOwner); } } diff --git a/test/unit/handlers/SafeRecoveryCommandHandler/parseRecoveryCalldataHash.t.sol b/test/unit/handlers/SafeRecoveryCommandHandler/parseRecoveryCalldataHash.t.sol new file mode 100644 index 00000000..9bdb32e1 --- /dev/null +++ b/test/unit/handlers/SafeRecoveryCommandHandler/parseRecoveryCalldataHash.t.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { SafeUnitBase } from "../../SafeUnitBase.t.sol"; +import { SafeRecoveryCommandHandler } from "src/handlers/SafeRecoveryCommandHandler.sol"; + +contract SafeRecoveryCommandHandler_parseRecoveryDataHash_Test is SafeUnitBase { + SafeRecoveryCommandHandler public safeRecoveryCommandHandler; + bytes[] public commandParams; + + function setUp() public override { + super.setUp(); + + safeRecoveryCommandHandler = new SafeRecoveryCommandHandler(); + + commandParams = new bytes[](3); + commandParams[0] = abi.encode(accountAddress1); + commandParams[1] = abi.encode(owner1); + commandParams[2] = abi.encode(newOwner1); + } + + function test_ParseRecoveryDataHash_RevertWhen_InvalidTemplateIndex() public { + skipIfNotSafeAccountType(); + + uint256 invalidTemplateIdx = 1; + vm.expectRevert( + abi.encodeWithSelector( + SafeRecoveryCommandHandler.InvalidTemplateIndex.selector, invalidTemplateIdx, 0 + ) + ); + safeRecoveryCommandHandler.parseRecoveryDataHash(invalidTemplateIdx, commandParams); + } + + function test_ParseRecoveryDataHash_Succeeds() public { + skipIfNotSafeAccountType(); + + bytes32 actualRecoveryDataHash = + safeRecoveryCommandHandler.parseRecoveryDataHash(templateIdx, commandParams); + + assertEq(actualRecoveryDataHash, recoveryDataHash); + } +} diff --git a/test/unit/handlers/SafeRecoverySubjectHandler/recoverySubjectTemplates.t.sol b/test/unit/handlers/SafeRecoveryCommandHandler/recoveryCommandTemplates.t.sol similarity index 58% rename from test/unit/handlers/SafeRecoverySubjectHandler/recoverySubjectTemplates.t.sol rename to test/unit/handlers/SafeRecoveryCommandHandler/recoveryCommandTemplates.t.sol index 6f06d659..81020c95 100644 --- a/test/unit/handlers/SafeRecoverySubjectHandler/recoverySubjectTemplates.t.sol +++ b/test/unit/handlers/SafeRecoveryCommandHandler/recoveryCommandTemplates.t.sol @@ -1,17 +1,22 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; import { SafeUnitBase } from "../../SafeUnitBase.t.sol"; +import { CommandHandlerType } from "../../../Base.t.sol"; +import { SafeRecoveryCommandHandler } from "src/handlers/SafeRecoveryCommandHandler.sol"; + +contract SafeRecoveryCommandHandler_recoveryCommandTemplates_Test is SafeUnitBase { + SafeRecoveryCommandHandler public safeRecoveryCommandHandler; -contract SafeRecoverySubjectHandler_recoverySubjectTemplates_Test is SafeUnitBase { function setUp() public override { super.setUp(); + safeRecoveryCommandHandler = new SafeRecoveryCommandHandler(); } - function test_RecoverySubjectTemplates_Succeeds() public { + function test_RecoveryCommandTemplates_Succeeds() public { skipIfNotSafeAccountType(); - string[][] memory templates = safeRecoverySubjectHandler.recoverySubjectTemplates(); + skipIfNotCommandHandlerType(CommandHandlerType.SafeRecoveryCommandHandler); + string[][] memory templates = safeRecoveryCommandHandler.recoveryCommandTemplates(); assertEq(templates.length, 1); assertEq(templates[0].length, 11); diff --git a/test/unit/handlers/SafeRecoveryCommandHandler/validateAcceptanceCommand.t.sol b/test/unit/handlers/SafeRecoveryCommandHandler/validateAcceptanceCommand.t.sol new file mode 100644 index 00000000..d7c350bc --- /dev/null +++ b/test/unit/handlers/SafeRecoveryCommandHandler/validateAcceptanceCommand.t.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { SafeUnitBase } from "../../SafeUnitBase.t.sol"; +import { SafeRecoveryCommandHandler } from "src/handlers/SafeRecoveryCommandHandler.sol"; + +contract SafeRecoveryCommandHandler_validateAcceptanceCommand_Test is SafeUnitBase { + SafeRecoveryCommandHandler public safeRecoveryCommandHandler; + + function setUp() public override { + super.setUp(); + safeRecoveryCommandHandler = new SafeRecoveryCommandHandler(); + } + + function test_ValidateAcceptanceCommand_RevertWhen_InvalidTemplateIndex() public { + skipIfNotSafeAccountType(); + bytes[] memory commandParams = new bytes[](1); + commandParams[0] = abi.encode(accountAddress1); + uint256 invalidTemplateIdx = 1; + + vm.expectRevert( + abi.encodeWithSelector( + SafeRecoveryCommandHandler.InvalidTemplateIndex.selector, invalidTemplateIdx, 0 + ) + ); + safeRecoveryCommandHandler.validateAcceptanceCommand(invalidTemplateIdx, commandParams); + } + + function test_ValidateAcceptanceCommand_RevertWhen_NoCommandParams() public { + skipIfNotSafeAccountType(); + bytes[] memory emptyCommandParams; + + vm.expectRevert( + abi.encodeWithSelector( + SafeRecoveryCommandHandler.InvalidCommandParams.selector, + emptyCommandParams.length, + 1 + ) + ); + safeRecoveryCommandHandler.validateAcceptanceCommand(templateIdx, emptyCommandParams); + } + + function test_ValidateAcceptanceCommand_RevertWhen_TooManyCommandParams() public { + skipIfNotSafeAccountType(); + bytes[] memory commandParams = new bytes[](2); + commandParams[0] = abi.encode(accountAddress1); + commandParams[1] = abi.encode("extra param"); + + vm.expectRevert( + abi.encodeWithSelector( + SafeRecoveryCommandHandler.InvalidCommandParams.selector, commandParams.length, 1 + ) + ); + safeRecoveryCommandHandler.validateAcceptanceCommand(templateIdx, commandParams); + } + + function test_ValidateAcceptanceCommand_Succeeds() public { + skipIfNotSafeAccountType(); + bytes[] memory commandParams = new bytes[](1); + commandParams[0] = abi.encode(accountAddress1); + + address account = + safeRecoveryCommandHandler.validateAcceptanceCommand(templateIdx, commandParams); + assertEq(account, accountAddress1); + } +} diff --git a/test/unit/handlers/SafeRecoveryCommandHandler/validateRecoveryCommand.t.sol b/test/unit/handlers/SafeRecoveryCommandHandler/validateRecoveryCommand.t.sol new file mode 100644 index 00000000..abc6dd4d --- /dev/null +++ b/test/unit/handlers/SafeRecoveryCommandHandler/validateRecoveryCommand.t.sol @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; +import { CommandHandlerType } from "../../../Base.t.sol"; +import { SafeUnitBase } from "../../SafeUnitBase.t.sol"; +import { SafeRecoveryCommandHandler } from "src/handlers/SafeRecoveryCommandHandler.sol"; + +contract SafeRecoveryCommandHandler_validateRecoveryCommand_Test is SafeUnitBase { + using Strings for uint256; + + SafeRecoveryCommandHandler public safeRecoveryCommandHandler; + bytes[] public commandParams; + + function setUp() public override { + super.setUp(); + + safeRecoveryCommandHandler = new SafeRecoveryCommandHandler(); + + commandParams = new bytes[](3); + commandParams[0] = abi.encode(accountAddress1); + commandParams[1] = abi.encode(owner1); + commandParams[2] = abi.encode(newOwner1); + } + + function test_ValidateRecoveryCommand_RevertWhen_InvalidTemplateIndex() public { + skipIfNotSafeAccountType(); + skipIfNotCommandHandlerType(CommandHandlerType.SafeRecoveryCommandHandler); + uint256 invalidTemplateIdx = 1; + + vm.expectRevert( + abi.encodeWithSelector( + SafeRecoveryCommandHandler.InvalidTemplateIndex.selector, invalidTemplateIdx, 0 + ) + ); + safeRecoveryCommandHandler.validateRecoveryCommand(invalidTemplateIdx, commandParams); + } + + function test_ValidateAcceptanceCommand_RevertWhen_NoCommandParams() public { + skipIfNotSafeAccountType(); + skipIfNotCommandHandlerType(CommandHandlerType.SafeRecoveryCommandHandler); + bytes[] memory emptyCommandParams; + + vm.expectRevert( + abi.encodeWithSelector( + SafeRecoveryCommandHandler.InvalidCommandParams.selector, + emptyCommandParams.length, + 3 + ) + ); + safeRecoveryCommandHandler.validateRecoveryCommand(templateIdx, emptyCommandParams); + } + + function test_ValidateAcceptanceCommand_RevertWhen_TooManyCommandParams() public { + skipIfNotSafeAccountType(); + skipIfNotCommandHandlerType(CommandHandlerType.SafeRecoveryCommandHandler); + bytes[] memory longCommandParams = new bytes[](4); + longCommandParams[0] = commandParams[0]; + longCommandParams[1] = commandParams[1]; + longCommandParams[2] = commandParams[2]; + longCommandParams[3] = abi.encode("extra param"); + + vm.expectRevert( + abi.encodeWithSelector( + SafeRecoveryCommandHandler.InvalidCommandParams.selector, + longCommandParams.length, + 3 + ) + ); + safeRecoveryCommandHandler.validateRecoveryCommand(templateIdx, longCommandParams); + } + + function test_ValidateRecoveryCommand_RevertWhen_InvalidOldOwner() public { + skipIfNotSafeAccountType(); + skipIfNotCommandHandlerType(CommandHandlerType.SafeRecoveryCommandHandler); + commandParams[1] = abi.encode(address(0)); + + vm.expectRevert( + abi.encodeWithSelector(SafeRecoveryCommandHandler.InvalidOldOwner.selector, address(0)) + ); + safeRecoveryCommandHandler.validateRecoveryCommand(templateIdx, commandParams); + } + + function test_ValidateRecoveryCommand_RevertWhen_ZeroNewOwner() public { + skipIfNotSafeAccountType(); + skipIfNotCommandHandlerType(CommandHandlerType.SafeRecoveryCommandHandler); + commandParams[2] = abi.encode(address(0)); + + vm.expectRevert( + abi.encodeWithSelector(SafeRecoveryCommandHandler.InvalidNewOwner.selector, address(0)) + ); + safeRecoveryCommandHandler.validateRecoveryCommand(templateIdx, commandParams); + } + + function test_ValidateRecoveryCommand_RevertWhen_InvalidNewOwner() public { + skipIfNotSafeAccountType(); + skipIfNotCommandHandlerType(CommandHandlerType.SafeRecoveryCommandHandler); + commandParams[2] = abi.encode(owner1); + + vm.expectRevert( + abi.encodeWithSelector(SafeRecoveryCommandHandler.InvalidNewOwner.selector, owner1) + ); + safeRecoveryCommandHandler.validateRecoveryCommand(templateIdx, commandParams); + } + + function test_ValidateRecoveryCommand_Succeeds() public { + skipIfNotSafeAccountType(); + skipIfNotCommandHandlerType(CommandHandlerType.SafeRecoveryCommandHandler); + address accountFromEmail = + safeRecoveryCommandHandler.validateRecoveryCommand(templateIdx, commandParams); + assertEq(accountFromEmail, accountAddress1); + } +} diff --git a/test/unit/handlers/SafeRecoverySubjectHandler/extractRecoveredAccountFromAcceptanceSubject.t.sol b/test/unit/handlers/SafeRecoverySubjectHandler/extractRecoveredAccountFromAcceptanceSubject.t.sol deleted file mode 100644 index 4addbced..00000000 --- a/test/unit/handlers/SafeRecoverySubjectHandler/extractRecoveredAccountFromAcceptanceSubject.t.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.25; - -import { console2 } from "forge-std/console2.sol"; -import { SafeRecoverySubjectHandler } from "src/handlers/SafeRecoverySubjectHandler.sol"; -import { SafeUnitBase } from "../../SafeUnitBase.t.sol"; - -contract SafeRecoverySubjectHandler_extractRecoveredAccountFromAcceptanceSubject_Test is - SafeUnitBase -{ - function setUp() public override { - super.setUp(); - } - - function test_ExtractRecoveredAccountFromAcceptanceSubject_Succeeds() public { - skipIfNotSafeAccountType(); - bytes[] memory subjectParams = new bytes[](1); - subjectParams[0] = abi.encode(accountAddress1); - - address extractedAccount = safeRecoverySubjectHandler - .extractRecoveredAccountFromAcceptanceSubject(subjectParams, templateIdx); - assertEq(extractedAccount, accountAddress1); - } -} diff --git a/test/unit/handlers/SafeRecoverySubjectHandler/extractRecoveredAccountFromRecoverySubject.t.sol b/test/unit/handlers/SafeRecoverySubjectHandler/extractRecoveredAccountFromRecoverySubject.t.sol deleted file mode 100644 index af6a0a28..00000000 --- a/test/unit/handlers/SafeRecoverySubjectHandler/extractRecoveredAccountFromRecoverySubject.t.sol +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.25; - -import { console2 } from "forge-std/console2.sol"; -import { SafeUnitBase } from "../../SafeUnitBase.t.sol"; - -contract SafeRecoverySubjectHandler_extractRecoveredAccountFromRecoverySubject_Test is - SafeUnitBase -{ - function setUp() public override { - super.setUp(); - } - - function test_ExtractRecoveredAccountFromRecoverySubject_Succeeds() public { - skipIfNotSafeAccountType(); - bytes[] memory subjectParams = new bytes[](3); - subjectParams[0] = abi.encode(accountAddress1); - subjectParams[1] = abi.encode(newOwner1); - subjectParams[2] = abi.encode(recoveryModuleAddress); - - address extractedAccount = safeRecoverySubjectHandler - .extractRecoveredAccountFromRecoverySubject(subjectParams, templateIdx); - assertEq(extractedAccount, accountAddress1); - } -} diff --git a/test/unit/handlers/SafeRecoverySubjectHandler/parseRecoveryCalldataHash.t.sol b/test/unit/handlers/SafeRecoverySubjectHandler/parseRecoveryCalldataHash.t.sol deleted file mode 100644 index 00505f2f..00000000 --- a/test/unit/handlers/SafeRecoverySubjectHandler/parseRecoveryCalldataHash.t.sol +++ /dev/null @@ -1,40 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.25; - -import { console2 } from "forge-std/console2.sol"; -import { SafeUnitBase } from "../../SafeUnitBase.t.sol"; -import { SafeRecoverySubjectHandler } from "src/handlers/SafeRecoverySubjectHandler.sol"; - -contract SafeRecoverySubjectHandler_parseRecoveryDataHash_Test is SafeUnitBase { - bytes[] subjectParams; - - function setUp() public override { - super.setUp(); - - subjectParams = new bytes[](3); - subjectParams[0] = abi.encode(accountAddress1); - subjectParams[1] = abi.encode(owner1); - subjectParams[2] = abi.encode(newOwner1); - } - - function test_ParseRecoveryDataHash_RevertWhen_InvalidTemplateIndex() public { - skipIfNotSafeAccountType(); - - uint256 invalidTemplateIdx = 1; - vm.expectRevert( - abi.encodeWithSelector( - SafeRecoverySubjectHandler.InvalidTemplateIndex.selector, invalidTemplateIdx, 0 - ) - ); - safeRecoverySubjectHandler.parseRecoveryDataHash(invalidTemplateIdx, subjectParams); - } - - function test_ParseRecoveryDataHash_Succeeds() public { - skipIfNotSafeAccountType(); - - bytes32 actualRecoveryDataHash = - safeRecoverySubjectHandler.parseRecoveryDataHash(templateIdx, subjectParams); - - assertEq(actualRecoveryDataHash, recoveryDataHash); - } -} diff --git a/test/unit/handlers/SafeRecoverySubjectHandler/validateAcceptanceSubject.t.sol b/test/unit/handlers/SafeRecoverySubjectHandler/validateAcceptanceSubject.t.sol deleted file mode 100644 index 98203fb7..00000000 --- a/test/unit/handlers/SafeRecoverySubjectHandler/validateAcceptanceSubject.t.sol +++ /dev/null @@ -1,64 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.25; - -import { console2 } from "forge-std/console2.sol"; -import { SafeRecoverySubjectHandler } from "src/handlers/SafeRecoverySubjectHandler.sol"; -import { SafeUnitBase } from "../../SafeUnitBase.t.sol"; - -contract SafeRecoverySubjectHandler_validateAcceptanceSubject_Test is SafeUnitBase { - function setUp() public override { - super.setUp(); - } - - function test_ValidateAcceptanceSubject_RevertWhen_InvalidTemplateIndex() public { - skipIfNotSafeAccountType(); - bytes[] memory subjectParams = new bytes[](1); - subjectParams[0] = abi.encode(accountAddress1); - uint256 invalidTemplateIdx = 1; - - vm.expectRevert( - abi.encodeWithSelector( - SafeRecoverySubjectHandler.InvalidTemplateIndex.selector, invalidTemplateIdx, 0 - ) - ); - safeRecoverySubjectHandler.validateAcceptanceSubject(invalidTemplateIdx, subjectParams); - } - - function test_ValidateAcceptanceSubject_RevertWhen_NoSubjectParams() public { - skipIfNotSafeAccountType(); - bytes[] memory emptySubjectParams; - - vm.expectRevert( - abi.encodeWithSelector( - SafeRecoverySubjectHandler.InvalidSubjectParams.selector, - emptySubjectParams.length, - 1 - ) - ); - safeRecoverySubjectHandler.validateAcceptanceSubject(templateIdx, emptySubjectParams); - } - - function test_ValidateAcceptanceSubject_RevertWhen_TooManySubjectParams() public { - skipIfNotSafeAccountType(); - bytes[] memory subjectParams = new bytes[](2); - subjectParams[0] = abi.encode(accountAddress1); - subjectParams[1] = abi.encode("extra param"); - - vm.expectRevert( - abi.encodeWithSelector( - SafeRecoverySubjectHandler.InvalidSubjectParams.selector, subjectParams.length, 1 - ) - ); - safeRecoverySubjectHandler.validateAcceptanceSubject(templateIdx, subjectParams); - } - - function test_ValidateAcceptanceSubject_Succeeds() public { - skipIfNotSafeAccountType(); - bytes[] memory subjectParams = new bytes[](1); - subjectParams[0] = abi.encode(accountAddress1); - - address account = - safeRecoverySubjectHandler.validateAcceptanceSubject(templateIdx, subjectParams); - assertEq(account, accountAddress1); - } -} diff --git a/test/unit/handlers/SafeRecoverySubjectHandler/validateRecoverySubject.t.sol b/test/unit/handlers/SafeRecoverySubjectHandler/validateRecoverySubject.t.sol deleted file mode 100644 index 56a5cbf8..00000000 --- a/test/unit/handlers/SafeRecoverySubjectHandler/validateRecoverySubject.t.sol +++ /dev/null @@ -1,103 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.25; - -import { console2 } from "forge-std/console2.sol"; -import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; -import { SafeRecoverySubjectHandler } from "src/handlers/SafeRecoverySubjectHandler.sol"; -import { SafeUnitBase } from "../../SafeUnitBase.t.sol"; - -contract SafeRecoverySubjectHandler_validateRecoverySubject_Test is SafeUnitBase { - using Strings for uint256; - - bytes[] subjectParams; - - function setUp() public override { - super.setUp(); - - subjectParams = new bytes[](3); - subjectParams[0] = abi.encode(accountAddress1); - subjectParams[1] = abi.encode(owner1); - subjectParams[2] = abi.encode(newOwner1); - } - - function test_ValidateRecoverySubject_RevertWhen_InvalidTemplateIndex() public { - skipIfNotSafeAccountType(); - uint256 invalidTemplateIdx = 1; - - vm.expectRevert( - abi.encodeWithSelector( - SafeRecoverySubjectHandler.InvalidTemplateIndex.selector, invalidTemplateIdx, 0 - ) - ); - safeRecoverySubjectHandler.validateRecoverySubject(invalidTemplateIdx, subjectParams); - } - - function test_ValidateAcceptanceSubject_RevertWhen_NoSubjectParams() public { - skipIfNotSafeAccountType(); - bytes[] memory emptySubjectParams; - - vm.expectRevert( - abi.encodeWithSelector( - SafeRecoverySubjectHandler.InvalidSubjectParams.selector, - emptySubjectParams.length, - 3 - ) - ); - safeRecoverySubjectHandler.validateRecoverySubject(templateIdx, emptySubjectParams); - } - - function test_ValidateAcceptanceSubject_RevertWhen_TooManySubjectParams() public { - skipIfNotSafeAccountType(); - bytes[] memory longSubjectParams = new bytes[](4); - longSubjectParams[0] = subjectParams[0]; - longSubjectParams[1] = subjectParams[1]; - longSubjectParams[2] = subjectParams[2]; - longSubjectParams[3] = abi.encode("extra param"); - - vm.expectRevert( - abi.encodeWithSelector( - SafeRecoverySubjectHandler.InvalidSubjectParams.selector, - longSubjectParams.length, - 3 - ) - ); - safeRecoverySubjectHandler.validateRecoverySubject(templateIdx, longSubjectParams); - } - - function test_ValidateRecoverySubject_RevertWhen_InvalidOldOwner() public { - skipIfNotSafeAccountType(); - subjectParams[1] = abi.encode(address(0)); - - vm.expectRevert( - abi.encodeWithSelector(SafeRecoverySubjectHandler.InvalidOldOwner.selector, address(0)) - ); - safeRecoverySubjectHandler.validateRecoverySubject(templateIdx, subjectParams); - } - - function test_ValidateRecoverySubject_RevertWhen_ZeroNewOwner() public { - skipIfNotSafeAccountType(); - subjectParams[2] = abi.encode(address(0)); - - vm.expectRevert( - abi.encodeWithSelector(SafeRecoverySubjectHandler.InvalidNewOwner.selector, address(0)) - ); - safeRecoverySubjectHandler.validateRecoverySubject(templateIdx, subjectParams); - } - - function test_ValidateRecoverySubject_RevertWhen_InvalidNewOwner() public { - skipIfNotSafeAccountType(); - subjectParams[2] = abi.encode(owner1); - - vm.expectRevert( - abi.encodeWithSelector(SafeRecoverySubjectHandler.InvalidNewOwner.selector, owner1) - ); - safeRecoverySubjectHandler.validateRecoverySubject(templateIdx, subjectParams); - } - - function test_ValidateRecoverySubject_Succeeds() public { - skipIfNotSafeAccountType(); - address accountFromEmail = - safeRecoverySubjectHandler.validateRecoverySubject(templateIdx, subjectParams); - assertEq(accountFromEmail, accountAddress1); - } -} diff --git a/test/unit/libraries/EnumerableGuardianMap/fuzz/remove.t.sol b/test/unit/libraries/EnumerableGuardianMap/fuzz/remove.t.sol new file mode 100644 index 00000000..18f5268e --- /dev/null +++ b/test/unit/libraries/EnumerableGuardianMap/fuzz/remove.t.sol @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { UnitBase } from "../../../UnitBase.t.sol"; +import { + EnumerableGuardianMap, + GuardianStorage, + GuardianStatus +} from "src/libraries/EnumerableGuardianMap.sol"; + +/* solhint-disable gas-custom-errors */ + +contract EnumerableGuardianMap_Remove_Fuzz_Test is UnitBase { + using EnumerableGuardianMap for EnumerableGuardianMap.AddressToGuardianMap; + + mapping(address account => EnumerableGuardianMap.AddressToGuardianMap guardian) internal + guardiansStorage; + + function setUp() public override { + super.setUp(); + } + + function testFuzz_Remove_RemovesAddedKeys( + address key, + uint256 guardianStatus, + uint256 weight + ) + public + { + guardianStatus = bound(guardianStatus, 0, 2); + + guardiansStorage[accountAddress1].set({ + key: key, + value: GuardianStorage(GuardianStatus(guardianStatus), weight) + }); + GuardianStorage memory guardian = guardiansStorage[accountAddress1]._values[key]; + assertEq(uint256(guardian.status), guardianStatus); + assertEq(guardian.weight, weight); + + bool result = guardiansStorage[accountAddress1].remove(key); + assertTrue(result); + + guardian = guardiansStorage[accountAddress1]._values[key]; + assertEq(uint256(guardian.status), uint256(GuardianStatus.NONE)); + assertEq(guardian.weight, 0); + } + + function testFuzz_Remove_ReturnsFalseWhenRemovingKeysNotInTheSet(address key) public { + bool result = guardiansStorage[accountAddress1].remove(key); + assertFalse(result); + } + + function testFuzz_Remove_AddsAndRemovesMultipleKeys( + address key1, + address key2, + address key3, + uint256 guardianStatus1, + uint256 guardianStatus2, + uint256 guardianStatus3, + uint256 weight1, + uint256 weight2, + uint256 weight3 + ) + public + { + vm.assume(key1 != key2 && key1 != key3 && key2 != key3); + guardianStatus1 = bound(guardianStatus1, 0, 2); + guardianStatus2 = bound(guardianStatus2, 0, 2); + guardianStatus3 = bound(guardianStatus3, 0, 2); + bool result; + + // [] + + result = guardiansStorage[accountAddress1].set({ + key: key1, + value: GuardianStorage(GuardianStatus(guardianStatus1), weight1) + }); + assertTrue(result); + result = guardiansStorage[accountAddress1].set({ + key: key3, + value: GuardianStorage(GuardianStatus(guardianStatus3), weight3) + }); + assertTrue(result); + + GuardianStorage memory guardian1 = guardiansStorage[accountAddress1]._values[key1]; + GuardianStorage memory guardian3 = guardiansStorage[accountAddress1]._values[key3]; + assertEq(uint256(guardian1.status), guardianStatus1); + assertEq(guardian1.weight, weight1); + assertEq(uint256(guardian3.status), guardianStatus3); + assertEq(guardian3.weight, weight3); + + // [1, 3] + + result = guardiansStorage[accountAddress1].remove(key1); + assertTrue(result); + result = guardiansStorage[accountAddress1].remove(key2); + assertFalse(result); + + guardian1 = guardiansStorage[accountAddress1]._values[key1]; + assertEq(uint256(guardian1.status), uint256(GuardianStatus.NONE)); + assertEq(guardian1.weight, 0); + + // [3] + + result = guardiansStorage[accountAddress1].set({ + key: key2, + value: GuardianStorage(GuardianStatus(guardianStatus2), weight2) + }); + assertTrue(result); + + GuardianStorage memory guardian2 = guardiansStorage[accountAddress1]._values[key2]; + assertEq(uint256(guardian2.status), guardianStatus2); + assertEq(guardian2.weight, weight2); + + // [3,2] + + result = guardiansStorage[accountAddress1].set({ + key: key1, + value: GuardianStorage(GuardianStatus(guardianStatus3), weight3) + }); + assertTrue(result); + result = guardiansStorage[accountAddress1].remove(key3); + assertTrue(result); + + guardian1 = guardiansStorage[accountAddress1]._values[key1]; + guardian3 = guardiansStorage[accountAddress1]._values[key3]; + assertEq(uint256(guardian1.status), guardianStatus3); + assertEq(guardian1.weight, weight3); + assertEq(uint256(guardian3.status), uint256(GuardianStatus.NONE)); + assertEq(guardian3.weight, 0); + + // [1,2] + + result = guardiansStorage[accountAddress1].set({ + key: key1, + value: GuardianStorage(GuardianStatus(guardianStatus3), weight3) + }); + assertFalse(result); + result = guardiansStorage[accountAddress1].set({ + key: key2, + value: GuardianStorage(GuardianStatus(guardianStatus2), weight2) + }); + assertFalse(result); + + guardian1 = guardiansStorage[accountAddress1]._values[key1]; + guardian2 = guardiansStorage[accountAddress1]._values[key2]; + assertEq(uint256(guardian1.status), guardianStatus3); + assertEq(guardian1.weight, weight3); + assertEq(uint256(guardian2.status), guardianStatus2); + assertEq(guardian2.weight, weight2); + + // [1,2] + + result = guardiansStorage[accountAddress1].set({ + key: key3, + value: GuardianStorage(GuardianStatus(guardianStatus2), weight2) + }); + assertTrue(result); + result = guardiansStorage[accountAddress1].remove(key1); + assertTrue(result); + + guardian3 = guardiansStorage[accountAddress1]._values[key3]; + guardian1 = guardiansStorage[accountAddress1]._values[key1]; + assertEq(uint256(guardian3.status), guardianStatus2); + assertEq(guardian3.weight, weight2); + assertEq(uint256(guardian1.status), uint256(GuardianStatus.NONE)); + assertEq(guardian1.weight, 0); + + // [2,3] + + result = guardiansStorage[accountAddress1].set({ + key: key1, + value: GuardianStorage(GuardianStatus(guardianStatus2), weight2) + }); + assertTrue(result); + result = guardiansStorage[accountAddress1].remove(key2); + assertTrue(result); + + guardian1 = guardiansStorage[accountAddress1]._values[key1]; + guardian2 = guardiansStorage[accountAddress1]._values[key2]; + guardian3 = guardiansStorage[accountAddress1]._values[key3]; + assertEq(uint256(guardian1.status), guardianStatus2); + assertEq(guardian1.weight, weight2); + assertEq(uint256(guardian2.status), uint256(GuardianStatus.NONE)); + assertEq(guardian2.weight, 0); + assertEq(uint256(guardian3.status), guardianStatus2); + assertEq(guardian3.weight, weight2); + + // [1,3] + } +} diff --git a/test/unit/libraries/EnumerableGuardianMap/fuzz/set.t.sol b/test/unit/libraries/EnumerableGuardianMap/fuzz/set.t.sol new file mode 100644 index 00000000..01fbc137 --- /dev/null +++ b/test/unit/libraries/EnumerableGuardianMap/fuzz/set.t.sol @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { UnitBase } from "../../../UnitBase.t.sol"; +import { + EnumerableGuardianMap, + GuardianStorage, + GuardianStatus +} from "src/libraries/EnumerableGuardianMap.sol"; + +/* solhint-disable gas-custom-errors */ + +contract EnumerableGuardianMap_Set_Fuzz_Test is UnitBase { + using EnumerableGuardianMap for EnumerableGuardianMap.AddressToGuardianMap; + + mapping(address account => EnumerableGuardianMap.AddressToGuardianMap guardian) internal + guardiansStorage; + + function setUp() public override { + super.setUp(); + } + + function testFuzz_Set_AddsAKey(address key, uint256 guardianStatus, uint256 weight) public { + guardianStatus = bound(guardianStatus, 0, 2); + + bool result = guardiansStorage[accountAddress1].set({ + key: key, + value: GuardianStorage(GuardianStatus(guardianStatus), weight) + }); + + GuardianStorage memory guardian = guardiansStorage[accountAddress1]._values[key]; + + assertTrue(result); + assertEq(uint256(guardian.status), guardianStatus); + assertEq(guardian.weight, weight); + } + + function testFuzz_Set_AddsSeveralKeys( + address key1, + address key2, + address key3, + uint256 guardianStatus1, + uint256 guardianStatus2, + uint256 guardianStatus3, + uint256 weight1, + uint256 weight2, + uint256 weight3 + ) + public + { + guardianStatus1 = bound(guardianStatus1, 0, 2); + guardianStatus2 = bound(guardianStatus2, 0, 2); + guardianStatus3 = bound(guardianStatus3, 0, 2); + + bool result; + + result = guardiansStorage[vm.addr(1)].set({ + key: key1, + value: GuardianStorage(GuardianStatus(guardianStatus1), weight1) + }); + assertEq(result, true); + + result = guardiansStorage[vm.addr(2)].set({ + key: key2, + value: GuardianStorage(GuardianStatus(guardianStatus2), weight2) + }); + assertEq(result, true); + + result = guardiansStorage[vm.addr(3)].set({ + key: key3, + value: GuardianStorage(GuardianStatus(guardianStatus3), weight3) + }); + assertEq(result, true); + + GuardianStorage memory guardian1 = guardiansStorage[vm.addr(1)]._values[key1]; + assertEq(uint256(guardian1.status), guardianStatus1); + assertEq(guardian1.weight, weight1); + + GuardianStorage memory guardian2 = guardiansStorage[vm.addr(2)]._values[key2]; + assertEq(uint256(guardian2.status), guardianStatus2); + assertEq(guardian2.weight, weight2); + + GuardianStorage memory guardian3 = guardiansStorage[vm.addr(3)]._values[key3]; + assertEq(uint256(guardian3.status), guardianStatus3); + assertEq(guardian3.weight, weight3); + } + + function testFuzz_Set_ReturnsFalseWhen_AddingKeysAlreadyInTheSet( + address key, + uint256 guardianStatus, + uint256 weight + ) + public + { + guardianStatus = bound(guardianStatus, 0, 2); + + bool result; + + result = guardiansStorage[vm.addr(1)].set({ + key: key, + value: GuardianStorage(GuardianStatus(guardianStatus), weight) + }); + assertEq(result, true); + result = guardiansStorage[vm.addr(1)].set({ + key: key, + value: GuardianStorage(GuardianStatus(guardianStatus), weight) + }); + assertEq(result, false); + } + + function testFuzz_Set_UpdatesValuesForKeysAlreadyInTheSet(address key, uint256 weight) public { + bool result; + + result = guardiansStorage[vm.addr(1)].set({ + key: key, + value: GuardianStorage(GuardianStatus.REQUESTED, weight) + }); + assertEq(result, true); + result = guardiansStorage[vm.addr(1)].set({ + key: key, + value: GuardianStorage(GuardianStatus.ACCEPTED, weight) + }); + assertEq(result, false); + + GuardianStorage memory guardian = guardiansStorage[vm.addr(1)]._values[key]; + assertEq(uint256(guardian.status), uint256(GuardianStatus.ACCEPTED)); + assertEq(guardian.weight, weight); + } +} diff --git a/test/unit/libraries/EnumerableGuardianMap/get.t.sol b/test/unit/libraries/EnumerableGuardianMap/get.t.sol index 8e04d7d2..56448635 100644 --- a/test/unit/libraries/EnumerableGuardianMap/get.t.sol +++ b/test/unit/libraries/EnumerableGuardianMap/get.t.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; import { UnitBase } from "../../UnitBase.t.sol"; import { EnumerableGuardianMap, @@ -9,6 +8,8 @@ import { GuardianStatus } from "../../../../src/libraries/EnumerableGuardianMap.sol"; +/* solhint-disable gas-custom-errors */ + contract EnumerableGuardianMap_get_Test is UnitBase { using EnumerableGuardianMap for EnumerableGuardianMap.AddressToGuardianMap; @@ -17,21 +18,21 @@ contract EnumerableGuardianMap_get_Test is UnitBase { function setUp() public override { super.setUp(); - guardiansStorage[accountAddress].set({ - key: guardian1, + guardiansStorage[accountAddress1].set({ + key: guardians1[0], value: GuardianStorage(GuardianStatus.REQUESTED, guardianWeights[0]) }); } function test_Get_GetsExistingValue() public view { - GuardianStorage memory result = guardiansStorage[accountAddress].get(guardian1); + GuardianStorage memory result = guardiansStorage[accountAddress1].get(guardians1[0]); require(result.status == GuardianStatus.REQUESTED, "Expected status to be REQUESTED"); require(result.weight == guardianWeights[0], "Expected weight to be 1"); } function test_Get_GetsNonExistentValue() public view { // It will returns the default value - GuardianStorage memory result = guardiansStorage[accountAddress].get(guardian2); + GuardianStorage memory result = guardiansStorage[accountAddress1].get(guardians1[1]); require(result.status == GuardianStatus.NONE, "Expected status to be NONE"); require(result.weight == 0, "Expected weight to be 0"); } diff --git a/test/unit/libraries/EnumerableGuardianMap/keys.t.sol b/test/unit/libraries/EnumerableGuardianMap/keys.t.sol index 9c7ab12c..0185d975 100644 --- a/test/unit/libraries/EnumerableGuardianMap/keys.t.sol +++ b/test/unit/libraries/EnumerableGuardianMap/keys.t.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; import { UnitBase } from "../../UnitBase.t.sol"; import { EnumerableGuardianMap, @@ -20,7 +19,7 @@ contract EnumerableGuardianMap_keys_Test is UnitBase { } function test_Keys_StartsEmpty() public view { - address[] memory keys = guardiansStorage[accountAddress].keys(); + address[] memory keys = guardiansStorage[accountAddress1].keys(); assertEq(keys.length, 0); } @@ -28,13 +27,13 @@ contract EnumerableGuardianMap_keys_Test is UnitBase { bool result; for (uint256 i = 1; i <= 3; i++) { - result = guardiansStorage[accountAddress].set({ + result = guardiansStorage[accountAddress1].set({ key: vm.addr(i), value: GuardianStorage(GuardianStatus.REQUESTED, guardianWeights[1]) }); assertEq(result, true); } - address[] memory keys = guardiansStorage[accountAddress].keys(); + address[] memory keys = guardiansStorage[accountAddress1].keys(); assertEq(keys.length, 3); for (uint256 i = 0; i < 3; i++) { assertEq(keys[i], vm.addr(i + 1)); @@ -45,13 +44,13 @@ contract EnumerableGuardianMap_keys_Test is UnitBase { bool result; for (uint256 i = 1; i <= EnumerableGuardianMap.MAX_NUMBER_OF_GUARDIANS; i++) { - result = guardiansStorage[accountAddress].set({ + result = guardiansStorage[accountAddress1].set({ key: vm.addr(i), value: GuardianStorage(GuardianStatus.REQUESTED, guardianWeights[1]) }); assertEq(result, true); } - address[] memory keys = guardiansStorage[accountAddress].keys(); + address[] memory keys = guardiansStorage[accountAddress1].keys(); assertEq(keys.length, EnumerableGuardianMap.MAX_NUMBER_OF_GUARDIANS); for (uint256 i = 0; i < EnumerableGuardianMap.MAX_NUMBER_OF_GUARDIANS; i++) { assertEq(keys[i], vm.addr(i + 1)); diff --git a/test/unit/libraries/EnumerableGuardianMap/remove.t.sol b/test/unit/libraries/EnumerableGuardianMap/remove.t.sol index 506dac95..8cfa8ad6 100644 --- a/test/unit/libraries/EnumerableGuardianMap/remove.t.sol +++ b/test/unit/libraries/EnumerableGuardianMap/remove.t.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; import { UnitBase } from "../../UnitBase.t.sol"; import { EnumerableGuardianMap, @@ -9,6 +8,8 @@ import { GuardianStatus } from "../../../../src/libraries/EnumerableGuardianMap.sol"; +/* solhint-disable gas-custom-errors */ + contract EnumerableGuardianMap_remove_Test is UnitBase { using EnumerableGuardianMap for EnumerableGuardianMap.AddressToGuardianMap; @@ -22,14 +23,14 @@ contract EnumerableGuardianMap_remove_Test is UnitBase { function test_Remove_RemovesAddedKeys() public { bool result; - guardiansStorage[accountAddress].set({ - key: guardian1, + guardiansStorage[accountAddress1].set({ + key: guardians1[0], value: GuardianStorage(GuardianStatus.REQUESTED, guardianWeights[0]) }); - result = guardiansStorage[accountAddress].remove(guardian1); + result = guardiansStorage[accountAddress1].remove(guardians1[0]); assertEq(result, true); require( - guardiansStorage[accountAddress]._values[guardian1].status == GuardianStatus.NONE, + guardiansStorage[accountAddress1]._values[guardians1[0]].status == GuardianStatus.NONE, "Expected status to be NONE" ); } @@ -37,7 +38,7 @@ contract EnumerableGuardianMap_remove_Test is UnitBase { function test_Remove_ReturnsFalseWhenRemovingKeysNotInTheSet() public { bool result; - result = guardiansStorage[accountAddress].remove(guardian1); + result = guardiansStorage[accountAddress1].remove(guardians1[0]); assertEq(result, false); } @@ -46,86 +47,88 @@ contract EnumerableGuardianMap_remove_Test is UnitBase { // [] - result = guardiansStorage[accountAddress].set({ - key: guardian1, + result = guardiansStorage[accountAddress1].set({ + key: guardians1[0], value: GuardianStorage(GuardianStatus.REQUESTED, guardianWeights[0]) }); assertEq(result, true); - result = guardiansStorage[accountAddress].set({ - key: guardian3, + result = guardiansStorage[accountAddress1].set({ + key: guardians1[2], value: GuardianStorage(GuardianStatus.REQUESTED, guardianWeights[0]) }); assertEq(result, true); // [1, 3] - result = guardiansStorage[accountAddress].remove(guardian1); + result = guardiansStorage[accountAddress1].remove(guardians1[0]); assertEq(result, true); - result = guardiansStorage[accountAddress].remove(guardian2); + result = guardiansStorage[accountAddress1].remove(guardians1[1]); assertEq(result, false); // [3] - result = guardiansStorage[accountAddress].set({ - key: guardian2, + result = guardiansStorage[accountAddress1].set({ + key: guardians1[1], value: GuardianStorage(GuardianStatus.REQUESTED, guardianWeights[0]) }); assertEq(result, true); // [3,2] - result = guardiansStorage[accountAddress].set({ - key: guardian1, + result = guardiansStorage[accountAddress1].set({ + key: guardians1[0], value: GuardianStorage(GuardianStatus.REQUESTED, guardianWeights[0]) }); assertEq(result, true); - result = guardiansStorage[accountAddress].remove(guardian3); + result = guardiansStorage[accountAddress1].remove(guardians1[2]); assertEq(result, true); // [1,2] - result = guardiansStorage[accountAddress].set({ - key: guardian1, + result = guardiansStorage[accountAddress1].set({ + key: guardians1[0], value: GuardianStorage(GuardianStatus.REQUESTED, guardianWeights[0]) }); assertEq(result, false); - result = guardiansStorage[accountAddress].set({ - key: guardian2, + result = guardiansStorage[accountAddress1].set({ + key: guardians1[1], value: GuardianStorage(GuardianStatus.REQUESTED, guardianWeights[0]) }); assertEq(result, false); // [1,2] - result = guardiansStorage[accountAddress].set({ - key: guardian3, + result = guardiansStorage[accountAddress1].set({ + key: guardians1[2], value: GuardianStorage(GuardianStatus.REQUESTED, guardianWeights[0]) }); assertEq(result, true); - result = guardiansStorage[accountAddress].remove(guardian1); + result = guardiansStorage[accountAddress1].remove(guardians1[0]); assertEq(result, true); // [2,3] - result = guardiansStorage[accountAddress].set({ - key: guardian1, + result = guardiansStorage[accountAddress1].set({ + key: guardians1[0], value: GuardianStorage(GuardianStatus.REQUESTED, guardianWeights[0]) }); assertEq(result, true); - result = guardiansStorage[accountAddress].remove(guardian2); + result = guardiansStorage[accountAddress1].remove(guardians1[1]); assertEq(result, true); // [1,3] require( - guardiansStorage[accountAddress]._values[guardian1].status == GuardianStatus.REQUESTED, + guardiansStorage[accountAddress1]._values[guardians1[0]].status + == GuardianStatus.REQUESTED, "Expected status to be REQUESTED" ); require( - guardiansStorage[accountAddress]._values[guardian2].status == GuardianStatus.NONE, + guardiansStorage[accountAddress1]._values[guardians1[1]].status == GuardianStatus.NONE, "Expected status to be NONE" ); require( - guardiansStorage[accountAddress]._values[guardian3].status == GuardianStatus.REQUESTED, + guardiansStorage[accountAddress1]._values[guardians1[2]].status + == GuardianStatus.REQUESTED, "Expected status to be REQUESTED" ); } diff --git a/test/unit/libraries/EnumerableGuardianMap/removeAll.t.sol b/test/unit/libraries/EnumerableGuardianMap/removeAll.t.sol index e83fe4ca..390f7a32 100644 --- a/test/unit/libraries/EnumerableGuardianMap/removeAll.t.sol +++ b/test/unit/libraries/EnumerableGuardianMap/removeAll.t.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; import { UnitBase } from "../../UnitBase.t.sol"; import { EnumerableGuardianMap, @@ -9,6 +8,8 @@ import { GuardianStatus } from "../../../../src/libraries/EnumerableGuardianMap.sol"; +/* solhint-disable gas-custom-errors */ + contract EnumerableGuardianMap_removeAll_Test is UnitBase { using EnumerableGuardianMap for EnumerableGuardianMap.AddressToGuardianMap; @@ -23,7 +24,7 @@ contract EnumerableGuardianMap_removeAll_Test is UnitBase { bool result; for (uint256 i = 1; i <= 3; i++) { - result = guardiansStorage[accountAddress].set({ + result = guardiansStorage[accountAddress1].set({ key: vm.addr(i), value: GuardianStorage(GuardianStatus.REQUESTED, guardianWeights[1]) }); @@ -33,10 +34,10 @@ contract EnumerableGuardianMap_removeAll_Test is UnitBase { addresses[0] = vm.addr(1); addresses[1] = vm.addr(2); addresses[2] = vm.addr(3); - guardiansStorage[accountAddress].removeAll(addresses); + guardiansStorage[accountAddress1].removeAll(addresses); for (uint256 i = 1; i <= 3; i++) { require( - guardiansStorage[accountAddress]._values[vm.addr(i)].status == GuardianStatus.NONE, + guardiansStorage[accountAddress1]._values[vm.addr(i)].status == GuardianStatus.NONE, "Expected status to be NONE" ); } @@ -46,7 +47,7 @@ contract EnumerableGuardianMap_removeAll_Test is UnitBase { bool result; for (uint256 i = 1; i <= EnumerableGuardianMap.MAX_NUMBER_OF_GUARDIANS; i++) { - result = guardiansStorage[accountAddress].set({ + result = guardiansStorage[accountAddress1].set({ key: vm.addr(i), value: GuardianStorage(GuardianStatus.REQUESTED, guardianWeights[1]) }); @@ -57,10 +58,10 @@ contract EnumerableGuardianMap_removeAll_Test is UnitBase { for (uint256 i = 0; i < EnumerableGuardianMap.MAX_NUMBER_OF_GUARDIANS; i++) { addresses[i] = vm.addr(i + 1); } - guardiansStorage[accountAddress].removeAll(addresses); + guardiansStorage[accountAddress1].removeAll(addresses); for (uint256 i = 1; i <= EnumerableGuardianMap.MAX_NUMBER_OF_GUARDIANS; i++) { require( - guardiansStorage[accountAddress]._values[vm.addr(i)].status == GuardianStatus.NONE, + guardiansStorage[accountAddress1]._values[vm.addr(i)].status == GuardianStatus.NONE, "Expected status to be NONE" ); } @@ -74,6 +75,6 @@ contract EnumerableGuardianMap_removeAll_Test is UnitBase { addresses[i] = vm.addr(i + 1); } vm.expectRevert(EnumerableGuardianMap.TooManyValuesToRemove.selector); - guardiansStorage[accountAddress].removeAll(addresses); + guardiansStorage[accountAddress1].removeAll(addresses); } } diff --git a/test/unit/libraries/EnumerableGuardianMap/set.t.sol b/test/unit/libraries/EnumerableGuardianMap/set.t.sol index 7ce9797c..2b5926fb 100644 --- a/test/unit/libraries/EnumerableGuardianMap/set.t.sol +++ b/test/unit/libraries/EnumerableGuardianMap/set.t.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; import { UnitBase } from "../../UnitBase.t.sol"; import { EnumerableGuardianMap, @@ -9,6 +8,8 @@ import { GuardianStatus } from "../../../../src/libraries/EnumerableGuardianMap.sol"; +/* solhint-disable gas-custom-errors */ + contract EnumerableGuardianMap_set_Test is UnitBase { using EnumerableGuardianMap for EnumerableGuardianMap.AddressToGuardianMap; @@ -19,13 +20,25 @@ contract EnumerableGuardianMap_set_Test is UnitBase { super.setUp(); } + function test_Set_AddsAZeroKey() public { + guardiansStorage[accountAddress1].set({ + key: address(0), + value: GuardianStorage(GuardianStatus.REQUESTED, guardianWeights[1]) + }); + require( + guardiansStorage[accountAddress1]._values[address(0)].status == GuardianStatus.REQUESTED, + "Expected status to be REQUESTED" + ); + } + function test_Set_AddsAKey() public { - guardiansStorage[accountAddress].set({ - key: guardian1, + guardiansStorage[accountAddress1].set({ + key: guardians1[0], value: GuardianStorage(GuardianStatus.REQUESTED, guardianWeights[1]) }); require( - guardiansStorage[accountAddress]._values[guardian1].status == GuardianStatus.REQUESTED, + guardiansStorage[accountAddress1]._values[guardians1[0]].status + == GuardianStatus.REQUESTED, "Expected status to be REQUESTED" ); } @@ -34,25 +47,25 @@ contract EnumerableGuardianMap_set_Test is UnitBase { bool result; result = guardiansStorage[vm.addr(1)].set({ - key: guardian1, + key: guardians1[0], value: GuardianStorage(GuardianStatus.REQUESTED, guardianWeights[1]) }); assertEq(result, true); result = guardiansStorage[vm.addr(2)].set({ - key: guardian2, + key: guardians1[1], value: GuardianStorage(GuardianStatus.REQUESTED, guardianWeights[2]) }); assertEq(result, true); require( - guardiansStorage[vm.addr(1)]._values[guardian1].status == GuardianStatus.REQUESTED, + guardiansStorage[vm.addr(1)]._values[guardians1[0]].status == GuardianStatus.REQUESTED, "Expected status to be REQUESTED" ); require( - guardiansStorage[vm.addr(2)]._values[guardian2].status == GuardianStatus.REQUESTED, + guardiansStorage[vm.addr(2)]._values[guardians1[1]].status == GuardianStatus.REQUESTED, "Expected status to be REQUESTED" ); require( - guardiansStorage[vm.addr(3)]._values[guardian3].status == GuardianStatus.NONE, + guardiansStorage[vm.addr(3)]._values[guardians1[2]].status == GuardianStatus.NONE, "Expected status to be NONE" ); } @@ -61,12 +74,12 @@ contract EnumerableGuardianMap_set_Test is UnitBase { bool result; result = guardiansStorage[vm.addr(1)].set({ - key: guardian1, + key: guardians1[0], value: GuardianStorage(GuardianStatus.REQUESTED, guardianWeights[1]) }); assertEq(result, true); result = guardiansStorage[vm.addr(1)].set({ - key: guardian1, + key: guardians1[0], value: GuardianStorage(GuardianStatus.REQUESTED, guardianWeights[1]) }); assertEq(result, false); @@ -76,17 +89,17 @@ contract EnumerableGuardianMap_set_Test is UnitBase { bool result; result = guardiansStorage[vm.addr(1)].set({ - key: guardian1, + key: guardians1[0], value: GuardianStorage(GuardianStatus.REQUESTED, guardianWeights[1]) }); assertEq(result, true); result = guardiansStorage[vm.addr(1)].set({ - key: guardian1, + key: guardians1[0], value: GuardianStorage(GuardianStatus.ACCEPTED, guardianWeights[1]) }); assertEq(result, false); require( - guardiansStorage[vm.addr(1)]._values[guardian1].status == GuardianStatus.ACCEPTED, + guardiansStorage[vm.addr(1)]._values[guardians1[0]].status == GuardianStatus.ACCEPTED, "Expected status to be ACCEPTED" ); } @@ -94,15 +107,15 @@ contract EnumerableGuardianMap_set_Test is UnitBase { function test_Set_RevertWhen_MaxNumberOfGuardiansReached() public { bool result; for (uint256 i = 1; i <= 32; i++) { - result = guardiansStorage[accountAddress].set({ + result = guardiansStorage[accountAddress1].set({ key: vm.addr(i), value: GuardianStorage(GuardianStatus.REQUESTED, guardianWeights[1]) }); assertEq(result, true); } vm.expectRevert(EnumerableGuardianMap.MaxNumberOfGuardiansReached.selector); - guardiansStorage[accountAddress].set({ - key: guardian1, + guardiansStorage[accountAddress1].set({ + key: guardians1[0], value: GuardianStorage(GuardianStatus.REQUESTED, guardianWeights[1]) }); } @@ -110,24 +123,24 @@ contract EnumerableGuardianMap_set_Test is UnitBase { function test_Set_UpdatesValueWhenMaxNumberOfGuardiansReached() public { bool result; for (uint256 i = 1; i <= 32; i++) { - result = guardiansStorage[accountAddress].set({ + result = guardiansStorage[accountAddress1].set({ key: vm.addr(i), value: GuardianStorage(GuardianStatus.REQUESTED, guardianWeights[1]) }); assertEq(result, true); } - bool success = guardiansStorage[accountAddress].set({ + bool success = guardiansStorage[accountAddress1].set({ key: vm.addr(1), // update first guardian added in loop value: GuardianStorage(GuardianStatus.ACCEPTED, guardianWeights[0]) }); assertEq(success, false); require( - guardiansStorage[accountAddress]._values[vm.addr(1)].status == GuardianStatus.ACCEPTED, + guardiansStorage[accountAddress1]._values[vm.addr(1)].status == GuardianStatus.ACCEPTED, "Expected status to be ACCEPTED" ); require( - guardiansStorage[accountAddress]._values[vm.addr(1)].weight == guardianWeights[0], + guardiansStorage[accountAddress1]._values[vm.addr(1)].weight == guardianWeights[0], "Expected weight to be 1" ); } diff --git a/test/unit/modules/EmailRecoveryModule/EmailRecoveryModuleBase.t.sol b/test/unit/modules/EmailRecoveryModule/EmailRecoveryModuleBase.t.sol index 262daa64..03c94547 100644 --- a/test/unit/modules/EmailRecoveryModule/EmailRecoveryModuleBase.t.sol +++ b/test/unit/modules/EmailRecoveryModule/EmailRecoveryModuleBase.t.sol @@ -1,285 +1,89 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; -import { Test } from "forge-std/Test.sol"; -import { - RhinestoneModuleKit, - AccountInstance, - ModuleKitHelpers, - ModuleKitUserOp -} from "modulekit/ModuleKit.sol"; +import { ModuleKitHelpers, ModuleKitUserOp } from "modulekit/ModuleKit.sol"; import { MODULE_TYPE_EXECUTOR, MODULE_TYPE_VALIDATOR } from "modulekit/external/ERC7579.sol"; -import { ECDSAOwnedDKIMRegistry } from - "ether-email-auth/packages/contracts/src/utils/ECDSAOwnedDKIMRegistry.sol"; -import { SubjectUtils } from "ether-email-auth/packages/contracts/src/libraries/SubjectUtils.sol"; -import { - EmailAuth, - EmailAuthMsg, - EmailProof -} from "ether-email-auth/packages/contracts/src/EmailAuth.sol"; import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; -import { ECDSA } from "solady/utils/ECDSA.sol"; +import { Create2 } from "@openzeppelin/contracts/utils/Create2.sol"; -import { EmailRecoverySubjectHandler } from "src/handlers/EmailRecoverySubjectHandler.sol"; +import { BaseTest, CommandHandlerType } from "test/Base.t.sol"; +import { AccountHidingRecoveryCommandHandler } from + "src/handlers/AccountHidingRecoveryCommandHandler.sol"; import { EmailRecoveryModuleHarness } from "../../EmailRecoveryModuleHarness.sol"; import { EmailRecoveryFactory } from "src/factories/EmailRecoveryFactory.sol"; -import { OwnableValidator } from "src/test/OwnableValidator.sol"; -import { MockGroth16Verifier } from "src/test/MockGroth16Verifier.sol"; -import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; -abstract contract EmailRecoveryModuleBase is RhinestoneModuleKit, Test { +abstract contract EmailRecoveryModuleBase is BaseTest { using ModuleKitHelpers for *; using ModuleKitUserOp for *; using Strings for uint256; - // ZK Email contracts and variables - address zkEmailDeployer = vm.addr(1); - ECDSAOwnedDKIMRegistry dkimRegistry; - MockGroth16Verifier verifier; - EmailAuth emailAuthImpl; + EmailRecoveryFactory public emailRecoveryFactory; + address public commandHandlerAddress; + EmailRecoveryModuleHarness public emailRecoveryModule; - EmailRecoveryFactory emailRecoveryFactory; - EmailRecoverySubjectHandler emailRecoveryHandler; - EmailRecoveryModuleHarness emailRecoveryModule; + address public emailRecoveryModuleAddress; - address recoveryModuleAddress; - address validatorAddress; - - OwnableValidator validator; - bytes isInstalledContext; - bytes4 functionSelector; - bytes recoveryData; - bytes32 recoveryDataHash; - - // account and owners - AccountInstance instance; - address accountAddress; - address owner; - address newOwner; - - // recovery config - address[] guardians; - address guardian1; - address guardian2; - address guardian3; - uint256[] guardianWeights; - uint256 totalWeight; - uint256 delay; - uint256 expiry; - uint256 threshold; - uint256 templateIdx; - - // Account salts - bytes32 accountSalt1; - bytes32 accountSalt2; - bytes32 accountSalt3; - - string selector = "12345"; - string domainName = "gmail.com"; - bytes32 publicKeyHash = 0x0ea9c777dc7110e5a9e89b13f0cfc540e3845ba120b2b6dc24024d61488d4788; - - function setUp() public virtual { - init(); - - // Create ZK Email contracts - vm.startPrank(zkEmailDeployer); - { - ECDSAOwnedDKIMRegistry dkimImpl = new ECDSAOwnedDKIMRegistry(); - ERC1967Proxy dkimProxy = new ERC1967Proxy( - address(dkimImpl), - abi.encodeCall(dkimImpl.initialize, (zkEmailDeployer, zkEmailDeployer)) - ); - dkimRegistry = ECDSAOwnedDKIMRegistry(address(dkimProxy)); - } - string memory signedMsg = dkimRegistry.computeSignedMsg( - dkimRegistry.SET_PREFIX(), selector, domainName, publicKeyHash - ); - bytes32 digest = ECDSA.toEthSignedMessageHash(bytes(signedMsg)); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(1, digest); - bytes memory signature = abi.encodePacked(r, s, v); - dkimRegistry.setDKIMPublicKeyHash(selector, domainName, publicKeyHash, signature); - - verifier = new MockGroth16Verifier(); - - emailAuthImpl = new EmailAuth(); - vm.stopPrank(); + function setUp() public virtual override { + super.setUp(); // create owners - owner = vm.createWallet("owner").addr; - newOwner = vm.createWallet("newOwner").addr; address[] memory owners = new address[](1); - owners[0] = owner; - - // Deploy validator to be recovered - validator = new OwnableValidator(); - validatorAddress = address(validator); - isInstalledContext = bytes("0"); - functionSelector = bytes4(keccak256(bytes("changeOwner(address)"))); - bytes memory changeOwnerCalldata = abi.encodeWithSelector(functionSelector, newOwner); - recoveryData = abi.encode(validatorAddress, changeOwnerCalldata); - recoveryDataHash = keccak256(recoveryData); - - // Deploy handler and module - emailRecoveryHandler = new EmailRecoverySubjectHandler(); - emailRecoveryFactory = new EmailRecoveryFactory(address(verifier), address(emailAuthImpl)); - - emailRecoveryModule = new EmailRecoveryModuleHarness( - address(verifier), - address(dkimRegistry), - address(emailAuthImpl), - address(emailRecoveryHandler), - validatorAddress, - functionSelector - ); - recoveryModuleAddress = address(emailRecoveryModule); - - // Deploy and fund the account - instance = makeAccountInstance("account"); - accountAddress = instance.account; - vm.deal(address(instance.account), 10 ether); - - accountSalt1 = keccak256(abi.encode("account salt 1")); - accountSalt2 = keccak256(abi.encode("account salt 2")); - accountSalt3 = keccak256(abi.encode("account salt 3")); - - // Compute guardian addresses - guardian1 = emailRecoveryModule.computeEmailAuthAddress(instance.account, accountSalt1); - guardian2 = emailRecoveryModule.computeEmailAuthAddress(instance.account, accountSalt2); - guardian3 = emailRecoveryModule.computeEmailAuthAddress(instance.account, accountSalt3); - - guardians = new address[](3); - guardians[0] = guardian1; - guardians[1] = guardian2; - guardians[2] = guardian3; - - // Set recovery config variables - guardianWeights = new uint256[](3); - guardianWeights[0] = 1; - guardianWeights[1] = 2; - guardianWeights[2] = 1; - totalWeight = 4; - delay = 1 seconds; - expiry = 2 weeks; - threshold = 3; - templateIdx = 0; + owners[0] = owner1; // Install modules - instance.installModule({ + instance1.installModule({ moduleTypeId: MODULE_TYPE_VALIDATOR, module: validatorAddress, - data: abi.encode(owner) + data: abi.encode(owner1) }); - instance.installModule({ + instance1.installModule({ moduleTypeId: MODULE_TYPE_EXECUTOR, - module: recoveryModuleAddress, - data: abi.encode(isInstalledContext, guardians, guardianWeights, threshold, delay, expiry) + module: emailRecoveryModuleAddress, + data: abi.encode(isInstalledContext, guardians1, guardianWeights, threshold, delay, expiry) }); } // Helper functions - function acceptanceSubjectTemplates() public pure returns (string[][] memory) { - string[][] memory templates = new string[][](1); - templates[0] = new string[](5); - templates[0][0] = "Accept"; - templates[0][1] = "guardian"; - templates[0][2] = "request"; - templates[0][3] = "for"; - templates[0][4] = "{ethAddr}"; - return templates; - } - - function recoverySubjectTemplates() public pure returns (string[][] memory) { - string[][] memory templates = new string[][](1); - templates[0] = new string[](11); - templates[0][0] = "Recover"; - templates[0][1] = "account"; - templates[0][2] = "{ethAddr}"; - templates[0][3] = "via"; - templates[0][4] = "recovery"; - templates[0][5] = "module"; - templates[0][6] = "{ethAddr}"; - templates[0][7] = "using"; - templates[0][8] = "recovery"; - templates[0][9] = "hash"; - templates[0][10] = "{string}"; - return templates; - } - - function generateMockEmailProof( - string memory subject, - bytes32 nullifier, + function computeEmailAuthAddress( + address account, bytes32 accountSalt ) public view - returns (EmailProof memory) + override + returns (address) { - EmailProof memory emailProof; - emailProof.domainName = "gmail.com"; - emailProof.publicKeyHash = bytes32( - vm.parseUint( - "6632353713085157925504008443078919716322386156160602218536961028046468237192" - ) - ); - emailProof.timestamp = block.timestamp; - emailProof.maskedSubject = subject; - emailProof.emailNullifier = nullifier; - emailProof.accountSalt = accountSalt; - emailProof.isCodeExist = true; - emailProof.proof = bytes("0"); - - return emailProof; + return emailRecoveryModule.computeEmailAuthAddress(account, accountSalt); } - function acceptGuardian(bytes32 accountSalt) public { - string memory accountString = SubjectUtils.addressToChecksumHexString(accountAddress); - string memory subject = string.concat("Accept guardian request for ", accountString); - - bytes32 nullifier = keccak256(abi.encode("nullifier 1")); - - EmailProof memory emailProof = generateMockEmailProof(subject, nullifier, accountSalt); + function deployModule(bytes memory handlerBytecode) public override { + bytes32 commandHandlerSalt = bytes32(uint256(0)); + commandHandlerAddress = Create2.deploy(0, commandHandlerSalt, handlerBytecode); - bytes[] memory subjectParamsForAcceptance = new bytes[](1); - subjectParamsForAcceptance[0] = abi.encode(accountAddress); - EmailAuthMsg memory emailAuthMsg = EmailAuthMsg({ - templateId: emailRecoveryModule.computeAcceptanceTemplateId(templateIdx), - subjectParams: subjectParamsForAcceptance, - skipedSubjectPrefix: 0, - proof: emailProof - }); + emailRecoveryModule = new EmailRecoveryModuleHarness( + address(verifier), + address(dkimRegistry), + address(emailAuthImpl), + commandHandlerAddress, + minimumDelay, + killSwitchAuthorizer, + validatorAddress, + functionSelector + ); + emailRecoveryModuleAddress = address(emailRecoveryModule); - emailRecoveryModule.handleAcceptance(emailAuthMsg, templateIdx); + if (getCommandHandlerType() == CommandHandlerType.AccountHidingRecoveryCommandHandler) { + AccountHidingRecoveryCommandHandler(commandHandlerAddress).storeAccountHash( + accountAddress1 + ); + } } - function handleRecovery( - address recoveryModule, - bytes32 recoveryDataHash, - bytes32 accountSalt - ) - public - { - string memory accountString = SubjectUtils.addressToChecksumHexString(accountAddress); - string memory recoveryDataHashString = uint256(recoveryDataHash).toHexString(32); - - string memory subjectPart1 = string.concat("Recover account ", accountString); - string memory subjectPart2 = string.concat(" using recovery hash ", recoveryDataHashString); - string memory subject = string.concat(subjectPart1, subjectPart2); - - bytes32 nullifier = keccak256(abi.encode("nullifier 2")); - EmailProof memory emailProof = generateMockEmailProof(subject, nullifier, accountSalt); - - bytes[] memory subjectParamsForRecovery = new bytes[](2); - subjectParamsForRecovery[0] = abi.encode(accountAddress); - subjectParamsForRecovery[1] = abi.encode(recoveryDataHashString); - - EmailAuthMsg memory emailAuthMsg = EmailAuthMsg({ - templateId: emailRecoveryModule.computeRecoveryTemplateId(templateIdx), - subjectParams: subjectParamsForRecovery, - skipedSubjectPrefix: 0, - proof: emailProof - }); - emailRecoveryModule.handleRecovery(emailAuthMsg, templateIdx); + function setRecoveryData() public override { + functionSelector = bytes4(keccak256(bytes("changeOwner(address)"))); + recoveryCalldata = abi.encodeWithSelector(functionSelector, newOwner1); + recoveryData = abi.encode(validatorAddress, recoveryCalldata); + recoveryDataHash = keccak256(recoveryData); } } diff --git a/test/unit/modules/EmailRecoveryModule/canStartRecoveryRequest.t.sol b/test/unit/modules/EmailRecoveryModule/canStartRecoveryRequest.t.sol index 953a892e..bb0d239b 100644 --- a/test/unit/modules/EmailRecoveryModule/canStartRecoveryRequest.t.sol +++ b/test/unit/modules/EmailRecoveryModule/canStartRecoveryRequest.t.sol @@ -1,56 +1,78 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; -import { UnitBase } from "../../UnitBase.t.sol"; +import { ModuleKitHelpers } from "modulekit/ModuleKit.sol"; +import { MODULE_TYPE_EXECUTOR } from "modulekit/external/ERC7579.sol"; import { IGuardianManager } from "src/interfaces/IGuardianManager.sol"; import { EmailRecoveryModuleBase } from "./EmailRecoveryModuleBase.t.sol"; contract EmailRecoveryModule_canStartRecoveryRequest_Test is EmailRecoveryModuleBase { + using ModuleKitHelpers for *; + function setUp() public override { super.setUp(); } - function test_CanStartRecoveryRequest_ReturnsFalse_WhenThresholdCannotBeMet() public { - bool canStartRecoveryRequest = emailRecoveryModule.canStartRecoveryRequest(accountAddress); + function test_CanStartRecoveryRequest_ReturnsFalse_WhenThresholdIsZero() public { + instance1.uninstallModule(MODULE_TYPE_EXECUTOR, emailRecoveryModuleAddress, ""); + bool canStartRecoveryRequest = emailRecoveryModule.canStartRecoveryRequest(accountAddress1); + + IGuardianManager.GuardianConfig memory guardianConfig = + emailRecoveryModule.getGuardianConfig(accountAddress1); + + // Threshold is zero + assertFalse(canStartRecoveryRequest); + assertFalse( + guardianConfig.threshold > 0 + && guardianConfig.acceptedWeight >= guardianConfig.threshold + ); + } + + function test_CanStartRecoveryRequest_ReturnsFalse_WhenThresholdCannotBeMet() public view { + bool canStartRecoveryRequest = emailRecoveryModule.canStartRecoveryRequest(accountAddress1); - // Checking accepted weight is what we expect for this test case IGuardianManager.GuardianConfig memory guardianConfig = - emailRecoveryModule.getGuardianConfig(accountAddress); + emailRecoveryModule.getGuardianConfig(accountAddress1); // No guardians have accepted assertFalse(canStartRecoveryRequest); - assertFalse(guardianConfig.acceptedWeight >= guardianConfig.threshold); + assertFalse( + guardianConfig.threshold > 0 + && guardianConfig.acceptedWeight >= guardianConfig.threshold + ); } - function test_CanStartRecoveryRequest_ReturnsTrue_WhenThresholdIsHigherThanWeight() public { - acceptGuardian(accountSalt1); - acceptGuardian(accountSalt2); - acceptGuardian(accountSalt3); + function test_CanStartRecoveryRequest_ReturnsTrue_WhenWeightIsHigherThanThreshold() public { + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[2], emailRecoveryModuleAddress); - bool canStartRecoveryRequest = emailRecoveryModule.canStartRecoveryRequest(accountAddress); + bool canStartRecoveryRequest = emailRecoveryModule.canStartRecoveryRequest(accountAddress1); - // Checking accepted weight is what we expect for this test case IGuardianManager.GuardianConfig memory guardianConfig = - emailRecoveryModule.getGuardianConfig(accountAddress); + emailRecoveryModule.getGuardianConfig(accountAddress1); // Enough guardians have accepted so that accepted weight is higher than the threshold assertTrue(canStartRecoveryRequest); - assertTrue(guardianConfig.acceptedWeight > guardianConfig.threshold); + assertTrue( + guardianConfig.threshold > 0 && guardianConfig.acceptedWeight > guardianConfig.threshold + ); } function test_CanStartRecoveryRequest_ReturnsTrue_WhenThresholdIsEqualToWeight() public { - acceptGuardian(accountSalt1); - acceptGuardian(accountSalt2); + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); - bool canStartRecoveryRequest = emailRecoveryModule.canStartRecoveryRequest(accountAddress); + bool canStartRecoveryRequest = emailRecoveryModule.canStartRecoveryRequest(accountAddress1); - // Checking accepted weight is what we expect for this test case IGuardianManager.GuardianConfig memory guardianConfig = - emailRecoveryModule.getGuardianConfig(accountAddress); + emailRecoveryModule.getGuardianConfig(accountAddress1); // Enough guardians have accepted so that accepted weight is equal to the threshold assertTrue(canStartRecoveryRequest); - assertTrue(guardianConfig.acceptedWeight == guardianConfig.threshold); + assertTrue( + guardianConfig.threshold > 0 + && guardianConfig.acceptedWeight == guardianConfig.threshold + ); } } diff --git a/test/unit/modules/EmailRecoveryModule/constructor.t.sol b/test/unit/modules/EmailRecoveryModule/constructor.t.sol index cd674489..1838d2d7 100644 --- a/test/unit/modules/EmailRecoveryModule/constructor.t.sol +++ b/test/unit/modules/EmailRecoveryModule/constructor.t.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; import { IModule } from "erc7579/interfaces/IERC7579Module.sol"; -import { IERC7579Account } from "erc7579/interfaces/IERC7579Account.sol"; import { ISafe } from "src/interfaces/ISafe.sol"; import { EmailRecoveryModuleBase } from "./EmailRecoveryModuleBase.t.sol"; import { EmailRecoveryModule } from "src/modules/EmailRecoveryModule.sol"; -import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; contract EmailRecoveryModule_constructor_Test is EmailRecoveryModuleBase { function setUp() public override { @@ -23,55 +20,65 @@ contract EmailRecoveryModule_constructor_Test is EmailRecoveryModuleBase { address(verifier), address(dkimRegistry), address(emailAuthImpl), - address(emailRecoveryHandler), + commandHandlerAddress, + minimumDelay, + killSwitchAuthorizer, invalidValidator, functionSelector ); } function test_Constructor_When_SafeAddOwnerSelector() public { - _skipIfNotSafeAccountType(); + skipIfNotSafeAccountType(); new EmailRecoveryModule( address(verifier), address(dkimRegistry), address(emailAuthImpl), - address(emailRecoveryHandler), + commandHandlerAddress, + minimumDelay, + killSwitchAuthorizer, validatorAddress, ISafe.addOwnerWithThreshold.selector ); } function test_Constructor_When_SafeRemoveOwnerSelector() public { - _skipIfNotSafeAccountType(); + skipIfNotSafeAccountType(); new EmailRecoveryModule( address(verifier), address(dkimRegistry), address(emailAuthImpl), - address(emailRecoveryHandler), + commandHandlerAddress, + minimumDelay, + killSwitchAuthorizer, validatorAddress, ISafe.removeOwner.selector ); } function test_Constructor_When_SafeSwapOwnerSelector() public { - _skipIfNotSafeAccountType(); + skipIfNotSafeAccountType(); new EmailRecoveryModule( address(verifier), address(dkimRegistry), address(emailAuthImpl), - address(emailRecoveryHandler), + commandHandlerAddress, + minimumDelay, + killSwitchAuthorizer, validatorAddress, ISafe.swapOwner.selector ); } function test_Constructor_When_SafeChangeThresholdSelector() public { - _skipIfNotSafeAccountType(); + skipIfNotSafeAccountType(); new EmailRecoveryModule( address(verifier), address(dkimRegistry), address(emailAuthImpl), - address(emailRecoveryHandler), + commandHandlerAddress, + minimumDelay, + killSwitchAuthorizer, validatorAddress, ISafe.changeThreshold.selector ); @@ -87,7 +94,9 @@ contract EmailRecoveryModule_constructor_Test is EmailRecoveryModuleBase { address(verifier), address(dkimRegistry), address(emailAuthImpl), - address(emailRecoveryHandler), + commandHandlerAddress, + minimumDelay, + killSwitchAuthorizer, validatorAddress, IModule.onInstall.selector ); @@ -103,7 +112,9 @@ contract EmailRecoveryModule_constructor_Test is EmailRecoveryModuleBase { address(verifier), address(dkimRegistry), address(emailAuthImpl), - address(emailRecoveryHandler), + commandHandlerAddress, + minimumDelay, + killSwitchAuthorizer, validatorAddress, IModule.onUninstall.selector ); @@ -117,7 +128,9 @@ contract EmailRecoveryModule_constructor_Test is EmailRecoveryModuleBase { address(verifier), address(dkimRegistry), address(emailAuthImpl), - address(emailRecoveryHandler), + commandHandlerAddress, + minimumDelay, + killSwitchAuthorizer, validatorAddress, bytes4(0) ); @@ -128,7 +141,9 @@ contract EmailRecoveryModule_constructor_Test is EmailRecoveryModuleBase { address(verifier), address(dkimRegistry), address(emailAuthImpl), - address(emailRecoveryHandler), + commandHandlerAddress, + minimumDelay, + killSwitchAuthorizer, validatorAddress, functionSelector ); @@ -136,13 +151,4 @@ contract EmailRecoveryModule_constructor_Test is EmailRecoveryModuleBase { assertEq(validatorAddress, emailRecoveryModule.validator()); assertEq(functionSelector, emailRecoveryModule.selector()); } - - function _skipIfNotSafeAccountType() private { - string memory currentAccountType = vm.envOr("ACCOUNT_TYPE", string("")); - if (Strings.equal(currentAccountType, "SAFE")) { - vm.skip(false); - } else { - vm.skip(true); - } - } } diff --git a/test/unit/modules/EmailRecoveryModule/isInitialized.t.sol b/test/unit/modules/EmailRecoveryModule/isInitialized.t.sol index ff23f40d..bb9a7a9a 100644 --- a/test/unit/modules/EmailRecoveryModule/isInitialized.t.sol +++ b/test/unit/modules/EmailRecoveryModule/isInitialized.t.sol @@ -1,14 +1,38 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; +import { ModuleKitHelpers } from "modulekit/ModuleKit.sol"; import { EmailRecoveryModuleBase } from "./EmailRecoveryModuleBase.t.sol"; +import { MODULE_TYPE_EXECUTOR } from "modulekit/external/ERC7579.sol"; contract EmailRecoveryModule_isInitialized_Test is EmailRecoveryModuleBase { + using ModuleKitHelpers for *; + function setUp() public override { super.setUp(); } - function test_IsInitialized_ReturnsTrueWhenInitialized() public view { } - function test_IsInitialized_ReturnsFalseWhenUninitialized() public view { } + function test_IsInitialized_ReturnsTrueWhenInitialized() public { + instance1.uninstallModule(MODULE_TYPE_EXECUTOR, emailRecoveryModuleAddress, ""); + + // Install and initialize the module + instance1.installModule({ + moduleTypeId: MODULE_TYPE_EXECUTOR, + module: emailRecoveryModuleAddress, + data: abi.encode(isInstalledContext, guardians1, guardianWeights, threshold, delay, expiry) + }); + + // Verify that the module is initialized + bool isInitialized = emailRecoveryModule.isInitialized(accountAddress1); + assertTrue(isInitialized); + } + + function test_IsInitialized_ReturnsFalseWhenUninitialized() public { + // Uninstall the module to make it uninitialized + instance1.uninstallModule(MODULE_TYPE_EXECUTOR, emailRecoveryModuleAddress, ""); + + // Verify that the module is uninitialized + bool isInitialized = emailRecoveryModule.isInitialized(accountAddress1); + assertFalse(isInitialized); + } } diff --git a/test/unit/modules/EmailRecoveryModule/isModuleType.t.sol b/test/unit/modules/EmailRecoveryModule/isModuleType.t.sol index c208446c..54ac2c4f 100644 --- a/test/unit/modules/EmailRecoveryModule/isModuleType.t.sol +++ b/test/unit/modules/EmailRecoveryModule/isModuleType.t.sol @@ -1,13 +1,33 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; +import { ModuleKitHelpers } from "modulekit/ModuleKit.sol"; import { EmailRecoveryModuleBase } from "./EmailRecoveryModuleBase.t.sol"; +import { MODULE_TYPE_EXECUTOR, MODULE_TYPE_VALIDATOR } from "modulekit/external/ERC7579.sol"; contract EmailRecoveryModule_isModuleType_Test is EmailRecoveryModuleBase { + using ModuleKitHelpers for *; + function setUp() public override { super.setUp(); } - function test_IsModuleType_ReturnsModuleType() public view { } + function test_IsModuleType_ReturnsModuleType() public { + // Uninstall the module to ensure a clean state + instance1.uninstallModule(MODULE_TYPE_EXECUTOR, emailRecoveryModuleAddress, ""); + + // Install and initialize the module + instance1.installModule({ + moduleTypeId: MODULE_TYPE_EXECUTOR, + module: emailRecoveryModuleAddress, + data: abi.encode(isInstalledContext, guardians1, guardianWeights, threshold, delay, expiry) + }); + + // Verify that the module type is correct + bool isExecutor = emailRecoveryModule.isModuleType(MODULE_TYPE_EXECUTOR); + assertTrue(isExecutor, "Should be an executor module"); + + bool isValidator = emailRecoveryModule.isModuleType(MODULE_TYPE_VALIDATOR); + assertFalse(isValidator, "Should not be a validator module"); + } } diff --git a/test/unit/modules/EmailRecoveryModule/name.t.sol b/test/unit/modules/EmailRecoveryModule/name.t.sol index e6bdb3a2..736f020d 100644 --- a/test/unit/modules/EmailRecoveryModule/name.t.sol +++ b/test/unit/modules/EmailRecoveryModule/name.t.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; import { EmailRecoveryModuleBase } from "./EmailRecoveryModuleBase.t.sol"; contract EmailRecoveryModule_name_Test is EmailRecoveryModuleBase { @@ -9,5 +8,9 @@ contract EmailRecoveryModule_name_Test is EmailRecoveryModuleBase { super.setUp(); } - function test_Name_ReturnsName() public view { } + function test_Name_ReturnsName() public { + string memory expectedName = "ZKEmail.EmailRecoveryModule"; + string memory actualName = emailRecoveryModule.name(); + assertEq(actualName, expectedName, "Module name should match expected value"); + } } diff --git a/test/unit/modules/EmailRecoveryModule/onInstall.t.sol b/test/unit/modules/EmailRecoveryModule/onInstall.t.sol index 8cc5d404..0da57d1e 100644 --- a/test/unit/modules/EmailRecoveryModule/onInstall.t.sol +++ b/test/unit/modules/EmailRecoveryModule/onInstall.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; -import { AccountInstance, ModuleKitHelpers } from "modulekit/ModuleKit.sol"; +import { ModuleKitHelpers } from "modulekit/ModuleKit.sol"; import { MODULE_TYPE_EXECUTOR, MODULE_TYPE_VALIDATOR } from "modulekit/external/ERC7579.sol"; import { EmailRecoveryModule } from "src/modules/EmailRecoveryModule.sol"; @@ -16,41 +15,41 @@ contract EmailRecoveryModule_onInstall_Test is EmailRecoveryModuleBase { } function test_OnInstall_RevertWhen_InvalidOnInstallData() public { - instance.uninstallModule(MODULE_TYPE_EXECUTOR, recoveryModuleAddress, ""); + instance1.uninstallModule(MODULE_TYPE_EXECUTOR, emailRecoveryModuleAddress, ""); bytes memory emptyData = new bytes(0); assertEq(emptyData.length, 0); - vm.startPrank(accountAddress); + vm.startPrank(accountAddress1); vm.expectRevert(EmailRecoveryModule.InvalidOnInstallData.selector); emailRecoveryModule.onInstall(emptyData); } function test_OnInstall_RevertWhen_InvalidValidator() public { - instance.uninstallModule(MODULE_TYPE_VALIDATOR, validatorAddress, ""); + instance1.uninstallModule(MODULE_TYPE_VALIDATOR, validatorAddress, ""); vm.expectRevert( abi.encodeWithSelector(EmailRecoveryModule.InvalidValidator.selector, validatorAddress) ); - vm.startPrank(accountAddress); + vm.startPrank(accountAddress1); emailRecoveryModule.onInstall( - abi.encode(isInstalledContext, guardians, guardianWeights, threshold, delay, expiry) + abi.encode(isInstalledContext, guardians1, guardianWeights, threshold, delay, expiry) ); } function test_OnInstall_Succeeds() public { - instance.uninstallModule(MODULE_TYPE_EXECUTOR, recoveryModuleAddress, ""); + instance1.uninstallModule(MODULE_TYPE_EXECUTOR, emailRecoveryModuleAddress, ""); - instance.installModule({ + instance1.installModule({ moduleTypeId: MODULE_TYPE_EXECUTOR, - module: recoveryModuleAddress, - data: abi.encode(isInstalledContext, guardians, guardianWeights, threshold, delay, expiry) + module: emailRecoveryModuleAddress, + data: abi.encode(isInstalledContext, guardians1, guardianWeights, threshold, delay, expiry) }); - bool isInitialized = emailRecoveryModule.isInitialized(accountAddress); + bool isInitialized = emailRecoveryModule.isInitialized(accountAddress1); assertTrue(isInitialized); - bool isActivated = emailRecoveryModule.isActivated(accountAddress); + bool isActivated = emailRecoveryModule.isActivated(accountAddress1); assertTrue(isActivated); } } diff --git a/test/unit/modules/EmailRecoveryModule/onUninstall.t.sol b/test/unit/modules/EmailRecoveryModule/onUninstall.t.sol index 807a16fb..1b69c861 100644 --- a/test/unit/modules/EmailRecoveryModule/onUninstall.t.sol +++ b/test/unit/modules/EmailRecoveryModule/onUninstall.t.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; import { ModuleKitHelpers } from "modulekit/ModuleKit.sol"; import { MODULE_TYPE_EXECUTOR } from "modulekit/external/ERC7579.sol"; import { SentinelListHelper } from "sentinellist/SentinelListHelper.sol"; @@ -16,14 +15,12 @@ contract EmailRecoveryModule_onUninstall_Test is EmailRecoveryModuleBase { } function test_OnUninstall_Succeeds() public { - vm.prank(accountAddress); - instance.uninstallModule(MODULE_TYPE_EXECUTOR, recoveryModuleAddress, ""); - vm.stopPrank(); + instance1.uninstallModule(MODULE_TYPE_EXECUTOR, emailRecoveryModuleAddress, ""); - bool isInitialized = emailRecoveryModule.isInitialized(accountAddress); + bool isInitialized = emailRecoveryModule.isInitialized(accountAddress1); assertFalse(isInitialized); - bool isActivated = emailRecoveryModule.isActivated(accountAddress); + bool isActivated = emailRecoveryModule.isActivated(accountAddress1); assertFalse(isActivated); } } diff --git a/test/unit/modules/EmailRecoveryModule/recover.t.sol b/test/unit/modules/EmailRecoveryModule/recover.t.sol index 559020f0..b7cc3e84 100644 --- a/test/unit/modules/EmailRecoveryModule/recover.t.sol +++ b/test/unit/modules/EmailRecoveryModule/recover.t.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; import { EmailRecoveryModule } from "src/modules/EmailRecoveryModule.sol"; import { EmailRecoveryModuleBase } from "./EmailRecoveryModuleBase.t.sol"; @@ -15,67 +14,70 @@ contract EmailRecoveryModule_recover_Test is EmailRecoveryModuleBase { function test_Recover_RevertWhen_InvalidCalldataSelector() public { bytes4 invalidSelector = bytes4(keccak256(bytes("wrongSelector(address,address,address)"))); - bytes memory invalidCalldata = - abi.encodeWithSelector(invalidSelector, accountAddress, recoveryModuleAddress, newOwner); - bytes memory invalidData = abi.encode(accountAddress, invalidCalldata); + bytes memory invalidCalldata = abi.encodeWithSelector( + invalidSelector, accountAddress1, emailRecoveryModuleAddress, newOwner1 + ); + bytes memory invalidData = abi.encode(accountAddress1, invalidCalldata); - vm.startPrank(recoveryModuleAddress); + vm.startPrank(emailRecoveryModuleAddress); vm.expectRevert( abi.encodeWithSelector(EmailRecoveryModule.InvalidSelector.selector, invalidSelector) ); - emailRecoveryModule.exposed_recover(accountAddress, invalidData); + emailRecoveryModule.exposed_recover(accountAddress1, invalidData); } function test_Recover_RevertWhen_InvalidZeroCalldataSelector() public { bytes memory invalidChangeOwnerCaldata = bytes("0x"); - bytes memory invalidCalldata = abi.encode(accountAddress, invalidChangeOwnerCaldata); + bytes memory invalidCalldata = abi.encode(accountAddress1, invalidChangeOwnerCaldata); bytes4 expectedSelector; + // solhint-disable-next-line no-inline-assembly assembly { expectedSelector := mload(add(invalidChangeOwnerCaldata, 32)) } - vm.startPrank(recoveryModuleAddress); + vm.startPrank(emailRecoveryModuleAddress); vm.expectRevert( abi.encodeWithSelector(EmailRecoveryModule.InvalidSelector.selector, expectedSelector) ); - emailRecoveryModule.exposed_recover(accountAddress, invalidCalldata); + emailRecoveryModule.exposed_recover(accountAddress1, invalidCalldata); } function test_Recover_RevertWhen_CalldataWithoutValidator() public { bytes4 invalidSelector = bytes4(keccak256(bytes("wrongSelector(address,address,address)"))); - bytes memory calldataWithoutValidator = - abi.encodeWithSelector(invalidSelector, accountAddress, recoveryModuleAddress, newOwner); + bytes memory calldataWithoutValidator = abi.encodeWithSelector( + invalidSelector, accountAddress1, emailRecoveryModuleAddress, newOwner1 + ); - vm.startPrank(recoveryModuleAddress); + vm.startPrank(emailRecoveryModuleAddress); vm.expectRevert(); - emailRecoveryModule.exposed_recover(accountAddress, calldataWithoutValidator); + emailRecoveryModule.exposed_recover(accountAddress1, calldataWithoutValidator); } function test_Recover_RevertWhen_RecoveryDataWithTruncatedValidatorAddress() public { - bytes memory validCalldata = abi.encodeWithSelector(functionSelector, newOwner); - bytes memory invalidData = abi.encode(bytes8(bytes20(accountAddress)), validCalldata); + bytes memory validCalldata = abi.encodeWithSelector(functionSelector, newOwner1); + bytes memory invalidData = abi.encode(bytes8(bytes20(accountAddress1)), validCalldata); - vm.startPrank(recoveryModuleAddress); + vm.startPrank(emailRecoveryModuleAddress); vm.expectRevert(); - emailRecoveryModule.exposed_recover(accountAddress, invalidData); + emailRecoveryModule.exposed_recover(accountAddress1, invalidData); } function test_Recover_DoesNotRevertWhen_ZeroAddress() public { - bytes memory validCalldata = abi.encodeWithSelector(functionSelector, newOwner); + bytes memory validCalldata = abi.encodeWithSelector(functionSelector, newOwner1); bytes memory dataWithZeroAddress = abi.encode(address(0), validCalldata); - vm.startPrank(recoveryModuleAddress); - emailRecoveryModule.exposed_recover(accountAddress, dataWithZeroAddress); + vm.startPrank(emailRecoveryModuleAddress); + emailRecoveryModule.exposed_recover(accountAddress1, dataWithZeroAddress); } function test_Recover_Succeeds() public { - vm.startPrank(recoveryModuleAddress); + vm.startPrank(emailRecoveryModuleAddress); vm.expectEmit(); - emit EmailRecoveryModule.RecoveryExecuted(accountAddress, validatorAddress); - emailRecoveryModule.exposed_recover(accountAddress, recoveryData); + emit EmailRecoveryModule.RecoveryExecuted(accountAddress1, validatorAddress); + emailRecoveryModule.exposed_recover(accountAddress1, recoveryData); - address updatedOwner = validator.owners(accountAddress); - assertEq(updatedOwner, newOwner); + address updatedOwner = validator.owners(accountAddress1); + assertEq(updatedOwner, newOwner1); } } diff --git a/test/unit/modules/EmailRecoveryModule/version.t.sol b/test/unit/modules/EmailRecoveryModule/version.t.sol index 54b6416e..617bbe69 100644 --- a/test/unit/modules/EmailRecoveryModule/version.t.sol +++ b/test/unit/modules/EmailRecoveryModule/version.t.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; import { EmailRecoveryModuleBase } from "./EmailRecoveryModuleBase.t.sol"; contract EmailRecoveryModule_version_Test is EmailRecoveryModuleBase { @@ -9,5 +8,9 @@ contract EmailRecoveryModule_version_Test is EmailRecoveryModuleBase { super.setUp(); } - function test_Version_ReturnsVersion() public view { } + function test_Version_ReturnsVersion() public { + string memory expectedVersion = "1.0.0"; + string memory actualVersion = emailRecoveryModule.version(); + assertEq(actualVersion, expectedVersion, "Module version should match expected value"); + } } diff --git a/test/unit/modules/SafeEmailRecoveryModule/canStartRecoveryRequest.t.sol b/test/unit/modules/SafeEmailRecoveryModule/canStartRecoveryRequest.t.sol new file mode 100644 index 00000000..d531917e --- /dev/null +++ b/test/unit/modules/SafeEmailRecoveryModule/canStartRecoveryRequest.t.sol @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { EmailAuthMsg } from "@zk-email/ether-email-auth-contracts/src/EmailAuth.sol"; +import { IGuardianManager } from "src/interfaces/IGuardianManager.sol"; +import { SafeNativeIntegrationBase } from + "../../../integration/SafeRecovery/SafeNativeIntegrationBase.t.sol"; + +contract SafeEmailRecoveryModule_canStartRecoveryRequest_Test is SafeNativeIntegrationBase { + function setUp() public override { + super.setUp(); + } + + function test_CanStartRecoveryRequest_ReturnsFalse_WhenThresholdIsZero() public { + skipIfNotSafeAccountType(); + + bool canStartRecoveryRequest = emailRecoveryModule.canStartRecoveryRequest(accountAddress1); + + IGuardianManager.GuardianConfig memory guardianConfig = + emailRecoveryModule.getGuardianConfig(accountAddress1); + + // Threshold is zero + assertFalse(canStartRecoveryRequest); + assertFalse( + guardianConfig.threshold > 0 + && guardianConfig.acceptedWeight >= guardianConfig.threshold + ); + } + + function test_CanStartRecoveryRequest_ReturnsFalse_WhenThresholdCannotBeMet() public { + skipIfNotSafeAccountType(); + vm.startPrank(accountAddress1); + emailRecoveryModule.configureSafeRecovery( + guardians1, guardianWeights, threshold, delay, expiry + ); + vm.stopPrank(); + + bool canStartRecoveryRequest = emailRecoveryModule.canStartRecoveryRequest(accountAddress1); + + IGuardianManager.GuardianConfig memory guardianConfig = + emailRecoveryModule.getGuardianConfig(accountAddress1); + + // No guardians have accepted + assertFalse(canStartRecoveryRequest); + assertFalse( + guardianConfig.threshold > 0 + && guardianConfig.acceptedWeight >= guardianConfig.threshold + ); + } + + function test_CanStartRecoveryRequest_ReturnsTrue_WhenWeightIsHigherThanThreshold() public { + skipIfNotSafeAccountType(); + vm.startPrank(accountAddress1); + emailRecoveryModule.configureSafeRecovery( + guardians1, guardianWeights, threshold, delay, expiry + ); + vm.stopPrank(); + + EmailAuthMsg memory emailAuthMsg = getAcceptanceEmailAuthMessageWithAccountSalt( + accountAddress1, guardians1[0], emailRecoveryModuleAddress, accountSalt1 + ); + emailRecoveryModule.handleAcceptance(emailAuthMsg, templateIdx); + emailAuthMsg = getAcceptanceEmailAuthMessageWithAccountSalt( + accountAddress1, guardians1[1], emailRecoveryModuleAddress, accountSalt2 + ); + emailRecoveryModule.handleAcceptance(emailAuthMsg, templateIdx); + emailAuthMsg = getAcceptanceEmailAuthMessageWithAccountSalt( + accountAddress1, guardians1[2], emailRecoveryModuleAddress, accountSalt3 + ); + emailRecoveryModule.handleAcceptance(emailAuthMsg, templateIdx); + + bool canStartRecoveryRequest = emailRecoveryModule.canStartRecoveryRequest(accountAddress1); + + IGuardianManager.GuardianConfig memory guardianConfig = + emailRecoveryModule.getGuardianConfig(accountAddress1); + + // Enough guardians have accepted so that accepted weight is higher than the threshold + assertTrue(canStartRecoveryRequest); + assertTrue( + guardianConfig.threshold > 0 && guardianConfig.acceptedWeight > guardianConfig.threshold + ); + } + + function test_CanStartRecoveryRequest_ReturnsTrue_WhenThresholdIsEqualToWeight() public { + skipIfNotSafeAccountType(); + vm.startPrank(accountAddress1); + emailRecoveryModule.configureSafeRecovery( + guardians1, guardianWeights, threshold, delay, expiry + ); + vm.stopPrank(); + + EmailAuthMsg memory emailAuthMsg = getAcceptanceEmailAuthMessageWithAccountSalt( + accountAddress1, guardians1[0], emailRecoveryModuleAddress, accountSalt1 + ); + emailRecoveryModule.handleAcceptance(emailAuthMsg, templateIdx); + emailAuthMsg = getAcceptanceEmailAuthMessageWithAccountSalt( + accountAddress1, guardians1[1], emailRecoveryModuleAddress, accountSalt2 + ); + emailRecoveryModule.handleAcceptance(emailAuthMsg, templateIdx); + + bool canStartRecoveryRequest = emailRecoveryModule.canStartRecoveryRequest(accountAddress1); + + IGuardianManager.GuardianConfig memory guardianConfig = + emailRecoveryModule.getGuardianConfig(accountAddress1); + + // Enough guardians have accepted so that accepted weight is equal to the threshold + assertTrue(canStartRecoveryRequest); + assertTrue( + guardianConfig.threshold > 0 + && guardianConfig.acceptedWeight == guardianConfig.threshold + ); + } +} diff --git a/test/unit/modules/SafeEmailRecoveryModule/configureSafeRecovery.t.sol b/test/unit/modules/SafeEmailRecoveryModule/configureSafeRecovery.t.sol index 3a383db3..b321a892 100644 --- a/test/unit/modules/SafeEmailRecoveryModule/configureSafeRecovery.t.sol +++ b/test/unit/modules/SafeEmailRecoveryModule/configureSafeRecovery.t.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; import { SafeEmailRecoveryModule } from "src/modules/SafeEmailRecoveryModule.sol"; import { SafeNativeIntegrationBase } from "../../../integration/SafeRecovery/SafeNativeIntegrationBase.t.sol"; @@ -14,10 +13,12 @@ contract SafeEmailRecoveryModule_configureSafeRecovery_Test is SafeNativeIntegra function test_ConfigureSafeRecovery_RevertWhen_ModuleNotInstalled() public { skipIfNotSafeAccountType(); - vm.startPrank(safeAddress); + vm.startPrank(accountAddress1); safe.disableModule(address(1), address(emailRecoveryModule)); vm.expectRevert( - abi.encodeWithSelector(SafeEmailRecoveryModule.ModuleNotInstalled.selector, safeAddress) + abi.encodeWithSelector( + SafeEmailRecoveryModule.ModuleNotInstalled.selector, accountAddress1 + ) ); emailRecoveryModule.configureSafeRecovery( guardians1, guardianWeights, threshold, delay, expiry diff --git a/test/unit/modules/SafeEmailRecoveryModule/constructor.t.sol b/test/unit/modules/SafeEmailRecoveryModule/constructor.t.sol new file mode 100644 index 00000000..2f4c6982 --- /dev/null +++ b/test/unit/modules/SafeEmailRecoveryModule/constructor.t.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { SafeNativeIntegrationBase } from + "../../../integration/SafeRecovery/SafeNativeIntegrationBase.t.sol"; + +import { SafeEmailRecoveryModule } from "src/modules/SafeEmailRecoveryModule.sol"; + +contract SafeEmailRecoveryModule_constructor_Test is SafeNativeIntegrationBase { + function setUp() public override { + super.setUp(); + } + + function test_Constructor() public { + skipIfNotSafeAccountType(); + new SafeEmailRecoveryModule( + address(verifier), + address(dkimRegistry), + address(emailAuthImpl), + commandHandlerAddress, + minimumDelay, + killSwitchAuthorizer + ); + } +} diff --git a/test/unit/modules/SafeEmailRecoveryModule/recover.t.sol b/test/unit/modules/SafeEmailRecoveryModule/recover.t.sol new file mode 100644 index 00000000..949cafe9 --- /dev/null +++ b/test/unit/modules/SafeEmailRecoveryModule/recover.t.sol @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { Safe } from "@safe-global/safe-contracts/contracts/Safe.sol"; +import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; +import { SafeEmailRecoveryModule } from "src/modules/SafeEmailRecoveryModule.sol"; +import { SafeNativeIntegrationBase } from + "../../../integration/SafeRecovery/SafeNativeIntegrationBase.t.sol"; + +contract SafeEmailRecoveryModule_recover_Test is SafeNativeIntegrationBase { + using Strings for uint256; + + function setUp() public override { + super.setUp(); + } + + function test_Recover_RevertWhen_InvalidCalldataSelector() public { + skipIfNotSafeAccountType(); + bytes4 invalidSelector = bytes4(keccak256(bytes("wrongSelector(address,address,address)"))); + bytes memory invalidCalldata = abi.encodeWithSelector( + invalidSelector, accountAddress1, emailRecoveryModuleAddress, newOwner1 + ); + bytes memory invalidData = abi.encode(accountAddress1, invalidCalldata); + + vm.startPrank(emailRecoveryModuleAddress); + vm.expectRevert( + abi.encodeWithSelector( + SafeEmailRecoveryModule.InvalidSelector.selector, invalidSelector + ) + ); + emailRecoveryModule.exposed_recover(accountAddress1, invalidData); + } + + function test_Recover_RevertWhen_InvalidZeroCalldataSelector() public { + skipIfNotSafeAccountType(); + bytes memory invalidChangeOwnerCaldata = bytes("0x"); + bytes memory invalidCalldata = abi.encode(accountAddress1, invalidChangeOwnerCaldata); + + bytes4 expectedSelector; + // solhint-disable-next-line no-inline-assembly + assembly { + expectedSelector := mload(add(invalidChangeOwnerCaldata, 32)) + } + + vm.startPrank(emailRecoveryModuleAddress); + vm.expectRevert( + abi.encodeWithSelector( + SafeEmailRecoveryModule.InvalidSelector.selector, expectedSelector + ) + ); + emailRecoveryModule.exposed_recover(accountAddress1, invalidCalldata); + } + + function test_Recover_RevertWhen_CalldataWithoutValidator() public { + skipIfNotSafeAccountType(); + bytes4 invalidSelector = bytes4(keccak256(bytes("wrongSelector(address,address,address)"))); + bytes memory calldataWithoutValidator = abi.encodeWithSelector( + invalidSelector, accountAddress1, emailRecoveryModuleAddress, newOwner1 + ); + + vm.startPrank(emailRecoveryModuleAddress); + vm.expectRevert(); + emailRecoveryModule.exposed_recover(accountAddress1, calldataWithoutValidator); + } + + function test_Recover_RevertWhen_RecoveryDataWithTruncatedValidatorAddress() public { + skipIfNotSafeAccountType(); + bytes memory validCalldata = abi.encodeWithSelector(functionSelector, newOwner1); + bytes memory invalidData = abi.encode(bytes8(bytes20(accountAddress1)), validCalldata); + + vm.startPrank(emailRecoveryModuleAddress); + vm.expectRevert(); + emailRecoveryModule.exposed_recover(accountAddress1, invalidData); + } + + function test_Recover_RevertWhen_RecoveryFailed() public { + skipIfNotSafeAccountType(); + bytes memory invalidCalldata = abi.encodeWithSelector(functionSelector, accountAddress1); + bytes memory invalidData = abi.encode(accountAddress1, invalidCalldata); + + vm.startPrank(emailRecoveryModuleAddress); + vm.expectRevert( + abi.encodeWithSelector( + SafeEmailRecoveryModule.RecoveryFailed.selector, accountAddress1, bytes("") + ) + ); + emailRecoveryModule.exposed_recover(accountAddress1, invalidData); + } + + function test_Recover_DoesNotRevertWhen_ZeroAddress() public { + skipIfNotSafeAccountType(); + bytes memory dataWithZeroAddress = abi.encode(address(0), recoveryCalldata); + + vm.startPrank(emailRecoveryModuleAddress); + emailRecoveryModule.exposed_recover(accountAddress1, dataWithZeroAddress); + } + + function test_Recover_Succeeds() public { + skipIfNotSafeAccountType(); + vm.startPrank(emailRecoveryModuleAddress); + vm.expectEmit(); + emit SafeEmailRecoveryModule.RecoveryExecuted(accountAddress1); + emailRecoveryModule.exposed_recover(accountAddress1, recoveryData); + + bool isOwner = Safe(payable(accountAddress1)).isOwner(newOwner1); + assertTrue(isOwner); + + bool oldOwnerIsOwner = Safe(payable(accountAddress1)).isOwner(owner1); + assertFalse(oldOwnerIsOwner); + } +} diff --git a/test/unit/modules/SafeEmailRecoveryModule/resetWhenDisabled.t.sol b/test/unit/modules/SafeEmailRecoveryModule/resetWhenDisabled.t.sol new file mode 100644 index 00000000..49af45e1 --- /dev/null +++ b/test/unit/modules/SafeEmailRecoveryModule/resetWhenDisabled.t.sol @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { IEmailRecoveryManager } from "src/interfaces/IEmailRecoveryManager.sol"; +import { GuardianManager } from "src/GuardianManager.sol"; +import { GuardianStorage, GuardianStatus } from "src/libraries/EnumerableGuardianMap.sol"; +import { SafeEmailRecoveryModule } from "src/modules/SafeEmailRecoveryModule.sol"; +import { SafeNativeIntegrationBase } from + "../../../integration/SafeRecovery/SafeNativeIntegrationBase.t.sol"; + +contract SafeEmailRecoveryModule_resetWhenDisabled_Test is SafeNativeIntegrationBase { + function setUp() public override { + super.setUp(); + } + + function test_ResetWhenDisabled_RevertWhen_InvalidAccount() public { + skipIfNotSafeAccountType(); + + vm.expectRevert( + abi.encodeWithSelector(SafeEmailRecoveryModule.InvalidAccount.selector, address(0)) + ); + emailRecoveryModule.resetWhenDisabled(address(0)); + } + + function test_ResetWhenDisabled_RevertWhen_ModuleNotInstalled() public { + skipIfNotSafeAccountType(); + + vm.expectRevert( + abi.encodeWithSelector(SafeEmailRecoveryModule.ResetFailed.selector, accountAddress1) + ); + emailRecoveryModule.resetWhenDisabled(accountAddress1); + } + + function test_ResetWhenDisabled_Succeeds() public { + skipIfNotSafeAccountType(); + + vm.startPrank(accountAddress1); + safe.disableModule(address(1), address(emailRecoveryModule)); + emailRecoveryModule.resetWhenDisabled(accountAddress1); + vm.stopPrank(); + + // assert that recovery config has been cleared successfully + IEmailRecoveryManager.RecoveryConfig memory recoveryConfig = + emailRecoveryModule.getRecoveryConfig(accountAddress1); + assertEq(recoveryConfig.delay, 0); + assertEq(recoveryConfig.expiry, 0); + + // assert that the recovery request has been cleared successfully + ( + uint256 executeAfter, + uint256 executeBefore, + uint256 currentWeight, + bytes32 _recoveryDataHash + ) = emailRecoveryModule.getRecoveryRequest(accountAddress1); + bool hasGuardian1Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[0]); + bool hasGuardian2Voted = + emailRecoveryModule.hasGuardianVoted(accountAddress1, guardians1[1]); + assertEq(executeAfter, 0); + assertEq(executeBefore, 0); + assertEq(currentWeight, 0); + assertEq(_recoveryDataHash, ""); + assertEq(hasGuardian1Voted, false); + assertEq(hasGuardian2Voted, false); + + IEmailRecoveryManager.PreviousRecoveryRequest memory previousRecoveryRequest = + emailRecoveryModule.getPreviousRecoveryRequest(accountAddress1); + assertEq(previousRecoveryRequest.previousGuardianInitiated, address(0)); + assertEq(previousRecoveryRequest.cancelRecoveryCooldown, 0); + + // assert that guardian storage has been cleared successfully for guardian 1 + GuardianStorage memory guardianStorage1 = + emailRecoveryModule.getGuardian(accountAddress1, guardians1[0]); + assertEq(uint256(guardianStorage1.status), uint256(GuardianStatus.NONE)); + assertEq(guardianStorage1.weight, uint256(0)); + + // assert that guardian storage has been cleared successfully for guardian 2 + GuardianStorage memory guardianStorage2 = + emailRecoveryModule.getGuardian(accountAddress1, guardians1[1]); + assertEq(uint256(guardianStorage2.status), uint256(GuardianStatus.NONE)); + assertEq(guardianStorage2.weight, uint256(0)); + + // assert that guardian config has been cleared successfully + GuardianManager.GuardianConfig memory guardianConfig = + emailRecoveryModule.getGuardianConfig(accountAddress1); + assertEq(guardianConfig.guardianCount, 0); + assertEq(guardianConfig.totalWeight, 0); + assertEq(guardianConfig.acceptedWeight, 0); + assertEq(guardianConfig.threshold, 0); + + bool isActivated = emailRecoveryModule.isActivated(accountAddress1); + assertFalse(isActivated); + } +} diff --git a/test/unit/modules/UniversalEmailRecoveryModule/allowValidatorRecovery.t.sol b/test/unit/modules/UniversalEmailRecoveryModule/allowValidatorRecovery.t.sol index 50f3b063..9c20cb5d 100644 --- a/test/unit/modules/UniversalEmailRecoveryModule/allowValidatorRecovery.t.sol +++ b/test/unit/modules/UniversalEmailRecoveryModule/allowValidatorRecovery.t.sol @@ -1,71 +1,68 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; import { ModuleKitHelpers } from "modulekit/ModuleKit.sol"; import { MODULE_TYPE_EXECUTOR, MODULE_TYPE_VALIDATOR } from "modulekit/external/ERC7579.sol"; import { IModule } from "erc7579/interfaces/IERC7579Module.sol"; -import { IERC7579Account } from "erc7579/interfaces/IERC7579Account.sol"; import { ISafe } from "src/interfaces/ISafe.sol"; import { SentinelListLib } from "sentinellist/SentinelList.sol"; import { OwnableValidator } from "src/test/OwnableValidator.sol"; import { UniversalEmailRecoveryModule } from "src/modules/UniversalEmailRecoveryModule.sol"; import { UnitBase } from "../../UnitBase.t.sol"; -import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; contract UniversalEmailRecoveryModule_allowValidatorRecovery_Test is UnitBase { using ModuleKitHelpers for *; - address newValidatorAddress; + address public newValidatorAddress; function setUp() public override { super.setUp(); // Deploy & install new validator to avoid `LinkedList_EntryAlreadyInList` errors OwnableValidator newValidator = new OwnableValidator(); newValidatorAddress = address(newValidator); - instance.installModule({ + instance1.installModule({ moduleTypeId: MODULE_TYPE_VALIDATOR, module: newValidatorAddress, - data: abi.encode(owner) + data: abi.encode(owner1) }); } function test_AllowValidatorRecovery_RevertWhen_RecoveryModuleNotInitialized() public { // Uninstall module so module is not initialized - instance.uninstallModule(MODULE_TYPE_EXECUTOR, recoveryModuleAddress, ""); + instance1.uninstallModule(MODULE_TYPE_EXECUTOR, emailRecoveryModuleAddress, ""); - vm.startPrank(accountAddress); + vm.startPrank(accountAddress1); vm.expectRevert(UniversalEmailRecoveryModule.RecoveryModuleNotInitialized.selector); emailRecoveryModule.allowValidatorRecovery(validatorAddress, bytes("0"), functionSelector); } function test_AllowValidatorRecovery_When_SafeAddOwnerSelector() public { - _skipIfNotSafeAccountType(); - vm.startPrank(accountAddress); + skipIfNotSafeAccountType(); + vm.startPrank(accountAddress1); emailRecoveryModule.allowValidatorRecovery( newValidatorAddress, bytes("0"), ISafe.addOwnerWithThreshold.selector ); } function test_AllowValidatorRecovery_When_SafeRemoveOwnerSelector() public { - _skipIfNotSafeAccountType(); - vm.startPrank(accountAddress); + skipIfNotSafeAccountType(); + vm.startPrank(accountAddress1); emailRecoveryModule.allowValidatorRecovery( newValidatorAddress, bytes("0"), ISafe.removeOwner.selector ); } function test_AllowValidatorRecovery_When_SafeSwapOwnerSelector() public { - _skipIfNotSafeAccountType(); - vm.startPrank(accountAddress); + skipIfNotSafeAccountType(); + vm.startPrank(accountAddress1); emailRecoveryModule.allowValidatorRecovery( newValidatorAddress, bytes("0"), ISafe.swapOwner.selector ); } function test_AllowValidatorRecovery_When_SafeChangeThresholdSelector() public { - _skipIfNotSafeAccountType(); - vm.startPrank(accountAddress); + skipIfNotSafeAccountType(); + vm.startPrank(accountAddress1); emailRecoveryModule.allowValidatorRecovery( newValidatorAddress, bytes("0"), ISafe.changeThreshold.selector ); @@ -77,7 +74,7 @@ contract UniversalEmailRecoveryModule_allowValidatorRecovery_Test is UnitBase { UniversalEmailRecoveryModule.InvalidSelector.selector, IModule.onInstall.selector ) ); - vm.startPrank(accountAddress); + vm.startPrank(accountAddress1); emailRecoveryModule.allowValidatorRecovery( validatorAddress, bytes("0"), IModule.onInstall.selector ); @@ -89,7 +86,7 @@ contract UniversalEmailRecoveryModule_allowValidatorRecovery_Test is UnitBase { UniversalEmailRecoveryModule.InvalidSelector.selector, IModule.onUninstall.selector ) ); - vm.startPrank(accountAddress); + vm.startPrank(accountAddress1); emailRecoveryModule.allowValidatorRecovery( validatorAddress, bytes("0"), IModule.onUninstall.selector ); @@ -99,25 +96,25 @@ contract UniversalEmailRecoveryModule_allowValidatorRecovery_Test is UnitBase { vm.expectRevert( abi.encodeWithSelector(UniversalEmailRecoveryModule.InvalidSelector.selector, bytes4(0)) ); - vm.startPrank(accountAddress); + vm.startPrank(accountAddress1); emailRecoveryModule.allowValidatorRecovery(validatorAddress, bytes("0"), bytes4(0)); } function test_AllowValidatorRecovery_RevertWhen_InvalidValidator() public { - instance.uninstallModule(MODULE_TYPE_VALIDATOR, newValidatorAddress, ""); + instance1.uninstallModule(MODULE_TYPE_VALIDATOR, newValidatorAddress, ""); vm.expectRevert( abi.encodeWithSelector( UniversalEmailRecoveryModule.InvalidValidator.selector, newValidatorAddress ) ); - vm.startPrank(accountAddress); + vm.startPrank(accountAddress1); emailRecoveryModule.allowValidatorRecovery( newValidatorAddress, bytes("0"), functionSelector ); } function test_AllowValidatorRecovery_RevertWhen_ValidatorAlreadyInList() public { - vm.startPrank(accountAddress); + vm.startPrank(accountAddress1); vm.expectRevert( abi.encodeWithSelector( SentinelListLib.LinkedList_EntryAlreadyInList.selector, validatorAddress @@ -129,44 +126,44 @@ contract UniversalEmailRecoveryModule_allowValidatorRecovery_Test is UnitBase { function test_AllowValidatorRecovery_RevertWhen_MaxValidatorsReached() public { // One validator is already installed from setup for (uint256 i = 1; i <= 31; i++) { - address newValidatorAddress = address(new OwnableValidator()); - instance.installModule({ + address _newValidatorAddress = address(new OwnableValidator()); + instance1.installModule({ moduleTypeId: MODULE_TYPE_VALIDATOR, - module: newValidatorAddress, - data: abi.encode(owner) + module: _newValidatorAddress, + data: abi.encode(owner1) }); - vm.startPrank(accountAddress); - emailRecoveryModule.allowValidatorRecovery(newValidatorAddress, "", functionSelector); + vm.startPrank(accountAddress1); + emailRecoveryModule.allowValidatorRecovery(_newValidatorAddress, "", functionSelector); vm.stopPrank(); } - address newValidatorAddress = address(new OwnableValidator()); - instance.installModule({ + address lastValidatorAddress = address(new OwnableValidator()); + instance1.installModule({ moduleTypeId: MODULE_TYPE_VALIDATOR, - module: newValidatorAddress, - data: abi.encode(owner) + module: lastValidatorAddress, + data: abi.encode(owner1) }); - vm.startPrank(accountAddress); + vm.startPrank(accountAddress1); vm.expectRevert(UniversalEmailRecoveryModule.MaxValidatorsReached.selector); - emailRecoveryModule.allowValidatorRecovery(newValidatorAddress, "", functionSelector); + emailRecoveryModule.allowValidatorRecovery(lastValidatorAddress, "", functionSelector); - uint256 validatorCount = emailRecoveryModule.validatorCount(accountAddress); + uint256 validatorCount = emailRecoveryModule.validatorCount(accountAddress1); assertEq(validatorCount, 32); } function test_AllowValidatorRecovery_SucceedsWhenAlreadyInitialized() public { - vm.startPrank(accountAddress); + vm.startPrank(accountAddress1); vm.expectEmit(); emit UniversalEmailRecoveryModule.NewValidatorRecovery({ - account: accountAddress, + account: accountAddress1, validator: newValidatorAddress, recoverySelector: functionSelector }); emailRecoveryModule.allowValidatorRecovery(newValidatorAddress, "", functionSelector); address[] memory allowedValidators = - emailRecoveryModule.getAllowedValidators(accountAddress); - bytes4[] memory allowedSelectors = emailRecoveryModule.getAllowedSelectors(accountAddress); + emailRecoveryModule.getAllowedValidators(accountAddress1); + bytes4[] memory allowedSelectors = emailRecoveryModule.getAllowedSelectors(accountAddress1); assertEq(allowedValidators.length, 2); assertEq(allowedValidators[0], newValidatorAddress); @@ -178,16 +175,16 @@ contract UniversalEmailRecoveryModule_allowValidatorRecovery_Test is UnitBase { function test_AllowValidatorRecovery_SucceedsWhenInitializing() public { // Uninstall module so state is reset - instance.uninstallModule(MODULE_TYPE_EXECUTOR, recoveryModuleAddress, ""); + instance1.uninstallModule(MODULE_TYPE_EXECUTOR, emailRecoveryModuleAddress, ""); - instance.installModule( + instance1.installModule( MODULE_TYPE_EXECUTOR, - recoveryModuleAddress, + emailRecoveryModuleAddress, abi.encode( validatorAddress, isInstalledContext, functionSelector, - guardians, + guardians1, guardianWeights, threshold, delay, @@ -196,21 +193,12 @@ contract UniversalEmailRecoveryModule_allowValidatorRecovery_Test is UnitBase { ); address[] memory allowedValidators = - emailRecoveryModule.getAllowedValidators(accountAddress); - bytes4[] memory allowedSelectors = emailRecoveryModule.getAllowedSelectors(accountAddress); + emailRecoveryModule.getAllowedValidators(accountAddress1); + bytes4[] memory allowedSelectors = emailRecoveryModule.getAllowedSelectors(accountAddress1); assertEq(allowedValidators.length, 1); assertEq(allowedValidators[0], validatorAddress); assertEq(allowedSelectors.length, 1); assertEq(allowedSelectors[0], functionSelector); } - - function _skipIfNotSafeAccountType() private { - string memory currentAccountType = vm.envOr("ACCOUNT_TYPE", string("")); - if (Strings.equal(currentAccountType, "SAFE")) { - vm.skip(false); - } else { - vm.skip(true); - } - } } diff --git a/test/unit/modules/UniversalEmailRecoveryModule/canStartRecoveryRequest.t.sol b/test/unit/modules/UniversalEmailRecoveryModule/canStartRecoveryRequest.t.sol index 3450e30d..47d33ee6 100644 --- a/test/unit/modules/UniversalEmailRecoveryModule/canStartRecoveryRequest.t.sol +++ b/test/unit/modules/UniversalEmailRecoveryModule/canStartRecoveryRequest.t.sol @@ -1,24 +1,42 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; +import { ModuleKitHelpers } from "modulekit/ModuleKit.sol"; +import { MODULE_TYPE_EXECUTOR } from "modulekit/external/ERC7579.sol"; import { UnitBase } from "../../UnitBase.t.sol"; import { IGuardianManager } from "src/interfaces/IGuardianManager.sol"; contract UniversalEmailRecoveryModule_canStartRecoveryRequest_Test is UnitBase { + using ModuleKitHelpers for *; + function setUp() public override { super.setUp(); } - function test_CanStartRecoveryRequest_ReturnsFalse_WhenThresholdCannotBeMet() public { + function test_CanStartRecoveryRequest_ReturnsFalse_WhenThresholdIsZero() public { + instance1.uninstallModule(MODULE_TYPE_EXECUTOR, emailRecoveryModuleAddress, ""); + bool canStartRecoveryRequest = + emailRecoveryModule.canStartRecoveryRequest(accountAddress1, validatorAddress); + + IGuardianManager.GuardianConfig memory guardianConfig = + emailRecoveryModule.getGuardianConfig(accountAddress1); + + // Threshold is zero + assertFalse(canStartRecoveryRequest); + assertFalse( + guardianConfig.threshold > 0 + && guardianConfig.acceptedWeight >= guardianConfig.threshold + ); + } + + function test_CanStartRecoveryRequest_ReturnsFalse_WhenThresholdCannotBeMet() public view { bool canStartRecoveryRequest = - emailRecoveryModule.canStartRecoveryRequest(accountAddress, validatorAddress); + emailRecoveryModule.canStartRecoveryRequest(accountAddress1, validatorAddress); - // Checking accepted weight and sentinel list storage are what we expect for this test case IGuardianManager.GuardianConfig memory guardianConfig = - emailRecoveryModule.getGuardianConfig(accountAddress); + emailRecoveryModule.getGuardianConfig(accountAddress1); bool contains = - emailRecoveryModule.workaround_validatorsContains(accountAddress, validatorAddress); + emailRecoveryModule.workaround_validatorsContains(accountAddress1, validatorAddress); // No guardians have accepted assertFalse(canStartRecoveryRequest); @@ -28,17 +46,16 @@ contract UniversalEmailRecoveryModule_canStartRecoveryRequest_Test is UnitBase { function test_CanStartRecoveryRequest_ReturnsFalse_WhenValidatorNotAdded() public { address invalidValidatorAddress = address(1); - acceptGuardian(accountSalt1); - acceptGuardian(accountSalt2); + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); bool canStartRecoveryRequest = - emailRecoveryModule.canStartRecoveryRequest(accountAddress, invalidValidatorAddress); + emailRecoveryModule.canStartRecoveryRequest(accountAddress1, invalidValidatorAddress); - // Checking accepted weight and sentinel list storage are what we expect for this test case IGuardianManager.GuardianConfig memory guardianConfig = - emailRecoveryModule.getGuardianConfig(accountAddress); + emailRecoveryModule.getGuardianConfig(accountAddress1); bool contains = emailRecoveryModule.workaround_validatorsContains( - accountAddress, invalidValidatorAddress + accountAddress1, invalidValidatorAddress ); // Enough guardians have accepted but invalid guardian address @@ -47,22 +64,21 @@ contract UniversalEmailRecoveryModule_canStartRecoveryRequest_Test is UnitBase { assertFalse(contains); } - function test_CanStartRecoveryRequest_ReturnsTrue_WhenThresholdIsHigherThanWeightAndValidatorAdded( + function test_CanStartRecoveryRequest_ReturnsTrue_WhenWeightIsHigherThanThresholdAndValidatorAdded( ) public { - acceptGuardian(accountSalt1); - acceptGuardian(accountSalt2); - acceptGuardian(accountSalt3); + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[2], emailRecoveryModuleAddress); bool canStartRecoveryRequest = - emailRecoveryModule.canStartRecoveryRequest(accountAddress, validatorAddress); + emailRecoveryModule.canStartRecoveryRequest(accountAddress1, validatorAddress); - // Checking accepted weight and sentinel list storage are what we expect for this test case IGuardianManager.GuardianConfig memory guardianConfig = - emailRecoveryModule.getGuardianConfig(accountAddress); + emailRecoveryModule.getGuardianConfig(accountAddress1); bool contains = - emailRecoveryModule.workaround_validatorsContains(accountAddress, validatorAddress); + emailRecoveryModule.workaround_validatorsContains(accountAddress1, validatorAddress); // Enough guardians have accepted so that accepted weight is higher than the threshold assertTrue(canStartRecoveryRequest); @@ -74,17 +90,16 @@ contract UniversalEmailRecoveryModule_canStartRecoveryRequest_Test is UnitBase { ) public { - acceptGuardian(accountSalt1); - acceptGuardian(accountSalt2); + acceptGuardian(accountAddress1, guardians1[0], emailRecoveryModuleAddress); + acceptGuardian(accountAddress1, guardians1[1], emailRecoveryModuleAddress); bool canStartRecoveryRequest = - emailRecoveryModule.canStartRecoveryRequest(accountAddress, validatorAddress); + emailRecoveryModule.canStartRecoveryRequest(accountAddress1, validatorAddress); - // Checking accepted weight and sentinel list storage are what we expect for this test case IGuardianManager.GuardianConfig memory guardianConfig = - emailRecoveryModule.getGuardianConfig(accountAddress); + emailRecoveryModule.getGuardianConfig(accountAddress1); bool contains = - emailRecoveryModule.workaround_validatorsContains(accountAddress, validatorAddress); + emailRecoveryModule.workaround_validatorsContains(accountAddress1, validatorAddress); // Enough guardians have accepted so that accepted weight is equal to the threshold assertTrue(canStartRecoveryRequest); diff --git a/test/unit/modules/UniversalEmailRecoveryModule/constructor.t.sol b/test/unit/modules/UniversalEmailRecoveryModule/constructor.t.sol index 0f57dcb5..1fab4910 100644 --- a/test/unit/modules/UniversalEmailRecoveryModule/constructor.t.sol +++ b/test/unit/modules/UniversalEmailRecoveryModule/constructor.t.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; import { UnitBase } from "../../UnitBase.t.sol"; import { UniversalEmailRecoveryModule } from "src/modules/UniversalEmailRecoveryModule.sol"; @@ -11,11 +10,13 @@ contract UniversalEmailRecoveryModule_constructor_Test is UnitBase { } function test_Constructor() public { - UniversalEmailRecoveryModule emailRecoveryModule = new UniversalEmailRecoveryModule( + new UniversalEmailRecoveryModule( address(verifier), address(dkimRegistry), address(emailAuthImpl), - address(emailRecoveryHandler) + commandHandlerAddress, + minimumDelay, + killSwitchAuthorizer ); } } diff --git a/test/unit/modules/UniversalEmailRecoveryModule/disallowValidatorRecovery.t.sol b/test/unit/modules/UniversalEmailRecoveryModule/disallowValidatorRecovery.t.sol index 4214dd36..3cd1d277 100644 --- a/test/unit/modules/UniversalEmailRecoveryModule/disallowValidatorRecovery.t.sol +++ b/test/unit/modules/UniversalEmailRecoveryModule/disallowValidatorRecovery.t.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; import { ModuleKitHelpers } from "modulekit/ModuleKit.sol"; import { MODULE_TYPE_VALIDATOR, MODULE_TYPE_EXECUTOR } from "modulekit/external/ERC7579.sol"; import { SentinelListLib } from "sentinellist/SentinelList.sol"; @@ -21,9 +20,9 @@ contract UniversalEmailRecoveryModule_disallowValidatorRecovery_Test is UnitBase function test_DisallowValidatorRecovery_RevertWhen_RecoveryModuleNotInitialized() public { // Uninstall module so module is not initialized - instance.uninstallModule(MODULE_TYPE_EXECUTOR, recoveryModuleAddress, ""); + instance1.uninstallModule(MODULE_TYPE_EXECUTOR, emailRecoveryModuleAddress, ""); - vm.startPrank(accountAddress); + vm.startPrank(accountAddress1); vm.expectRevert(UniversalEmailRecoveryModule.RecoveryModuleNotInitialized.selector); emailRecoveryModule.disallowValidatorRecovery( validatorAddress, address(1), functionSelector @@ -34,7 +33,7 @@ contract UniversalEmailRecoveryModule_disallowValidatorRecovery_Test is UnitBase OwnableValidator newValidator = new OwnableValidator(); address invalidPreviousValidator = address(newValidator); - vm.startPrank(accountAddress); + vm.startPrank(accountAddress1); vm.expectRevert( abi.encodeWithSelector( SentinelListLib.LinkedList_InvalidEntry.selector, validatorAddress @@ -49,17 +48,17 @@ contract UniversalEmailRecoveryModule_disallowValidatorRecovery_Test is UnitBase // Deplopy and install new validator OwnableValidator newValidator = new OwnableValidator(); address newValidatorAddress = address(newValidator); - instance.installModule({ + instance1.installModule({ moduleTypeId: MODULE_TYPE_VALIDATOR, module: newValidatorAddress, - data: abi.encode(owner) + data: abi.encode(owner1) }); address[] memory allowedValidators = - emailRecoveryModule.getAllowedValidators(accountAddress); + emailRecoveryModule.getAllowedValidators(accountAddress1); address prevValidator = allowedValidators.findPrevious(validatorAddress); - vm.startPrank(accountAddress); + vm.startPrank(accountAddress1); vm.expectRevert( abi.encodeWithSelector( SentinelListLib.LinkedList_InvalidEntry.selector, newValidatorAddress @@ -72,12 +71,12 @@ contract UniversalEmailRecoveryModule_disallowValidatorRecovery_Test is UnitBase function test_DisallowValidatorRecovery_RevertsWhen_InvalidSelector() public { address[] memory allowedValidators = - emailRecoveryModule.getAllowedValidators(accountAddress); + emailRecoveryModule.getAllowedValidators(accountAddress1); address prevValidator = allowedValidators.findPrevious(validatorAddress); bytes4 invalidSelector = bytes4(keccak256(bytes("wrongSelector(address,address,address)"))); - vm.startPrank(accountAddress); + vm.startPrank(accountAddress1); vm.expectRevert( abi.encodeWithSelector( UniversalEmailRecoveryModule.InvalidSelector.selector, invalidSelector @@ -87,42 +86,47 @@ contract UniversalEmailRecoveryModule_disallowValidatorRecovery_Test is UnitBase validatorAddress, prevValidator, invalidSelector ); - allowedValidators = emailRecoveryModule.getAllowedValidators(accountAddress); - bytes4[] memory allowedSelectors = emailRecoveryModule.getAllowedSelectors(accountAddress); + allowedValidators = emailRecoveryModule.getAllowedValidators(accountAddress1); + bytes4[] memory allowedSelectors = emailRecoveryModule.getAllowedSelectors(accountAddress1); assertEq(allowedValidators.length, 1); assertEq(allowedSelectors.length, 1); } function test_DisallowValidatorRecovery_Succeeds() public { address[] memory allowedValidators = - emailRecoveryModule.getAllowedValidators(accountAddress); + emailRecoveryModule.getAllowedValidators(accountAddress1); address prevValidator = allowedValidators.findPrevious(validatorAddress); - vm.startPrank(accountAddress); + vm.startPrank(accountAddress1); emailRecoveryModule.disallowValidatorRecovery( validatorAddress, prevValidator, functionSelector ); - allowedValidators = emailRecoveryModule.getAllowedValidators(accountAddress); - bytes4[] memory allowedSelectors = emailRecoveryModule.getAllowedSelectors(accountAddress); + allowedValidators = emailRecoveryModule.getAllowedValidators(accountAddress1); + bytes4[] memory allowedSelectors = emailRecoveryModule.getAllowedSelectors(accountAddress1); assertEq(allowedValidators.length, 0); assertEq(allowedSelectors.length, 0); } function test_DisallowValidatorRecovery_SucceedsWhenValidatorUninstalled() public { - instance.uninstallModule(MODULE_TYPE_VALIDATOR, validatorAddress, ""); + // skip if recovering a 7579 safe as the 7579 safe is the validator + if (isAccountTypeSafe()) { + vm.skip(true); + } + + instance1.uninstallModule(MODULE_TYPE_VALIDATOR, validatorAddress, ""); address[] memory allowedValidators = - emailRecoveryModule.getAllowedValidators(accountAddress); + emailRecoveryModule.getAllowedValidators(accountAddress1); address prevValidator = allowedValidators.findPrevious(validatorAddress); - vm.startPrank(accountAddress); + vm.startPrank(accountAddress1); emailRecoveryModule.disallowValidatorRecovery( validatorAddress, prevValidator, functionSelector ); - allowedValidators = emailRecoveryModule.getAllowedValidators(accountAddress); - bytes4[] memory allowedSelectors = emailRecoveryModule.getAllowedSelectors(accountAddress); + allowedValidators = emailRecoveryModule.getAllowedValidators(accountAddress1); + bytes4[] memory allowedSelectors = emailRecoveryModule.getAllowedSelectors(accountAddress1); assertEq(allowedValidators.length, 0); assertEq(allowedSelectors.length, 0); } @@ -131,23 +135,23 @@ contract UniversalEmailRecoveryModule_disallowValidatorRecovery_Test is UnitBase // Deplopy and install new validator OwnableValidator newValidator = new OwnableValidator(); address newValidatorAddress = address(newValidator); - instance.installModule({ + instance1.installModule({ moduleTypeId: MODULE_TYPE_VALIDATOR, module: newValidatorAddress, - data: abi.encode(owner) + data: abi.encode(owner1) }); - vm.startPrank(accountAddress); + vm.startPrank(accountAddress1); emailRecoveryModule.allowValidatorRecovery(newValidatorAddress, "", functionSelector); address[] memory allowedValidators = - emailRecoveryModule.getAllowedValidators(accountAddress); + emailRecoveryModule.getAllowedValidators(accountAddress1); address prevValidator = allowedValidators.findPrevious(validatorAddress); - vm.startPrank(accountAddress); + vm.startPrank(accountAddress1); vm.expectEmit(); emit UniversalEmailRecoveryModule.RemovedValidatorRecovery({ - account: accountAddress, + account: accountAddress1, validator: validatorAddress, recoverySelector: functionSelector }); @@ -155,8 +159,8 @@ contract UniversalEmailRecoveryModule_disallowValidatorRecovery_Test is UnitBase validatorAddress, prevValidator, functionSelector ); - allowedValidators = emailRecoveryModule.getAllowedValidators(accountAddress); - bytes4[] memory allowedSelectors = emailRecoveryModule.getAllowedSelectors(accountAddress); + allowedValidators = emailRecoveryModule.getAllowedValidators(accountAddress1); + bytes4[] memory allowedSelectors = emailRecoveryModule.getAllowedSelectors(accountAddress1); assertEq(allowedValidators.length, 1); assertEq(allowedValidators[0], newValidatorAddress); assertEq(allowedSelectors.length, 1); diff --git a/test/unit/modules/UniversalEmailRecoveryModule/getAllowedSelectors.t.sol b/test/unit/modules/UniversalEmailRecoveryModule/getAllowedSelectors.t.sol index 2922fc02..8a909d6f 100644 --- a/test/unit/modules/UniversalEmailRecoveryModule/getAllowedSelectors.t.sol +++ b/test/unit/modules/UniversalEmailRecoveryModule/getAllowedSelectors.t.sol @@ -1,9 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; import { ModuleKitHelpers } from "modulekit/ModuleKit.sol"; -import { MODULE_TYPE_EXECUTOR, MODULE_TYPE_VALIDATOR } from "modulekit/external/ERC7579.sol"; +import { MODULE_TYPE_VALIDATOR } from "modulekit/external/ERC7579.sol"; import { OwnableValidator } from "src/test/OwnableValidator.sol"; import { UnitBase } from "../../UnitBase.t.sol"; @@ -15,7 +14,7 @@ contract UniversalEmailRecoveryModule_getAllowedSelectors_Test is UnitBase { } function test_GetAllowedSelectors_Succeeds() public view { - bytes4[] memory allowedSelectors = emailRecoveryModule.getAllowedSelectors(accountAddress); + bytes4[] memory allowedSelectors = emailRecoveryModule.getAllowedSelectors(accountAddress1); assertEq(allowedSelectors.length, 1); assertEq(allowedSelectors[0], functionSelector); @@ -25,18 +24,18 @@ contract UniversalEmailRecoveryModule_getAllowedSelectors_Test is UnitBase { // Deplopy and install new validator OwnableValidator newValidator = new OwnableValidator(); address newValidatorAddress = address(newValidator); - instance.installModule({ + instance1.installModule({ moduleTypeId: MODULE_TYPE_VALIDATOR, module: newValidatorAddress, - data: abi.encode(owner) + data: abi.encode(owner1) }); bytes4 newFunctionSelector = bytes4(keccak256(bytes("rotateOwner(address,address)"))); - vm.startPrank(accountAddress); + vm.startPrank(accountAddress1); emailRecoveryModule.allowValidatorRecovery(newValidatorAddress, "", newFunctionSelector); vm.stopPrank(); - bytes4[] memory allowedSelectors = emailRecoveryModule.getAllowedSelectors(accountAddress); + bytes4[] memory allowedSelectors = emailRecoveryModule.getAllowedSelectors(accountAddress1); assertEq(allowedSelectors.length, 2); assertEq(allowedSelectors[0], newFunctionSelector); assertEq(allowedSelectors[1], functionSelector); diff --git a/test/unit/modules/UniversalEmailRecoveryModule/getAllowedValidators.t.sol b/test/unit/modules/UniversalEmailRecoveryModule/getAllowedValidators.t.sol index de96c282..8e9cd5dd 100644 --- a/test/unit/modules/UniversalEmailRecoveryModule/getAllowedValidators.t.sol +++ b/test/unit/modules/UniversalEmailRecoveryModule/getAllowedValidators.t.sol @@ -1,9 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; import { ModuleKitHelpers } from "modulekit/ModuleKit.sol"; -import { MODULE_TYPE_EXECUTOR, MODULE_TYPE_VALIDATOR } from "modulekit/external/ERC7579.sol"; +import { MODULE_TYPE_VALIDATOR } from "modulekit/external/ERC7579.sol"; import { SentinelListHelper } from "sentinellist/SentinelListHelper.sol"; import { OwnableValidator } from "src/test/OwnableValidator.sol"; import { UnitBase } from "../../UnitBase.t.sol"; @@ -18,23 +17,23 @@ contract UniversalEmailRecoveryModule_getAllowedValidators_Test is UnitBase { function test_GetAllowedValidators_SucceedsWhenNoValidators() public { address[] memory allowedValidators = - emailRecoveryModule.getAllowedValidators(accountAddress); + emailRecoveryModule.getAllowedValidators(accountAddress1); address prevValidator = allowedValidators.findPrevious(validatorAddress); - vm.startPrank(accountAddress); + vm.startPrank(accountAddress1); emailRecoveryModule.disallowValidatorRecovery( validatorAddress, prevValidator, functionSelector ); vm.stopPrank(); - allowedValidators = emailRecoveryModule.getAllowedValidators(accountAddress); + allowedValidators = emailRecoveryModule.getAllowedValidators(accountAddress1); assertEq(allowedValidators.length, 0); } function test_GetAllowedValidators_SucceedsWithOneValidator() public view { address[] memory allowedValidators = - emailRecoveryModule.getAllowedValidators(accountAddress); + emailRecoveryModule.getAllowedValidators(accountAddress1); assertEq(allowedValidators.length, 1); assertEq(allowedValidators[0], validatorAddress); @@ -44,19 +43,19 @@ contract UniversalEmailRecoveryModule_getAllowedValidators_Test is UnitBase { // Deplopy and install new validator OwnableValidator newValidator = new OwnableValidator(); address newValidatorAddress = address(newValidator); - instance.installModule({ + instance1.installModule({ moduleTypeId: MODULE_TYPE_VALIDATOR, module: newValidatorAddress, - data: abi.encode(owner) + data: abi.encode(owner1) }); bytes4 newFunctionSelector = bytes4(keccak256(bytes("rotateOwner(address,address)"))); - vm.startPrank(accountAddress); + vm.startPrank(accountAddress1); emailRecoveryModule.allowValidatorRecovery(newValidatorAddress, "", newFunctionSelector); vm.stopPrank(); address[] memory allowedValidators = - emailRecoveryModule.getAllowedValidators(accountAddress); + emailRecoveryModule.getAllowedValidators(accountAddress1); assertEq(allowedValidators.length, 2); assertEq(allowedValidators[0], newValidatorAddress); assertEq(allowedValidators[1], validatorAddress); diff --git a/test/unit/modules/UniversalEmailRecoveryModule/isInitialized.t.sol b/test/unit/modules/UniversalEmailRecoveryModule/isInitialized.t.sol index 4ed775c8..e08386ba 100644 --- a/test/unit/modules/UniversalEmailRecoveryModule/isInitialized.t.sol +++ b/test/unit/modules/UniversalEmailRecoveryModule/isInitialized.t.sol @@ -1,14 +1,47 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; +import { ModuleKitHelpers } from "modulekit/ModuleKit.sol"; import { UnitBase } from "../../UnitBase.t.sol"; +import { MODULE_TYPE_EXECUTOR } from "modulekit/external/ERC7579.sol"; contract UniversalEmailRecoveryModule_isInitialized_Test is UnitBase { + using ModuleKitHelpers for *; + function setUp() public override { super.setUp(); } - function test_IsInitialized_ReturnsTrueWhenInitialized() public view { } - function test_IsInitialized_ReturnsFalseWhenUninitialized() public view { } + function test_IsInitialized_ReturnsTrueWhenInitialized() public { + instance1.uninstallModule(MODULE_TYPE_EXECUTOR, emailRecoveryModuleAddress, ""); + + // Install and initialize the module + instance1.installModule({ + moduleTypeId: MODULE_TYPE_EXECUTOR, + module: emailRecoveryModuleAddress, + data: abi.encode( + validatorAddress, + isInstalledContext, + functionSelector, + guardians1, + guardianWeights, + threshold, + delay, + expiry + ) + }); + + // Verify that the module is initialized + bool isInitialized = emailRecoveryModule.isInitialized(accountAddress1); + assertTrue(isInitialized); + } + + function test_IsInitialized_ReturnsFalseWhenUninitialized() public { + // Uninstall the module to make it uninitialized + instance1.uninstallModule(MODULE_TYPE_EXECUTOR, emailRecoveryModuleAddress, ""); + + // Verify that the module is uninitialized + bool isInitialized = emailRecoveryModule.isInitialized(accountAddress1); + assertFalse(isInitialized, "Module should not be initialized after uninstallation"); + } } diff --git a/test/unit/modules/UniversalEmailRecoveryModule/isModuleType.t.sol b/test/unit/modules/UniversalEmailRecoveryModule/isModuleType.t.sol index aceb2aec..bd435c75 100644 --- a/test/unit/modules/UniversalEmailRecoveryModule/isModuleType.t.sol +++ b/test/unit/modules/UniversalEmailRecoveryModule/isModuleType.t.sol @@ -1,13 +1,20 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; import { UnitBase } from "../../UnitBase.t.sol"; +import { MODULE_TYPE_EXECUTOR, MODULE_TYPE_VALIDATOR } from "modulekit/external/ERC7579.sol"; contract UniversalEmailRecoveryModule_isModuleType_Test is UnitBase { function setUp() public override { super.setUp(); } - function test_IsModuleType_ReturnsModuleType() public view { } + function test_IsModuleType_ReturnsModuleType() public { + // Verify that the module type is correct + bool isExecutor = emailRecoveryModule.isModuleType(MODULE_TYPE_EXECUTOR); + assertTrue(isExecutor, "Should be an executor module"); + + bool isValidator = emailRecoveryModule.isModuleType(MODULE_TYPE_VALIDATOR); + assertFalse(isValidator, "Should not be a validator module"); + } } diff --git a/test/unit/modules/UniversalEmailRecoveryModule/name.t.sol b/test/unit/modules/UniversalEmailRecoveryModule/name.t.sol index 4d4f46cc..8cd8e676 100644 --- a/test/unit/modules/UniversalEmailRecoveryModule/name.t.sol +++ b/test/unit/modules/UniversalEmailRecoveryModule/name.t.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; import { UnitBase } from "../../UnitBase.t.sol"; contract UniversalEmailRecoveryModule_name_Test is UnitBase { @@ -9,5 +8,9 @@ contract UniversalEmailRecoveryModule_name_Test is UnitBase { super.setUp(); } - function test_Name_ReturnsName() public view { } + function test_Name_ReturnsName() public { + string memory expectedName = "ZKEmail.UniversalEmailRecoveryModule"; + string memory actualName = emailRecoveryModule.name(); + assertEq(actualName, expectedName, "Module name should match expected value"); + } } diff --git a/test/unit/modules/UniversalEmailRecoveryModule/onInstall.t.sol b/test/unit/modules/UniversalEmailRecoveryModule/onInstall.t.sol index 32b333de..0cb49c39 100644 --- a/test/unit/modules/UniversalEmailRecoveryModule/onInstall.t.sol +++ b/test/unit/modules/UniversalEmailRecoveryModule/onInstall.t.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; import { ModuleKitHelpers } from "modulekit/ModuleKit.sol"; import { MODULE_TYPE_EXECUTOR } from "modulekit/external/ERC7579.sol"; import { UniversalEmailRecoveryModule } from "src/modules/UniversalEmailRecoveryModule.sol"; @@ -16,27 +15,27 @@ contract UniversalEmailRecoveryModule_onInstall_Test is UnitBase { } function test_OnInstall_RevertWhen_InvalidOnInstallData() public { - instance.uninstallModule(MODULE_TYPE_EXECUTOR, recoveryModuleAddress, ""); + instance1.uninstallModule(MODULE_TYPE_EXECUTOR, emailRecoveryModuleAddress, ""); bytes memory emptyData = new bytes(0); assertEq(emptyData.length, 0); - vm.startPrank(accountAddress); + vm.startPrank(accountAddress1); vm.expectRevert(UniversalEmailRecoveryModule.InvalidOnInstallData.selector); emailRecoveryModule.onInstall(emptyData); } function test_OnInstall_Succeeds() public { - instance.uninstallModule(MODULE_TYPE_EXECUTOR, recoveryModuleAddress, ""); + instance1.uninstallModule(MODULE_TYPE_EXECUTOR, emailRecoveryModuleAddress, ""); - instance.installModule({ + instance1.installModule({ moduleTypeId: MODULE_TYPE_EXECUTOR, - module: recoveryModuleAddress, + module: emailRecoveryModuleAddress, data: abi.encode( validatorAddress, isInstalledContext, functionSelector, - guardians, + guardians1, guardianWeights, threshold, delay, @@ -45,17 +44,17 @@ contract UniversalEmailRecoveryModule_onInstall_Test is UnitBase { }); bytes4 allowedSelector = - emailRecoveryModule.exposed_allowedSelectors(validatorAddress, accountAddress); + emailRecoveryModule.exposed_allowedSelectors(validatorAddress, accountAddress1); assertEq(allowedSelector, functionSelector); address[] memory allowedValidators = - emailRecoveryModule.getAllowedValidators(accountAddress); - bytes4[] memory allowedSelectors = emailRecoveryModule.getAllowedSelectors(accountAddress); + emailRecoveryModule.getAllowedValidators(accountAddress1); + bytes4[] memory allowedSelectors = emailRecoveryModule.getAllowedSelectors(accountAddress1); assertEq(allowedValidators.length, 1); assertEq(allowedSelectors.length, 1); - bool isActivated = emailRecoveryModule.isActivated(accountAddress); + bool isActivated = emailRecoveryModule.isActivated(accountAddress1); assertTrue(isActivated); } } diff --git a/test/unit/modules/UniversalEmailRecoveryModule/onUninstall.t.sol b/test/unit/modules/UniversalEmailRecoveryModule/onUninstall.t.sol index ad8b7621..d0a5d720 100644 --- a/test/unit/modules/UniversalEmailRecoveryModule/onUninstall.t.sol +++ b/test/unit/modules/UniversalEmailRecoveryModule/onUninstall.t.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; import { ModuleKitHelpers } from "modulekit/ModuleKit.sol"; import { MODULE_TYPE_EXECUTOR } from "modulekit/external/ERC7579.sol"; import { SentinelListHelper } from "sentinellist/SentinelListHelper.sol"; @@ -16,47 +15,43 @@ contract UniversalEmailRecoveryModule_onUninstall_Test is UnitBase { } function test_OnUninstall_Succeeds() public { - vm.prank(accountAddress); - instance.uninstallModule(MODULE_TYPE_EXECUTOR, recoveryModuleAddress, ""); - vm.stopPrank(); + instance1.uninstallModule(MODULE_TYPE_EXECUTOR, emailRecoveryModuleAddress, ""); bytes4 allowedSelector = - emailRecoveryModule.exposed_allowedSelectors(validatorAddress, accountAddress); + emailRecoveryModule.exposed_allowedSelectors(validatorAddress, accountAddress1); assertEq(allowedSelector, bytes4(0)); address[] memory allowedValidators = - emailRecoveryModule.getAllowedValidators(accountAddress); - bytes4[] memory allowedSelectors = emailRecoveryModule.getAllowedSelectors(accountAddress); + emailRecoveryModule.getAllowedValidators(accountAddress1); + bytes4[] memory allowedSelectors = emailRecoveryModule.getAllowedSelectors(accountAddress1); assertEq(allowedValidators.length, 0); assertEq(allowedSelectors.length, 0); } function test_OnUninstall_SucceedsWhenNoValidatorsConfigured() public { address[] memory allowedValidators = - emailRecoveryModule.getAllowedValidators(accountAddress); + emailRecoveryModule.getAllowedValidators(accountAddress1); address prevValidator = allowedValidators.findPrevious(validatorAddress); - vm.startPrank(accountAddress); + vm.startPrank(accountAddress1); emailRecoveryModule.disallowValidatorRecovery( validatorAddress, prevValidator, functionSelector ); vm.stopPrank(); - allowedValidators = emailRecoveryModule.getAllowedValidators(accountAddress); - bytes4[] memory allowedSelectors = emailRecoveryModule.getAllowedSelectors(accountAddress); + allowedValidators = emailRecoveryModule.getAllowedValidators(accountAddress1); + bytes4[] memory allowedSelectors = emailRecoveryModule.getAllowedSelectors(accountAddress1); assertEq(allowedValidators.length, 0); assertEq(allowedSelectors.length, 0); - vm.prank(accountAddress); - instance.uninstallModule(MODULE_TYPE_EXECUTOR, recoveryModuleAddress, ""); - vm.stopPrank(); + instance1.uninstallModule(MODULE_TYPE_EXECUTOR, emailRecoveryModuleAddress, ""); - allowedValidators = emailRecoveryModule.getAllowedValidators(accountAddress); - allowedSelectors = emailRecoveryModule.getAllowedSelectors(accountAddress); + allowedValidators = emailRecoveryModule.getAllowedValidators(accountAddress1); + allowedSelectors = emailRecoveryModule.getAllowedSelectors(accountAddress1); assertEq(allowedValidators.length, 0); assertEq(allowedSelectors.length, 0); - bool isActivated = emailRecoveryModule.isActivated(accountAddress); + bool isActivated = emailRecoveryModule.isActivated(accountAddress1); assertFalse(isActivated); } } diff --git a/test/unit/modules/UniversalEmailRecoveryModule/recover.t.sol b/test/unit/modules/UniversalEmailRecoveryModule/recover.t.sol index f31541d5..af7d0dfc 100644 --- a/test/unit/modules/UniversalEmailRecoveryModule/recover.t.sol +++ b/test/unit/modules/UniversalEmailRecoveryModule/recover.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; -import { ISafe7579 } from "safe7579/Safe7579.sol"; +import { Safe } from "@safe-global/safe-contracts/contracts/Safe.sol"; import { UniversalEmailRecoveryModule } from "src/modules/UniversalEmailRecoveryModule.sol"; import { UnitBase } from "../../UnitBase.t.sol"; @@ -14,7 +13,7 @@ contract UniversalEmailRecoveryModule_recover_Test is UnitBase { function test_Recover_RevertWhen_InvalidAccount() public { address invalidAccount = address(1); - vm.startPrank(recoveryModuleAddress); + vm.startPrank(emailRecoveryModuleAddress); vm.expectRevert( abi.encodeWithSelector( UniversalEmailRecoveryModule.InvalidSelector.selector, functionSelector @@ -25,90 +24,99 @@ contract UniversalEmailRecoveryModule_recover_Test is UnitBase { function test_Recover_RevertWhen_InvalidCalldataSelector() public { bytes4 invalidSelector = bytes4(keccak256(bytes("wrongSelector(address,address,address)"))); - bytes memory changeOwnerCalldata = - abi.encodeWithSelector(invalidSelector, accountAddress, recoveryModuleAddress, newOwner); - bytes memory invalidCalldata = abi.encode(accountAddress, changeOwnerCalldata); + bytes memory changeOwnerCalldata = abi.encodeWithSelector( + invalidSelector, accountAddress1, emailRecoveryModuleAddress, newOwner1 + ); + bytes memory invalidCalldata = abi.encode(accountAddress1, changeOwnerCalldata); - vm.startPrank(recoveryModuleAddress); + vm.startPrank(emailRecoveryModuleAddress); vm.expectRevert( abi.encodeWithSelector( UniversalEmailRecoveryModule.InvalidSelector.selector, invalidSelector ) ); - emailRecoveryModule.exposed_recover(accountAddress, invalidCalldata); + emailRecoveryModule.exposed_recover(accountAddress1, invalidCalldata); } function test_Recover_RevertWhen_InvalidZeroCalldataSelector() public { bytes memory invalidChangeOwnerCaldata = bytes("0x"); - bytes memory invalidCalldata = abi.encode(accountAddress, invalidChangeOwnerCaldata); + bytes memory invalidCalldata = abi.encode(accountAddress1, invalidChangeOwnerCaldata); bytes4 expectedSelector; + // solhint-disable-next-line no-inline-assembly assembly { expectedSelector := mload(add(invalidChangeOwnerCaldata, 32)) } - vm.startPrank(recoveryModuleAddress); + vm.startPrank(emailRecoveryModuleAddress); vm.expectRevert( abi.encodeWithSelector( UniversalEmailRecoveryModule.InvalidSelector.selector, expectedSelector ) ); - emailRecoveryModule.exposed_recover(accountAddress, invalidCalldata); + emailRecoveryModule.exposed_recover(accountAddress1, invalidCalldata); } function test_Recover_RevertWhen_CalldataWithoutValidator() public { bytes4 invalidSelector = bytes4(keccak256(bytes("wrongSelector(address,address,address)"))); - bytes memory calldataWithoutValidator = - abi.encodeWithSelector(invalidSelector, accountAddress, recoveryModuleAddress, newOwner); + bytes memory calldataWithoutValidator = abi.encodeWithSelector( + invalidSelector, accountAddress1, emailRecoveryModuleAddress, newOwner1 + ); - vm.startPrank(recoveryModuleAddress); + vm.startPrank(emailRecoveryModuleAddress); vm.expectRevert(); - emailRecoveryModule.exposed_recover(accountAddress, calldataWithoutValidator); + emailRecoveryModule.exposed_recover(accountAddress1, calldataWithoutValidator); } function test_Recover_RevertWhen_RecoveryDataWithTruncatedValidatorAddress() public { - bytes memory validCalldata = abi.encodeWithSelector(functionSelector, newOwner); - bytes memory invalidData = abi.encode(bytes8(bytes20(accountAddress)), validCalldata); + bytes memory validCalldata = abi.encodeWithSelector(functionSelector, newOwner1); + bytes memory invalidData = abi.encode(bytes8(bytes20(accountAddress1)), validCalldata); - vm.startPrank(recoveryModuleAddress); + vm.startPrank(emailRecoveryModuleAddress); vm.expectRevert(); - emailRecoveryModule.exposed_recover(accountAddress, invalidData); + emailRecoveryModule.exposed_recover(accountAddress1, invalidData); } function test_Recover_RevertWhen_ZeroValidatorAddress() public { address zeroValidator = address(0); - bytes memory validCalldata = abi.encodeWithSelector(functionSelector, newOwner); + bytes memory validCalldata = abi.encodeWithSelector(functionSelector, newOwner1); bytes memory invalidData = abi.encode(zeroValidator, validCalldata); - vm.startPrank(recoveryModuleAddress); + vm.startPrank(emailRecoveryModuleAddress); vm.expectRevert( abi.encodeWithSelector( UniversalEmailRecoveryModule.InvalidValidator.selector, zeroValidator ) ); - emailRecoveryModule.exposed_recover(accountAddress, invalidData); + emailRecoveryModule.exposed_recover(accountAddress1, invalidData); } function test_Recover_RevertWhen_IncorrectValidatorAddress() public { - address wrongValidator = accountAddress; - bytes memory validCalldata = abi.encodeWithSelector(functionSelector, newOwner); - bytes memory invalidData = abi.encode(accountAddress, validCalldata); - vm.startPrank(recoveryModuleAddress); + address wrongValidator = address(1); + bytes memory validCalldata = abi.encodeWithSelector(functionSelector, newOwner1); + bytes memory invalidData = abi.encode(wrongValidator, validCalldata); + vm.startPrank(emailRecoveryModuleAddress); vm.expectRevert( abi.encodeWithSelector( UniversalEmailRecoveryModule.InvalidSelector.selector, functionSelector ) ); - emailRecoveryModule.exposed_recover(accountAddress, invalidData); + emailRecoveryModule.exposed_recover(accountAddress1, invalidData); } function test_Recover_Succeeds() public { - vm.startPrank(recoveryModuleAddress); + vm.startPrank(emailRecoveryModuleAddress); vm.expectEmit(); - emit UniversalEmailRecoveryModule.RecoveryExecuted(accountAddress, validatorAddress); - emailRecoveryModule.exposed_recover(accountAddress, recoveryData); - - address updatedOwner = validator.owners(accountAddress); - assertEq(updatedOwner, newOwner); + emit UniversalEmailRecoveryModule.RecoveryExecuted(accountAddress1, validatorAddress); + emailRecoveryModule.exposed_recover(accountAddress1, recoveryData); + + address updatedOwner; + if (isAccountTypeSafe()) { + bool isOwner = Safe(payable(accountAddress1)).isOwner(newOwner1); + assertTrue(isOwner); + } else { + updatedOwner = validator.owners(accountAddress1); + assertEq(updatedOwner, newOwner1); + } } } diff --git a/test/unit/modules/UniversalEmailRecoveryModule/version.t.sol b/test/unit/modules/UniversalEmailRecoveryModule/version.t.sol index 75130062..65e78d5b 100644 --- a/test/unit/modules/UniversalEmailRecoveryModule/version.t.sol +++ b/test/unit/modules/UniversalEmailRecoveryModule/version.t.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { console2 } from "forge-std/console2.sol"; import { UnitBase } from "../../UnitBase.t.sol"; contract UniversalEmailRecoveryModule_version_Test is UnitBase { @@ -9,5 +8,9 @@ contract UniversalEmailRecoveryModule_version_Test is UnitBase { super.setUp(); } - function test_Version_ReturnsVersion() public view { } + function test_Version_ReturnsVersion() public { + string memory expectedVersion = "1.0.0"; + string memory actualVersion = emailRecoveryModule.version(); + assertEq(actualVersion, expectedVersion, "Module version should match expected value"); + } }