From b1485e44fe62f9d9ecc9895f757ed00cff8efb7b Mon Sep 17 00:00:00 2001 From: JohnGuilding Date: Thu, 1 Aug 2024 22:18:00 +0100 Subject: [PATCH] Add additional integration tests --- .../EmailRecoveryManager.integration.t.sol | 36 +- .../EmailRecoveryModule.t.sol | 291 +++++++++----- .../EmailRecoveryModuleBase.t.sol | 70 +++- .../UniversalEmailRecoveryModule.t.sol | 367 +++++++++++++----- .../UniversalEmailRecoveryModuleBase.t.sol | 71 +++- 5 files changed, 629 insertions(+), 206 deletions(-) diff --git a/test/integration/EmailRecoveryManager/EmailRecoveryManager.integration.t.sol b/test/integration/EmailRecoveryManager/EmailRecoveryManager.integration.t.sol index 78fc4fce..71d08730 100644 --- a/test/integration/EmailRecoveryManager/EmailRecoveryManager.integration.t.sol +++ b/test/integration/EmailRecoveryManager/EmailRecoveryManager.integration.t.sol @@ -150,21 +150,27 @@ contract EmailRecoveryManager_Integration_Test is emailRecoveryModule.handleRecovery(emailAuthMsg, templateIdx); } - // function test_RevertWhen_HandleRecoveryCalled_AfterCompleteRecovery() public { - // acceptGuardian(accountAddress1, guardian1); - // acceptGuardian(accountAddress1, guardian2); - // vm.warp(12 seconds); - // handleRecovery(accountAddress1, guardian1, calldataHash1); - // handleRecovery(accountAddress1, guardian2, calldataHash1); - // vm.warp(block.timestamp + delay); - // emailRecoveryModule.completeRecovery(accountAddress1, recoveryCalldata1); - - // EmailAuthMsg memory emailAuthMsg = - // getRecoveryEmailAuthMessage(accountAddress1, guardian1, calldataHash1); - - // // vm.expectRevert("email nullifier already used"); // FIXME: - // emailRecoveryModule.handleRecovery(emailAuthMsg, templateIdx); - // } + function test_HandleRecoveryCalled_AfterCompleteRecoveryStartsNewRecoveryRequest() public { + acceptGuardian(accountAddress1, guardians1[0]); + acceptGuardian(accountAddress1, guardians1[1]); + acceptGuardian(accountAddress1, guardians1[2]); + vm.warp(12 seconds); + handleRecovery(accountAddress1, guardians1[0], calldataHash1); + handleRecovery(accountAddress1, guardians1[1], calldataHash1); + vm.warp(block.timestamp + delay); + emailRecoveryModule.completeRecovery(accountAddress1, recoveryCalldata1); + + IEmailRecoveryManager.RecoveryRequest memory recoveryRequest = + emailRecoveryModule.getRecoveryRequest(accountAddress1); + assertEq(recoveryRequest.currentWeight, 0); + + EmailAuthMsg memory emailAuthMsg = + getRecoveryEmailAuthMessage(accountAddress1, guardians1[2], calldataHash1); + + emailRecoveryModule.handleRecovery(emailAuthMsg, templateIdx); + recoveryRequest = emailRecoveryModule.getRecoveryRequest(accountAddress1); + assertEq(recoveryRequest.currentWeight, 1); + } function test_RevertWhen_CompleteRecoveryCalled_BeforeConfigureRecovery() public { vm.prank(accountAddress1); diff --git a/test/integration/OwnableValidatorRecovery/EmailRecoveryModule/EmailRecoveryModule.t.sol b/test/integration/OwnableValidatorRecovery/EmailRecoveryModule/EmailRecoveryModule.t.sol index 8a055bf2..c1cd7659 100644 --- a/test/integration/OwnableValidatorRecovery/EmailRecoveryModule/EmailRecoveryModule.t.sol +++ b/test/integration/OwnableValidatorRecovery/EmailRecoveryModule/EmailRecoveryModule.t.sol @@ -5,8 +5,11 @@ 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 { 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"; @@ -15,6 +18,27 @@ import { OwnableValidatorRecovery_EmailRecoveryModule_Base } from "./EmailRecove contract OwnableValidatorRecovery_EmailRecoveryModule_Integration_Test is OwnableValidatorRecovery_EmailRecoveryModule_Base { + using ModuleKitHelpers for *; + using Strings for uint256; + + // Helper function + function executeRecoveryFlowForAccount( + address account, + address[] memory guardians, + bytes32 calldataHash, + bytes memory recoveryCalldata + ) + internal + { + acceptGuardian(account, guardians[0]); + acceptGuardian(account, guardians[1]); + vm.warp(block.timestamp + 12 seconds); + handleRecovery(account, guardians[0], calldataHash); + handleRecovery(account, guardians[1], calldataHash); + vm.warp(block.timestamp + delay); + emailRecoveryModule.completeRecovery(account, recoveryCalldata); + } + function setUp() public override { super.setUp(); } @@ -68,102 +92,183 @@ contract OwnableValidatorRecovery_EmailRecoveryModule_Integration_Test is assertEq(updatedOwner, newOwner1); } - // function test_Recover_CannotMixAccountHandleAcceptance() public { - // acceptGuardian(accountAddress1, guardians1[0]); - // acceptGuardian(accountAddress2, guardians2[1]); - // vm.warp(12 seconds); - // handleRecovery(accountAddress1, guardians1[0], calldataHash1); - - // EmailAuthMsg memory emailAuthMsg = getRecoveryEmailAuthMessage( - // accountAddress1, - // guardians1[1], - // calldataHash1 - // ); - - // vm.expectRevert( - // abi.encodeWithSelector( - // IEmailRecoveryManager.InvalidGuardianStatus.selector, - // GuardianStatus.REQUESTED, - // GuardianStatus.ACCEPTED - // ) - // ); - // emailRecoveryModule.handleRecovery(emailAuthMsg, templateIdx); - // } - - // function test_Recover_CannotMixAccountHandleRecovery() public { - // acceptGuardian(accountAddress1, guardians1[0]); - // acceptGuardian(accountAddress1, guardians1[1]); - // vm.warp(12 seconds); - // handleRecovery(accountAddress1, guardians1[0], calldataHash1); - - // EmailAuthMsg memory emailAuthMsg = getRecoveryEmailAuthMessage( - // accountAddress2, - // guardians1[1], - // calldataHash2 - // ); - - // vm.expectRevert( - // abi.encodeWithSelector( - // IEmailRecoveryManager.InvalidGuardianStatus.selector, - // GuardianStatus.REQUESTED, - // GuardianStatus.ACCEPTED - // ) - // ); - // emailRecoveryModule.handleRecovery(emailAuthMsg, templateIdx); - // } + function test_Recover_RevertWhen_MixAccountHandleAcceptance() public { + acceptGuardian(accountAddress1, guardians1[0]); + acceptGuardianWithAccountSalt(accountAddress2, guardians1[1], accountSalt2); + vm.warp(12 seconds); - // Helper function - function executeRecoveryFlowForAccount( - address account, - bytes32 calldataHash, - bytes memory recoveryCalldata - ) - internal - { - acceptGuardian(account, guardians1[0]); - acceptGuardian(account, guardians1[1]); + EmailAuthMsg memory emailAuthMsg = + getRecoveryEmailAuthMessage(accountAddress1, guardians1[1], calldataHash1); + + vm.expectRevert("guardian is not deployed"); + emailRecoveryModule.handleRecovery(emailAuthMsg, templateIdx); + + emailAuthMsg = getRecoveryEmailAuthMessage(accountAddress1, guardians1[0], calldataHash1); + + vm.expectRevert( + abi.encodeWithSelector( + IEmailRecoveryManager.ThresholdExceedsAcceptedWeight.selector, 3, 1 + ) + ); + emailRecoveryModule.handleRecovery(emailAuthMsg, templateIdx); + } + + function test_Recover_RevertWhen_MixAccountHandleRecovery() public { + acceptGuardianWithAccountSalt(accountAddress2, guardians1[1], accountSalt2); + + acceptGuardian(accountAddress1, guardians1[0]); + acceptGuardian(accountAddress1, guardians1[1]); vm.warp(block.timestamp + 12 seconds); - handleRecovery(account, guardians1[0], calldataHash); - handleRecovery(account, guardians1[1], calldataHash); + handleRecovery(accountAddress1, guardians1[0], calldataHash1); + + EmailAuthMsg memory emailAuthMsg = getRecoveryEmailAuthMessageWithAccountSalt( + accountAddress2, guardians1[1], calldataHash2, accountSalt2 + ); + + vm.expectRevert( + abi.encodeWithSelector( + IEmailRecoveryManager.ThresholdExceedsAcceptedWeight.selector, + uint256(3), + uint256(2) + ) + ); + emailRecoveryModule.handleRecovery(emailAuthMsg, templateIdx); + } + + function test_Recover_RevertWhen_UninstallModuleBeforeAnyGuardiansAccepted() public { + instance1.uninstallModule(MODULE_TYPE_EXECUTOR, recoveryModuleAddress, ""); + + EmailAuthMsg memory emailAuthMsg = + getAcceptanceEmailAuthMessage(accountAddress1, guardians1[0]); + + vm.expectRevert( + abi.encodeWithSelector( + IEmailRecoveryManager.InvalidGuardianStatus.selector, + uint256(GuardianStatus.NONE), + uint256(GuardianStatus.REQUESTED) + ) + ); + emailRecoveryModule.handleAcceptance(emailAuthMsg, templateIdx); + } + + function test_Recover_RevertWhen_UninstallModuleBeforeEnoughAcceptedAndTryHandleAcceptance() + public + { + acceptGuardian(accountAddress1, guardians1[0]); + instance1.uninstallModule(MODULE_TYPE_EXECUTOR, recoveryModuleAddress, ""); + + EmailAuthMsg memory emailAuthMsg = + getAcceptanceEmailAuthMessage(accountAddress1, guardians1[1]); + + vm.expectRevert( + abi.encodeWithSelector( + IEmailRecoveryManager.InvalidGuardianStatus.selector, + uint256(GuardianStatus.NONE), + uint256(GuardianStatus.REQUESTED) + ) + ); + emailRecoveryModule.handleAcceptance(emailAuthMsg, templateIdx); + } + + function test_Recover_RevertWhen_UninstallModuleAfterEnoughAcceptedAndTryHandleRecovery() + public + { + acceptGuardian(accountAddress1, guardians1[0]); + acceptGuardian(accountAddress1, guardians1[1]); + vm.warp(12 seconds); + + EmailAuthMsg memory emailAuthMsg = + getRecoveryEmailAuthMessage(accountAddress1, guardians1[0], calldataHash1); + + instance1.uninstallModule(MODULE_TYPE_EXECUTOR, recoveryModuleAddress, ""); + vm.expectRevert( + abi.encodeWithSelector( + IEmailRecoveryManager.InvalidGuardianStatus.selector, + uint256(GuardianStatus.NONE), + uint256(GuardianStatus.ACCEPTED) + ) + ); + emailRecoveryModule.handleRecovery(emailAuthMsg, templateIdx); + } + + function test_Recover_RevertWhen_UninstallModuleAfterOneApprovalAndTryHandleRecovery() public { + acceptGuardian(accountAddress1, guardians1[0]); + acceptGuardian(accountAddress1, guardians1[1]); + vm.warp(12 seconds); + handleRecovery(accountAddress1, guardians1[0], calldataHash1); + + vm.startPrank(accountAddress1); + vm.expectRevert(IGuardianManager.RecoveryInProcess.selector); + emailRecoveryModule.onUninstall(""); + } + + function test_Recover_RevertWhen_UninstallModuleProcessRecoveryAndTryCompleteRecovery() + public + { + acceptGuardian(accountAddress1, guardians1[0]); + acceptGuardian(accountAddress1, guardians1[1]); + vm.warp(12 seconds); + handleRecovery(accountAddress1, guardians1[0], calldataHash1); + handleRecovery(accountAddress1, guardians1[1], calldataHash1); vm.warp(block.timestamp + delay); - emailRecoveryModule.completeRecovery(account, recoveryCalldata); + + vm.startPrank(accountAddress1); + vm.expectRevert(IGuardianManager.RecoveryInProcess.selector); + emailRecoveryModule.onUninstall(""); } - // function test_Recover_RotatesMultipleOwnersSuccessfully() public { - // executeRecoveryFlowForAccount( - // accountAddress1, - // calldataHash1, - // recoveryCalldata1 - // ); - // vm.warp(block.timestamp + 12 seconds); - // executeRecoveryFlowForAccount( - // accountAddress2, - // calldataHash2, - // recoveryCalldata2 - // ); - // vm.warp(block.timestamp + 12 seconds); - // executeRecoveryFlowForAccount( - // accountAddress3, - // calldataHash3, - // recoveryCalldata3 - // ); - - // 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_Recover_RevertWhen_InvalidTimestamp() public { - // acceptGuardian(accountAddress1, guardians1[0]); - // EmailAuthMsg memory emailAuthMsg = getAcceptanceEmailAuthMessage( - // accountAddress2, - // guardians2[0] - // ); - - // vm.expectRevert("invalid timestamp"); - // emailRecoveryModule.handleAcceptance(emailAuthMsg, templateIdx); - // } + function test_Recover_RevertWhen_UninstallModuleAndTryRecoveryAgain() public { + executeRecoveryFlowForAccount(accountAddress1, guardians1, calldataHash1, recoveryCalldata1); + address updatedOwner1 = validator.owners(accountAddress1); + assertEq(updatedOwner1, newOwner1); + + instance1.uninstallModule(MODULE_TYPE_EXECUTOR, recoveryModuleAddress, ""); + + EmailAuthMsg memory emailAuthMsg = + getAcceptanceEmailAuthMessage(accountAddress1, guardians1[0]); + + vm.expectRevert( + abi.encodeWithSelector( + IEmailRecoveryManager.InvalidGuardianStatus.selector, + uint256(GuardianStatus.NONE), + uint256(GuardianStatus.REQUESTED) + ) + ); + emailRecoveryModule.handleAcceptance(emailAuthMsg, templateIdx); + } + + function test_Recover_UninstallModuleAndRecoverAgain() public { + executeRecoveryFlowForAccount(accountAddress1, guardians1, calldataHash1, recoveryCalldata1); + address updatedOwner = validator.owners(accountAddress1); + assertEq(updatedOwner, newOwner1); + + instance1.uninstallModule(MODULE_TYPE_EXECUTOR, recoveryModuleAddress, ""); + instance1.installModule({ + moduleTypeId: MODULE_TYPE_EXECUTOR, + module: recoveryModuleAddress, + data: abi.encode(isInstalledContext, guardians1, guardianWeights, threshold, delay, expiry) + }); + + bytes memory newRecoveryCalldata = abi.encodeWithSelector(functionSelector, newOwner2); + bytes32 newCalldataHash = keccak256(newRecoveryCalldata); + executeRecoveryFlowForAccount( + accountAddress1, guardians1, newCalldataHash, newRecoveryCalldata + ); + + updatedOwner = validator.owners(accountAddress1); + assertEq(updatedOwner, newOwner2); + } + + function test_Recover_RotatesMultipleOwnersSuccessfully() public { + executeRecoveryFlowForAccount(accountAddress1, guardians1, calldataHash1, recoveryCalldata1); + executeRecoveryFlowForAccount(accountAddress2, guardians2, calldataHash2, recoveryCalldata2); + executeRecoveryFlowForAccount(accountAddress3, guardians3, calldataHash3, recoveryCalldata3); + + address updatedOwner1 = validator.owners(accountAddress1); + address updatedOwner2 = validator.owners(accountAddress2); + address updatedOwner3 = validator.owners(accountAddress3); + assertEq(updatedOwner1, newOwner1); + assertEq(updatedOwner2, newOwner2); + assertEq(updatedOwner3, newOwner3); + } } diff --git a/test/integration/OwnableValidatorRecovery/EmailRecoveryModule/EmailRecoveryModuleBase.t.sol b/test/integration/OwnableValidatorRecovery/EmailRecoveryModule/EmailRecoveryModuleBase.t.sol index c5494410..025abfb6 100644 --- a/test/integration/OwnableValidatorRecovery/EmailRecoveryModule/EmailRecoveryModuleBase.t.sol +++ b/test/integration/OwnableValidatorRecovery/EmailRecoveryModule/EmailRecoveryModuleBase.t.sol @@ -201,17 +201,48 @@ abstract contract OwnableValidatorRecovery_EmailRecoveryModule_Base is Integrati 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)); + } + + // 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 = getAccountSaltForGuardian(account, guardian); + + bytes32 accountSalt; + if (optionalAccountSalt == bytes32(0)) { + accountSalt = getAccountSaltForGuardian(account, guardian); + } else { + accountSalt = optionalAccountSalt; + } EmailProof memory emailProof = generateMockEmailProof(subject, nullifier, accountSalt); @@ -231,6 +262,21 @@ abstract contract OwnableValidatorRecovery_EmailRecoveryModule_Base is Integrati emailRecoveryModule.handleRecovery(emailAuthMsg, templateIdx); } + // WithAccountSalt variation - used for creating incorrect recovery setups + function handleRecoveryWithAccountSalt( + address account, + address guardian, + bytes32 calldataHash, + bytes32 optionalAccountSalt + ) + public + { + EmailAuthMsg memory emailAuthMsg = getRecoveryEmailAuthMessageWithAccountSalt( + account, guardian, calldataHash, optionalAccountSalt + ); + emailRecoveryModule.handleRecovery(emailAuthMsg, templateIdx); + } + function getRecoveryEmailAuthMessage( address account, address guardian, @@ -238,6 +284,20 @@ abstract contract OwnableValidatorRecovery_EmailRecoveryModule_Base is Integrati ) public returns (EmailAuthMsg memory) + { + return + getRecoveryEmailAuthMessageWithAccountSalt(account, guardian, calldataHash, bytes32(0)); + } + + // WithAccountSalt variation - used for creating incorrect recovery setups + function getRecoveryEmailAuthMessageWithAccountSalt( + address account, + address guardian, + bytes32 calldataHash, + bytes32 optionalAccountSalt + ) + public + returns (EmailAuthMsg memory) { string memory accountString = SubjectUtils.addressToChecksumHexString(account); string memory calldataHashString = uint256(calldataHash).toHexString(32); @@ -249,7 +309,13 @@ abstract contract OwnableValidatorRecovery_EmailRecoveryModule_Base is Integrati string memory subject = string.concat(subjectPart1, subjectPart2, subjectPart3); bytes32 nullifier = generateNewNullifier(); - bytes32 accountSalt = getAccountSaltForGuardian(account, guardian); + + bytes32 accountSalt; + if (optionalAccountSalt == bytes32(0)) { + accountSalt = getAccountSaltForGuardian(account, guardian); + } else { + accountSalt = optionalAccountSalt; + } EmailProof memory emailProof = generateMockEmailProof(subject, nullifier, accountSalt); diff --git a/test/integration/OwnableValidatorRecovery/UniversalEmailRecoveryModule/UniversalEmailRecoveryModule.t.sol b/test/integration/OwnableValidatorRecovery/UniversalEmailRecoveryModule/UniversalEmailRecoveryModule.t.sol index 3b28b76e..be34f9e8 100644 --- a/test/integration/OwnableValidatorRecovery/UniversalEmailRecoveryModule/UniversalEmailRecoveryModule.t.sol +++ b/test/integration/OwnableValidatorRecovery/UniversalEmailRecoveryModule/UniversalEmailRecoveryModule.t.sol @@ -7,6 +7,7 @@ import { MODULE_TYPE_EXECUTOR, MODULE_TYPE_VALIDATOR } from "modulekit/external/ import { EmailAuthMsg } from "ether-email-auth/packages/contracts/src/EmailAuth.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"; @@ -16,6 +17,26 @@ import { OwnableValidatorRecovery_UniversalEmailRecoveryModule_Base } from contract OwnableValidatorRecovery_UniversalEmailRecoveryModule_Integration_Test is OwnableValidatorRecovery_UniversalEmailRecoveryModule_Base { + using ModuleKitHelpers for *; + + // Helper function + function executeRecoveryFlowForAccount( + address account, + address[] memory guardians, + bytes32 calldataHash, + bytes memory recoveryCalldata + ) + internal + { + acceptGuardian(account, guardians[0]); + acceptGuardian(account, guardians[1]); + vm.warp(block.timestamp + 12 seconds); + handleRecovery(account, guardians[0], calldataHash); + handleRecovery(account, guardians[1], calldataHash); + vm.warp(block.timestamp + delay); + emailRecoveryModule.completeRecovery(account, recoveryCalldata); + } + function setUp() public override { super.setUp(); } @@ -69,102 +90,262 @@ contract OwnableValidatorRecovery_UniversalEmailRecoveryModule_Integration_Test assertEq(updatedOwner, newOwner1); } - // function test_Recover_CannotMixAccountHandleAcceptance() public { - // acceptGuardian(accountAddress1, guardians1[0]); - // acceptGuardian(accountAddress2, guardians2[1]); - // vm.warp(12 seconds); - // handleRecovery(accountAddress1, guardians1[0], calldataHash1); - - // EmailAuthMsg memory emailAuthMsg = getRecoveryEmailAuthMessage( - // accountAddress1, - // guardians1[1], - // calldataHash1 - // ); - - // vm.expectRevert( - // abi.encodeWithSelector( - // IEmailRecoveryManager.InvalidGuardianStatus.selector, - // GuardianStatus.REQUESTED, - // GuardianStatus.ACCEPTED - // ) - // ); - // emailRecoveryModule.handleRecovery(emailAuthMsg, templateIdx); - // } - - // function test_Recover_CannotMixAccountHandleRecovery() public { - // acceptGuardian(accountAddress1, guardians1[0]); - // acceptGuardian(accountAddress1, guardians1[1]); - // vm.warp(12 seconds); - // handleRecovery(accountAddress1, guardians1[0], calldataHash1); - - // EmailAuthMsg memory emailAuthMsg = getRecoveryEmailAuthMessage( - // accountAddress2, - // guardians1[1], - // calldataHash2 - // ); - - // vm.expectRevert( - // abi.encodeWithSelector( - // IEmailRecoveryManager.InvalidGuardianStatus.selector, - // GuardianStatus.REQUESTED, - // GuardianStatus.ACCEPTED - // ) - // ); - // emailRecoveryModule.handleRecovery(emailAuthMsg, templateIdx); - // } + function test_Recover_RevertWhen_MixAccountHandleAcceptance() public { + acceptGuardian(accountAddress1, guardians1[0]); + acceptGuardianWithAccountSalt(accountAddress2, guardians1[1], accountSalt2); + vm.warp(12 seconds); - // Helper function - function executeRecoveryFlowForAccount( - address account, - bytes32 calldataHash, - bytes memory recoveryCalldata - ) - internal + EmailAuthMsg memory emailAuthMsg = + getRecoveryEmailAuthMessage(accountAddress1, guardians1[1], calldataHash1); + + vm.expectRevert("guardian is not deployed"); + emailRecoveryModule.handleRecovery(emailAuthMsg, templateIdx); + + emailAuthMsg = getRecoveryEmailAuthMessage(accountAddress1, guardians1[0], calldataHash1); + + vm.expectRevert( + abi.encodeWithSelector( + IEmailRecoveryManager.ThresholdExceedsAcceptedWeight.selector, 3, 1 + ) + ); + emailRecoveryModule.handleRecovery(emailAuthMsg, templateIdx); + } + + function test_Recover_RevertWhen_MixAccountHandleRecovery() public { + acceptGuardianWithAccountSalt(accountAddress2, guardians1[1], accountSalt2); + + acceptGuardian(accountAddress1, guardians1[0]); + acceptGuardian(accountAddress1, guardians1[1]); + vm.warp(12 seconds); + handleRecovery(accountAddress1, guardians1[0], calldataHash1); + + EmailAuthMsg memory emailAuthMsg = getRecoveryEmailAuthMessageWithAccountSalt( + accountAddress2, guardians1[1], calldataHash2, accountSalt2 + ); + + vm.expectRevert( + abi.encodeWithSelector( + IEmailRecoveryManager.ThresholdExceedsAcceptedWeight.selector, + uint256(3), + uint256(2) + ) + ); + emailRecoveryModule.handleRecovery(emailAuthMsg, templateIdx); + } + + function test_Recover_RevertWhen_UninstallModuleBeforeAnyGuardiansAccepted() public { + instance1.uninstallModule(MODULE_TYPE_EXECUTOR, recoveryModuleAddress, ""); + + EmailAuthMsg memory emailAuthMsg = + getAcceptanceEmailAuthMessage(accountAddress1, guardians1[0]); + + vm.expectRevert( + abi.encodeWithSelector( + IEmailRecoveryManager.InvalidGuardianStatus.selector, + uint256(GuardianStatus.NONE), + uint256(GuardianStatus.REQUESTED) + ) + ); + emailRecoveryModule.handleAcceptance(emailAuthMsg, templateIdx); + } + + function test_Recover_RevertWhen_UninstallModuleBeforeEnoughAcceptedAndTryHandleAcceptance() + public { - acceptGuardian(account, guardians1[0]); - acceptGuardian(account, guardians1[1]); - vm.warp(block.timestamp + 12 seconds); - handleRecovery(account, guardians1[0], calldataHash); - handleRecovery(account, guardians1[1], calldataHash); + acceptGuardian(accountAddress1, guardians1[0]); + instance1.uninstallModule(MODULE_TYPE_EXECUTOR, recoveryModuleAddress, ""); + + EmailAuthMsg memory emailAuthMsg = + getAcceptanceEmailAuthMessage(accountAddress1, guardians1[1]); + + vm.expectRevert( + abi.encodeWithSelector( + IEmailRecoveryManager.InvalidGuardianStatus.selector, + uint256(GuardianStatus.NONE), + uint256(GuardianStatus.REQUESTED) + ) + ); + emailRecoveryModule.handleAcceptance(emailAuthMsg, templateIdx); + } + + function test_Recover_RevertWhen_UninstallModuleAfterEnoughAcceptedAndTryHandleRecovery() + public + { + acceptGuardian(accountAddress1, guardians1[0]); + acceptGuardian(accountAddress1, guardians1[1]); + vm.warp(12 seconds); + + EmailAuthMsg memory emailAuthMsg = + getRecoveryEmailAuthMessage(accountAddress1, guardians1[0], calldataHash1); + + instance1.uninstallModule(MODULE_TYPE_EXECUTOR, recoveryModuleAddress, ""); + vm.expectRevert( + abi.encodeWithSelector( + IEmailRecoveryManager.InvalidGuardianStatus.selector, + uint256(GuardianStatus.NONE), + uint256(GuardianStatus.ACCEPTED) + ) + ); + emailRecoveryModule.handleRecovery(emailAuthMsg, templateIdx); + } + + function test_Recover_RevertWhen_UninstallModuleAfterOneApprovalAndTryHandleRecovery() public { + acceptGuardian(accountAddress1, guardians1[0]); + acceptGuardian(accountAddress1, guardians1[1]); + vm.warp(12 seconds); + handleRecovery(accountAddress1, guardians1[0], calldataHash1); + + vm.startPrank(accountAddress1); + vm.expectRevert(IGuardianManager.RecoveryInProcess.selector); + emailRecoveryModule.onUninstall(""); + } + + function test_Recover_RevertWhen_UninstallModuleProcessRecoveryAndTryCompleteRecovery() + public + { + acceptGuardian(accountAddress1, guardians1[0]); + acceptGuardian(accountAddress1, guardians1[1]); + vm.warp(12 seconds); + handleRecovery(accountAddress1, guardians1[0], calldataHash1); + handleRecovery(accountAddress1, guardians1[1], calldataHash1); vm.warp(block.timestamp + delay); - emailRecoveryModule.completeRecovery(account, recoveryCalldata); + + vm.startPrank(accountAddress1); + vm.expectRevert(IGuardianManager.RecoveryInProcess.selector); + emailRecoveryModule.onUninstall(""); + } + + function test_Recover_RevertWhen_UninstallModuleAndTryRecoveryAgain() public { + executeRecoveryFlowForAccount(accountAddress1, guardians1, calldataHash1, recoveryCalldata1); + address updatedOwner1 = validator.owners(accountAddress1); + assertEq(updatedOwner1, newOwner1); + + instance1.uninstallModule(MODULE_TYPE_EXECUTOR, recoveryModuleAddress, ""); + + EmailAuthMsg memory emailAuthMsg = + getAcceptanceEmailAuthMessage(accountAddress1, guardians1[0]); + + vm.expectRevert( + abi.encodeWithSelector( + IEmailRecoveryManager.InvalidGuardianStatus.selector, + uint256(GuardianStatus.NONE), + uint256(GuardianStatus.REQUESTED) + ) + ); + emailRecoveryModule.handleAcceptance(emailAuthMsg, templateIdx); } - // function test_Recover_RotatesMultipleOwnersSuccessfully() public { - // executeRecoveryFlowForAccount( - // accountAddress1, - // calldataHash1, - // recoveryCalldata1 - // ); - // vm.warp(block.timestamp + 12 seconds); - // executeRecoveryFlowForAccount( - // accountAddress2, - // calldataHash2, - // recoveryCalldata2 - // ); - // vm.warp(block.timestamp + 12 seconds); - // executeRecoveryFlowForAccount( - // accountAddress3, - // calldataHash3, - // recoveryCalldata3 - // ); - - // 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_Recover_RevertWhen_InvalidTimestamp() public { - // acceptGuardian(accountAddress1, guardians1[0]); - // EmailAuthMsg memory emailAuthMsg = getAcceptanceEmailAuthMessage( - // accountAddress2, - // guardians2[0] - // ); - - // vm.expectRevert("invalid timestamp"); - // emailRecoveryModule.handleAcceptance(emailAuthMsg, templateIdx); - // } + function test_Recover_UninstallModuleAndRecoverAgain() public { + executeRecoveryFlowForAccount(accountAddress1, guardians1, calldataHash1, recoveryCalldata1); + address updatedOwner = validator.owners(accountAddress1); + assertEq(updatedOwner, newOwner1); + + instance1.uninstallModule(MODULE_TYPE_EXECUTOR, recoveryModuleAddress, ""); + instance1.installModule({ + moduleTypeId: MODULE_TYPE_EXECUTOR, + module: recoveryModuleAddress, + data: abi.encode( + validatorAddress, + isInstalledContext, + functionSelector, + guardians1, + guardianWeights, + threshold, + delay, + expiry + ) + }); + + bytes memory newChangeOwnerCalldata = abi.encodeWithSelector(functionSelector, newOwner2); + bytes memory newRecoveryCalldata = abi.encode(validatorAddress, newChangeOwnerCalldata); + bytes32 newCalldataHash = keccak256(newRecoveryCalldata); + executeRecoveryFlowForAccount( + accountAddress1, guardians1, newCalldataHash, newRecoveryCalldata + ); + + updatedOwner = validator.owners(accountAddress1); + assertEq(updatedOwner, newOwner2); + } + + function test_Recover_RotatesMultipleOwnersSuccessfully() public { + executeRecoveryFlowForAccount(accountAddress1, guardians1, calldataHash1, recoveryCalldata1); + executeRecoveryFlowForAccount(accountAddress2, guardians2, calldataHash2, recoveryCalldata2); + executeRecoveryFlowForAccount(accountAddress3, guardians3, calldataHash3, recoveryCalldata3); + + 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_Recover_RecoversMultipleValidatorsOneAfterTheOther() public { + OwnableValidator validator2 = new OwnableValidator(); + address validator2Address = address(validator2); + instance1.installModule({ + moduleTypeId: MODULE_TYPE_VALIDATOR, + module: validator2Address, + data: abi.encode(owner2) + }); + + bytes memory newChangeOwnerCalldata = abi.encodeWithSelector(functionSelector, newOwner2); + bytes memory validator2RecoveryCalldata = + abi.encode(validatorAddress, newChangeOwnerCalldata); + bytes32 validator2CalldataHash = keccak256(validator2RecoveryCalldata); + + executeRecoveryFlowForAccount(accountAddress1, guardians1, calldataHash1, recoveryCalldata1); + address updatedOwner1 = validator.owners(accountAddress1); + assertEq(updatedOwner1, newOwner1); + + handleRecovery(accountAddress1, guardians1[0], validator2CalldataHash); + handleRecovery(accountAddress1, guardians1[1], validator2CalldataHash); + vm.warp(block.timestamp + delay); + emailRecoveryModule.completeRecovery(accountAddress1, validator2RecoveryCalldata); + + address updatedOwner2 = validator.owners(accountAddress1); + assertEq(updatedOwner2, newOwner2); + } + + function test_Recover_RevertWhen_RecoversMultipleValidatorsAtOnce() public { + OwnableValidator validator2 = new OwnableValidator(); + address validator2Address = address(validator2); + instance1.installModule({ + moduleTypeId: MODULE_TYPE_VALIDATOR, + module: validator2Address, + data: abi.encode(owner2) + }); + + bytes memory newChangeOwnerCalldata = abi.encodeWithSelector(functionSelector, newOwner2); + bytes memory validator2RecoveryCalldata = + abi.encode(validatorAddress, newChangeOwnerCalldata); + bytes32 validator2CalldataHash = keccak256(validator2RecoveryCalldata); + + // Accept guardians + acceptGuardian(accountAddress1, guardians1[0]); + acceptGuardian(accountAddress1, guardians1[1]); + vm.warp(block.timestamp + 12 seconds); + + // process recovery for validator 1 + handleRecovery(accountAddress1, guardians1[0], calldataHash1); + vm.warp(block.timestamp + 12 seconds); + // process recovery for validator 2 + handleRecovery(accountAddress1, guardians1[0], validator2CalldataHash); + + // process recovery for validator 1 + handleRecovery(accountAddress1, guardians1[1], calldataHash1); + vm.warp(block.timestamp + 12 seconds); + // process recovery for validator 2 + handleRecovery(accountAddress1, guardians1[1], validator2CalldataHash); + + vm.warp(block.timestamp + delay); + vm.expectRevert( + abi.encodeWithSelector( + IEmailRecoveryManager.InvalidCalldataHash.selector, + calldataHash1, + validator2CalldataHash + ) + ); + emailRecoveryModule.completeRecovery(accountAddress1, recoveryCalldata1); + } } diff --git a/test/integration/OwnableValidatorRecovery/UniversalEmailRecoveryModule/UniversalEmailRecoveryModuleBase.t.sol b/test/integration/OwnableValidatorRecovery/UniversalEmailRecoveryModule/UniversalEmailRecoveryModuleBase.t.sol index 63e49e52..d3d23317 100644 --- a/test/integration/OwnableValidatorRecovery/UniversalEmailRecoveryModule/UniversalEmailRecoveryModuleBase.t.sol +++ b/test/integration/OwnableValidatorRecovery/UniversalEmailRecoveryModule/UniversalEmailRecoveryModuleBase.t.sol @@ -17,7 +17,6 @@ import { EmailRecoveryUniversalFactory } from "src/factories/EmailRecoveryUniver import { EmailRecoveryManager } from "src/EmailRecoveryManager.sol"; import { UniversalEmailRecoveryModuleHarness } from "../../../unit/UniversalEmailRecoveryModuleHarness.sol"; -// test/unit/UniversalEmailRecoveryModuleHarness.sol import { OwnableValidator } from "src/test/OwnableValidator.sol"; import { IntegrationBase } from "../../IntegrationBase.t.sol"; @@ -229,17 +228,48 @@ abstract contract OwnableValidatorRecovery_UniversalEmailRecoveryModule_Base is 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)); + } + + // 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 = getAccountSaltForGuardian(account, guardian); + + bytes32 accountSalt; + if (optionalAccountSalt == bytes32(0)) { + accountSalt = getAccountSaltForGuardian(account, guardian); + } else { + accountSalt = optionalAccountSalt; + } EmailProof memory emailProof = generateMockEmailProof(subject, nullifier, accountSalt); @@ -259,6 +289,21 @@ abstract contract OwnableValidatorRecovery_UniversalEmailRecoveryModule_Base is emailRecoveryModule.handleRecovery(emailAuthMsg, templateIdx); } + // WithAccountSalt variation - used for creating incorrect recovery setups + function handleRecoveryWithAccountSalt( + address account, + address guardian, + bytes32 calldataHash, + bytes32 optionalAccountSalt + ) + public + { + EmailAuthMsg memory emailAuthMsg = getRecoveryEmailAuthMessageWithAccountSalt( + account, guardian, calldataHash, optionalAccountSalt + ); + emailRecoveryModule.handleRecovery(emailAuthMsg, templateIdx); + } + function getRecoveryEmailAuthMessage( address account, address guardian, @@ -266,6 +311,20 @@ abstract contract OwnableValidatorRecovery_UniversalEmailRecoveryModule_Base is ) public returns (EmailAuthMsg memory) + { + return + getRecoveryEmailAuthMessageWithAccountSalt(account, guardian, calldataHash, bytes32(0)); + } + + // WithAccountSalt variation - used for creating incorrect recovery setups + function getRecoveryEmailAuthMessageWithAccountSalt( + address account, + address guardian, + bytes32 calldataHash, + bytes32 optionalAccountSalt + ) + public + returns (EmailAuthMsg memory) { string memory accountString = SubjectUtils.addressToChecksumHexString(account); string memory calldataHashString = uint256(calldataHash).toHexString(32); @@ -277,7 +336,13 @@ abstract contract OwnableValidatorRecovery_UniversalEmailRecoveryModule_Base is string memory subject = string.concat(subjectPart1, subjectPart2, subjectPart3); bytes32 nullifier = generateNewNullifier(); - bytes32 accountSalt = getAccountSaltForGuardian(account, guardian); + + bytes32 accountSalt; + if (optionalAccountSalt == bytes32(0)) { + accountSalt = getAccountSaltForGuardian(account, guardian); + } else { + accountSalt = optionalAccountSalt; + } EmailProof memory emailProof = generateMockEmailProof(subject, nullifier, accountSalt);