diff --git a/.github/workflows/build-test-fmt.yml b/.github/workflows/build-test-fmt.yml index e36b5886..17f94353 100644 --- a/.github/workflows/build-test-fmt.yml +++ b/.github/workflows/build-test-fmt.yml @@ -26,11 +26,11 @@ jobs: - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 with: - version: nightly-cafc2606a2187a42b236df4aa65f4e8cdfcea970 + version: nightly-0079a1146b79a4aeda58b0258215bedb1f92700b - name: Run tests working-directory: packages/contracts - run: forge build + run: yarn build - name: Free Disk Space (Ubuntu) uses: jlumbroso/free-disk-space@main diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index fe42c3cd..29740de9 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -57,7 +57,7 @@ jobs: - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1.2.0 with: - version: nightly-cafc2606a2187a42b236df4aa65f4e8cdfcea970 + version: nightly-0079a1146b79a4aeda58b0258215bedb1f92700b - name: Run tests working-directory: packages/contracts diff --git a/.gitignore b/.gitignore index e07a445f..0ee936ed 100644 --- a/.gitignore +++ b/.gitignore @@ -82,3 +82,4 @@ book # For zksync zkout +.cache diff --git a/docs/getting-started.md b/docs/getting-started.md index 2d145a4e..b1c18339 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -10,13 +10,6 @@ First, install foundry by running the following command: curl -L https://foundry.paradigm.xyz | bash ``` -Then, install the specific version of foundry by running the following command: -Note: The latest version of foundry fails some tests. - -```sh -foundryup -v nightly-cafc2606a2187a42b236df4aa65f4e8cdfcea970 -``` - ## Clone the repository ```sh diff --git a/packages/contracts/README.md b/packages/contracts/README.md index 75ca880f..406c6006 100644 --- a/packages/contracts/README.md +++ b/packages/contracts/README.md @@ -32,7 +32,7 @@ Then, move `email_auth.zkey` and `email_auth.wasm` in the unzipped directory `pa Run each integration tests **one by one** as each test will consume lot of memory. ```bash -Eg: forge test --match-test 'testIntegration_Account_Recovery' -vvv --chain 8453 --ffi +Eg: contracts % forge test --skip '*ZKSync*' --match-contract "IntegrationTest" -vvv --chain 8453 --ffi ``` #### Deploy Common Contracts. You need to deploy common contracts, i.e., `ECDSAOwnedDKIMRegistry`, `Verifier`, and implementations of `EmailAuth` and `SimpleWallet`, only once before deploying each wallet. @@ -42,9 +42,9 @@ You need to deploy common contracts, i.e., `ECDSAOwnedDKIMRegistry`, `Verifier`, 4. `forge script script/DeployCommons.s.sol:Deploy --rpc-url $RPC_URL --chain-id $CHAIN_ID --etherscan-api-key $ETHERSCAN_API_KEY --broadcast --verify -vvvv` #### Deploy Each Wallet. -After deploying common contracts, you can deploy a proxy contract of `SimpleWallet`, which is an example contract supporting our email-based account recovery. +After deploying common contracts, you can deploy a proxy contract of `SimpleWallet`, which is an example contract supporting our email-based account recovery by `RecoveryController`. 1. Check that the env values of `DKIM`, `VERIFIER`, `EMAIL_AUTH_IMPL`, and `SIMPLE_WALLET_IMPL` are the same as those output by the `DeployCommons.s.sol` script. -2. `forge script script/DeploySimpleWallet.s.sol:Deploy --rpc-url $RPC_URL --chain-id $CHAIN_ID --broadcast -vvvv` +2. `forge script script/DeployRecoveryController.s.sol:Deploy --rpc-url $RPC_URL --chain-id $CHAIN_ID --broadcast -vvvv` ## Specification There are four main contracts that developers should understand: `IDKIMRegistry`, `Verifier`, `EmailAuth` and `EmailAccountRecovery`. @@ -251,62 +251,40 @@ Next, you should uncomment the following lines in `foundry.toml`. # via-ir = true ``` -And then you should uncomment the following lines in `src/EmailAccountRecovery.sol`. +Partial comment-out files can be found the following. Please uncomment them. +(Uncomment from `FOR_ZKSYNC:START` to `FOR_ZKSYNC:END`) -``` -// import {SystemContractsCaller} from "@matterlabs/zksync-contracts/l2/system-contracts/libraries/SystemContractsCaller.sol"; -// import {DEPLOYER_SYSTEM_CONTRACT} from "@matterlabs/zksync-contracts/l2/system-contracts/Constants.sol"; -``` - -And lines 229 - 263 in the `handleAcceptance` function too. +- src/utils/ZKSyncCreate2Factory.sol +- test/helpers/DeploymentHelper.sol -At the first forge build, you got the following warning like the following. +At the first forge build, you need to detect the missing libraries. ``` -┌──────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Warning: Your code or one of its dependencies uses the 'extcodesize' instruction, which is │ -│ usually needed in the following cases: │ -│ 1. To detect whether an address belongs to a smart contract. │ -│ 2. To detect whether the deploy code execution has finished. │ -│ zkSync Era comes with native account abstraction support (so accounts are smart contracts, │ -│ including private-key controlled EOAs), and you should avoid differentiating between contracts │ -│ and non-contract addresses. │ -└──────────────────────────────────────────────────────────────────────────────────────────────────┘ ---> ../../node_modules/forge-std/src/StdCheats.sol - -Failed to compile with zksolc: Missing libraries detected [ZkMissingLibrary { contract_name: "SubjectUtils", contract_path: "src/libraries/SubjectUtils.sol", missing_libraries: ["src/libraries/DecimalUtils.sol:DecimalUtils"] }, ZkMissingLibrary { contract_name: "DecimalUtils", contract_path: "src/libraries/DecimalUtils.sol", missing_libraries: [] }] +forge build --zksync --zk-detect-missing-libraries ``` -Please run the following command in order to deploy the missing libraries: +As you saw before, you need to deploy missing libraries. +You can deploy them by the following command for example. ``` -forge create --deploy-missing-libraries --private-key --rpc-url --chain --zksync -forge create --deploy-missing-libraries --private-key {YOUR_PRIVATE_KEY} --rpc-url https://sepolia.era.zksync.dev --chain 300 --zksync +$ forge build --zksync --zk-detect-missing-libraries +Missing libraries detected: src/libraries/SubjectUtils.sol:SubjectUtils, src/libraries/DecimalUtils.sol:DecimalUtils ``` -The above command output the following(for example): +Run the following command in order to deploy each missing library: ``` -[⠊] Compiling... -No files changed, compilation skipped -Deployer: 0xfB1CcCBDa2C41a77cDAC448641006Fc7fcf1f3b9 -Deployed to: 0x91cc0f0A227b8dD56794f9391E8Af48B40420A0b -Transaction hash: 0x4f94ab71443d01988105540c3abb09ed66f8af5d0bb6a88691e2dafa88b3583d -[⠢] Compiling... -[⠃] Compiling 68 files with 0.8.26 -[⠆] Solc 0.8.26 finished in 12.20s -Compiler run successful! -Deployer: 0xfB1CcCBDa2C41a77cDAC448641006Fc7fcf1f3b9 -Deployed to: 0x981E3Df952358A57753C7B85dE7949Da4aBCf54A -Transaction hash: 0xfdca7b9eb3ae933ca123111489572427ee95eb6be74978b24c73fe74cb4988d7 +forge create src/libraries/DecimalUtils.sol:DecimalUtils --private-key {YOUR_PRIVATE_KEY} --rpc-url https://sepolia.era.zksync.dev --chain 300 --zksync +forge create src/libraries/SubjectUtils.sol:SubjectUtils --private-key {YOUR_PRIVATE_KEY} --rpc-url https://sepolia.era.zksync.dev --chain 300 --zksync --libraries src/libraries/DecimalUtils.sol:DecimalUtils:{DECIMAL_UTILS_DEPLOYED_ADDRESS} ``` After that, you can see the following line in foundry.toml. Also, this line is needed only for foundry-zksync, if you use foundry, please remove this line. Otherwise, the test will fail. ``` -libraries = ["{PROJECT_DIR}/packages/contracts/src/libraries/DecimalUtils.sol:DecimalUtils:{DEPLOYED_ADDRESS}", "{PROJECT_DIR}/packages/contracts/src/libraries/SubjectUtils.sol:SubjectUtils:{DEPLOYED_ADDRESS}"] - +libraries = [ + "{PROJECT_DIR}/packages/contracts/src/libraries/DecimalUtils.sol:DecimalUtils:{DEPLOYED_ADDRESS}", + "{PROJECT_DIR}/packages/contracts/src/libraries/SubjectUtils.sol:SubjectUtils:{DEPLOYED_ADDRESS}"] ``` Incidentally, the above line already exists in `foundy.toml` with it commented out, if you uncomment it by replacing `{PROJECT_DIR}` with the appropriate path, it will also work. @@ -336,7 +314,7 @@ https://github.com/matter-labs/foundry-zksync/issues/382 Failing test cases are here. -EmailAuthWithUserOverrideableDkim.t.sol +DKIMRegistryUpgrade.t.sol - testAuthEmail() @@ -348,25 +326,47 @@ EmailAuth.t.sol - testExpectRevertAuthEmailInvalidSubject() - testExpectRevertAuthEmailInvalidTimestamp() -DeployCommons.t.sol +EmailAuthWithUserOverrideableDkim.t.sol + +- testAuthEmail() -- test_run() +# For integration testing -DeployRecoveryController.t.sol +To pass the instegration testing, you should use era-test-node. +See the following URL and install it. +https://github.com/matter-labs/era-test-node -- test_run() +Run the era-test-node -DeploySimpleWallet.t.sol +``` +era_test_node fork https://sepolia.era.zksync.dev +``` -- test_run() -- test_run_no_dkim() -- test_run_no_email_auth() -- test_run_no_simple_wallet() -- test_run_no_verifier() +You remove .zksolc-libraries-cache directory, and run the following command. -# For integration testing +``` +forge build --zksync --zk-detect-missing-libraries +``` + +As you saw before, you need to deploy missing libraries. +You can deploy them by the following command for example. + +``` +Missing libraries detected: src/libraries/SubjectUtils.sol:SubjectUtils, src/libraries/DecimalUtils.sol:DecimalUtils + +Run the following command in order to deploy each missing library: -forge test --match-test 'testIntegration_Account_Recovery' --zksync --chain 300 -vvv --ffi +forge create src/libraries/DecimalUtils.sol:DecimalUtils --private-key {YOUR_PRIVATE_KEY} --rpc-url http://127.0.0.1:8011 --chain 260 --zksync +forge create src/libraries/SubjectUtils.sol:SubjectUtils --private-key {YOUR_PRIVATE_KEY} --rpc-url http://127.0.0.1:8011 --chain 260 --zksync --libraries src/libraries/DecimalUtils.sol:DecimalUtils:{DECIMAL_UTILS_DEPLOYED_ADDRESS} +``` + +Set the libraries in foundry.toml using the above deployed address. + +And then, run the integration testing. + +``` +forge test --match-contract "IntegrationZkSyncTest" --system-mode=true --zksync --gas-limit 1000000000 --chain 300 -vvv --ffi +``` # For zkSync deployment (For test net) @@ -375,6 +375,6 @@ Second just run the following commands with `--zksync` ``` source .env -forge script script/DeployCommons.s.sol:Deploy --zksync --rpc-url $SEPOLIA_RPC_URL --broadcast -vvvv +forge script script/DeployRecoveryControllerZKSync.s.sol:Deploy --zksync --rpc-url $RPC_URL --broadcast --slow --via-ir --system-mode true -vvvv ``` diff --git a/packages/contracts/package.json b/packages/contracts/package.json index bc701317..b6e26824 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -3,10 +3,10 @@ "version": "1.0.0", "license": "MIT", "scripts": { - "build": "forge build", + "build": "forge build --skip '*ZKSync*'", "zkbuild": "forge build --zksync", - "test": "forge test --no-match-test \"testIntegration\" --no-match-contract \".*Script.*\"", - "zktest": "forge test --no-match-test \"testIntegration\" --no-match-contract \".*Script.*\" --zksync --chain 300", + "test": "forge test --no-match-test \"testIntegration\" --no-match-contract \".*Script.*\" --skip '*ZKSync*'", + "zktest": "forge test --no-match-test \"testIntegration\" --no-match-contract \".*Script.*\" --system-mode=true --zksync --gas-limit 1000000000 --chain 300", "lint": "solhint 'src/**/*.sol'" }, "dependencies": { diff --git a/packages/contracts/script/ChangeOwners.s.sol b/packages/contracts/script/ChangeOwners.s.sol index 2507fab0..8f7bd0ef 100644 --- a/packages/contracts/script/ChangeOwners.s.sol +++ b/packages/contracts/script/ChangeOwners.s.sol @@ -10,6 +10,7 @@ import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Own contract ChangeOwners is Script { function run() external { + uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); if (deployerPrivateKey == 0) { console.log("PRIVATE_KEY env var not set"); diff --git a/packages/contracts/script/DeployEmailAuthWithCreate2ZKSync.s.sol b/packages/contracts/script/DeployEmailAuthWithCreate2ZKSync.s.sol new file mode 100644 index 00000000..59972915 --- /dev/null +++ b/packages/contracts/script/DeployEmailAuthWithCreate2ZKSync.s.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "forge-std/Script.sol"; + +import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import "../src/EmailAuth.sol"; +import {ZKSyncCreate2Factory} from "../src/utils/ZKSyncCreate2Factory.sol"; + +contract Deploy is Script { + using ECDSA for *; + + function run() external { + uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + if (deployerPrivateKey == 0) { + console.log("PRIVATE_KEY env var not set"); + return; + } + + vm.startBroadcast(deployerPrivateKey); + + EmailAuth emailAuth = new EmailAuth(); + + address recoveredAccount = address(0x1); + bytes32 accountSalt = 0x0; + + ERC1967Proxy proxy = new ERC1967Proxy( + address(emailAuth), + abi.encodeCall(emailAuth.initialize, (recoveredAccount, accountSalt, address(this))) + ); + console.log("normal deployed proxyAddress %s", address(proxy)); + + ZKSyncCreate2Factory factory = new ZKSyncCreate2Factory(); + string memory artifact = vm.readFile("zkout/ERC1967Proxy.sol/ERC1967Proxy.json"); + bytes32 bytecodeHash = vm.parseJsonBytes32(artifact, ".hash"); + console.log("bytecodeHash"); + console.logBytes32(bytes32(bytecodeHash)); + + bytes memory emailAuthInit = abi.encode( + address(emailAuth), abi.encodeCall(EmailAuth.initialize, (recoveredAccount, accountSalt, address(this))) + ); + + address computedAddress = factory.computeAddress(accountSalt, bytecodeHash, emailAuthInit); + console.log("computedAddress", computedAddress); + + (bool success, bytes memory returnData) = factory.deploy(accountSalt, bytecodeHash, emailAuthInit); + + address payable proxyAddress = abi.decode(returnData, (address)); + console.log("proxyAddress %s", proxyAddress); + + EmailAuth emailAuthProxy = EmailAuth(proxyAddress); + console.log("emailAuthProxy.controller() %s", emailAuthProxy.controller()); + vm.stopBroadcast(); + } +} diff --git a/packages/contracts/script/DeployRecoveryControllerZKSync.s.sol b/packages/contracts/script/DeployRecoveryControllerZKSync.s.sol new file mode 100644 index 00000000..6767cc5f --- /dev/null +++ b/packages/contracts/script/DeployRecoveryControllerZKSync.s.sol @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "forge-std/Script.sol"; + +import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import "../test/helpers/SimpleWallet.sol"; +import "../test/helpers/RecoveryControllerZKSync.sol"; +import "../src/utils/Verifier.sol"; +import "../src/utils/ECDSAOwnedDKIMRegistry.sol"; +// import "../src/utils/ForwardDKIMRegistry.sol"; +import "../src/EmailAuth.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {ZKSyncCreate2Factory} from "../src/utils/ZKSyncCreate2Factory.sol"; + +contract Deploy is Script { + using ECDSA for *; + + ECDSAOwnedDKIMRegistry dkim; + Verifier verifier; + EmailAuth emailAuthImpl; + SimpleWallet simpleWallet; + RecoveryControllerZKSync recoveryControllerZKSync; + ZKSyncCreate2Factory factoryImpl; + + function run() external { + uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + if (deployerPrivateKey == 0) { + console.log("PRIVATE_KEY env var not set"); + return; + } + address signer = vm.envAddress("SIGNER"); + if (signer == address(0)) { + console.log("SIGNER env var not set"); + return; + } + + vm.startBroadcast(deployerPrivateKey); + address initialOwner = msg.sender; + + // Deploy ECDSAOwned DKIM registry + dkim = ECDSAOwnedDKIMRegistry(vm.envOr("ECDSA_DKIM", address(0))); + if (address(dkim) == address(0)) { + ECDSAOwnedDKIMRegistry ecdsaDkimImpl = new ECDSAOwnedDKIMRegistry(); + console.log( + "ECDSAOwnedDKIMRegistry implementation deployed at: %s", + address(ecdsaDkimImpl) + ); + ERC1967Proxy ecdsaDkimProxy = new ERC1967Proxy( + address(ecdsaDkimImpl), + abi.encodeCall(ecdsaDkimImpl.initialize, (initialOwner, signer)) + ); + dkim = ECDSAOwnedDKIMRegistry(address(ecdsaDkimProxy)); + console.log( + "ECDSAOwnedDKIMRegistry deployed at: %s", + address(dkim) + ); + vm.setEnv("ECDSA_DKIM", vm.toString(address(dkim))); + // dkimImpl = new ForwardDKIMRegistry(); + // console.log( + // "ForwardDKIMRegistry implementation deployed at: %s", + // address(dkimImpl) + // ); + // ERC1967Proxy dkimProxy = new ERC1967Proxy( + // address(dkimImpl), + // abi.encodeCall(dkimImpl.initialize, (initialOwner, signer)) + // ); + // dkim = ForwardDKIMRegistry(address(dkimProxy)); + // console.log("ForwardDKIMRegistry deployed at: %s", address(dkim)); + // vm.setEnv("DKIM", vm.toString(address(dkim))); + } + // Deploy Verifier + verifier = Verifier(vm.envOr("VERIFIER", address(0))); + if (address(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 = Verifier(address(verifierProxy)); + console.log("Verifier deployed at: %s", address(verifier)); + vm.setEnv("VERIFIER", vm.toString(address(verifier))); + } + + // Deploy EmailAuth Implementation + emailAuthImpl = EmailAuth(vm.envOr("EMAIL_AUTH_IMPL", address(0))); + if (address(emailAuthImpl) == address(0)) { + emailAuthImpl = new EmailAuth(); + console.log( + "EmailAuth implementation deployed at: %s", + address(emailAuthImpl) + ); + vm.setEnv("EMAIL_AUTH_IMPL", vm.toString(address(emailAuthImpl))); + } + + // Deploy Factory + factoryImpl = ZKSyncCreate2Factory(vm.envOr("FACTORY", address(0))); + if (address(factoryImpl) == address(0)) { + factoryImpl = new ZKSyncCreate2Factory(); + console.log( + "Factory implementation deployed at: %s", + address(factoryImpl) + ); + vm.setEnv("FACTORY", vm.toString(address(factoryImpl))); + } + + // Create RecoveryControllerZKSync as EmailAccountRecovery implementation + { + RecoveryControllerZKSync recoveryControllerZKSyncImpl = new RecoveryControllerZKSync(); + ERC1967Proxy recoveryControllerZKSyncProxy = new ERC1967Proxy( + address(recoveryControllerZKSyncImpl), + abi.encodeCall( + recoveryControllerZKSyncImpl.initialize, + ( + signer, + address(verifier), + address(dkim), + address(emailAuthImpl), + address(factoryImpl) + ) + ) + ); + recoveryControllerZKSync = RecoveryControllerZKSync( + payable(address(recoveryControllerZKSyncProxy)) + ); + console.log( + "RecoveryControllerZKSync deployed at: %s", + address(recoveryControllerZKSync) + ); + vm.setEnv( + "RECOVERY_CONTROLLER_ZKSYNC", + vm.toString(address(recoveryControllerZKSync)) + ); + } + + // Deploy SimpleWallet Implementation + { + SimpleWallet simpleWalletImpl = new SimpleWallet(); + console.log( + "SimpleWallet implementation deployed at: %s", + address(simpleWalletImpl) + ); + vm.setEnv( + "SIMPLE_WALLET_IMPL", + vm.toString(address(simpleWalletImpl)) + ); + ERC1967Proxy simpleWalletProxy = new ERC1967Proxy( + address(simpleWalletImpl), + abi.encodeCall( + simpleWalletImpl.initialize, + (signer, address(recoveryControllerZKSync)) + ) + ); + simpleWallet = SimpleWallet(payable(address(simpleWalletProxy))); + console.log("SimpleWallet deployed at: %s", address(simpleWallet)); + vm.setEnv("SIMPLE_WALLET", vm.toString(address(simpleWallet))); + } + vm.stopBroadcast(); + } +} diff --git a/packages/contracts/src/EmailAccountRecovery.sol b/packages/contracts/src/EmailAccountRecovery.sol index 0fa61497..d1754387 100644 --- a/packages/contracts/src/EmailAccountRecovery.sol +++ b/packages/contracts/src/EmailAccountRecovery.sol @@ -4,9 +4,6 @@ pragma solidity ^0.8.12; import "./EmailAuth.sol"; import "@openzeppelin/contracts/utils/Create2.sol"; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; -import {L2ContractHelper} from "@matterlabs/zksync-contracts/l2/contracts/L2ContractHelper.sol"; -// import {SystemContractsCaller} from "@matterlabs/zksync-contracts/l2/system-contracts/libraries/SystemContractsCaller.sol"; -// import {DEPLOYER_SYSTEM_CONTRACT} from "@matterlabs/zksync-contracts/l2/system-contracts/Constants.sol"; /// @title Email Account Recovery Contract /// @notice Provides mechanisms for email-based account recovery, leveraging guardians and template-based email verification. @@ -16,8 +13,6 @@ abstract contract EmailAccountRecovery { address public verifierAddr; address public dkimAddr; address public emailAuthImplementationAddr; - bytes32 public proxyBytecodeHash = - 0x0100008338d33e12c716a5b695c6f7f4e526cf162a9378c0713eea5386c09951; /// @notice Returns the address of the verifier contract. /// @dev This function is virtual and can be overridden by inheriting contracts. @@ -118,18 +113,13 @@ abstract contract EmailAccountRecovery { function computeEmailAuthAddress( address recoveredAccount, bytes32 accountSalt - ) public view returns (address) { - // If on zksync, we use L2ContractHelper.computeCreate2Address - if (block.chainid == 324 || block.chainid == 300) { - // The bytecodeHash is hardcoded here because type(ERC1967Proxy).creationCode doesn't work on eraVM currently - // If you failed some test cases, check the bytecodeHash by yourself - // see, test/ComputeCreate2Address.t.sol - return - L2ContractHelper.computeCreate2Address( - address(this), - accountSalt, - bytes32(proxyBytecodeHash), - keccak256( + ) public view virtual returns (address) { + return + Create2.computeAddress( + accountSalt, + keccak256( + abi.encodePacked( + type(ERC1967Proxy).creationCode, abi.encode( emailAuthImplementation(), abi.encodeCall( @@ -138,29 +128,27 @@ abstract contract EmailAccountRecovery { ) ) ) - ); - } else { - return - Create2.computeAddress( - accountSalt, - keccak256( - abi.encodePacked( - type(ERC1967Proxy).creationCode, - abi.encode( - emailAuthImplementation(), - abi.encodeCall( - EmailAuth.initialize, - ( - recoveredAccount, - accountSalt, - address(this) - ) - ) - ) - ) - ) - ); - } + ) + ); + } + + /// @notice Deploys a new proxy contract for email authentication. + /// @dev This function uses the CREATE2 opcode to deploy a new ERC1967Proxy contract with a deterministic address. + /// @param recoveredAccount The address of the account to be recovered. + /// @param accountSalt A bytes32 salt value used to ensure the uniqueness of the deployed proxy address. + /// @return address The address of the newly deployed proxy contract. + function deployEmailAuthProxy( + address recoveredAccount, + bytes32 accountSalt + ) internal virtual returns (address) { + ERC1967Proxy proxy = new ERC1967Proxy{salt: accountSalt}( + emailAuthImplementation(), + abi.encodeCall( + EmailAuth.initialize, + (recoveredAccount, accountSalt, address(this)) + ) + ); + return address(proxy); } /// @notice Calculates a unique command template ID for an acceptance command template using its index. @@ -225,57 +213,11 @@ abstract contract EmailAccountRecovery { EmailAuth guardianEmailAuth; if (guardian.code.length == 0) { - // // Deploy proxy of the guardian's EmailAuth contract - // if (block.chainid == 324 || block.chainid == 300) { - // (bool success, bytes memory returnData) = SystemContractsCaller - // .systemCallWithReturndata( - // uint32(gasleft()), - // address(DEPLOYER_SYSTEM_CONTRACT), - // uint128(0), - // abi.encodeCall( - // DEPLOYER_SYSTEM_CONTRACT.create2, - // ( - // emailAuthMsg.proof.accountSalt, - // proxyBytecodeHash, - // abi.encode( - // emailAuthImplementation(), - // abi.encodeCall( - // EmailAuth.initialize, - // ( - // recoveredAccount, - // emailAuthMsg.proof.accountSalt, - // address(this) - // ) - // ) - // ) - // ) - // ) - // ); - // address payable proxyAddress = abi.decode(returnData, (address)); - // ERC1967Proxy proxy = ERC1967Proxy(proxyAddress); - // guardianEmailAuth = EmailAuth(address(proxy)); - // guardianEmailAuth.initialize( - // recoveredAccount, - // emailAuthMsg.proof.accountSalt, - // address(this) - // ); - // } else { - // Deploy proxy of the guardian's EmailAuth contract - ERC1967Proxy proxy = new ERC1967Proxy{ - salt: emailAuthMsg.proof.accountSalt - }( - emailAuthImplementation(), - abi.encodeCall( - EmailAuth.initialize, - ( - recoveredAccount, - emailAuthMsg.proof.accountSalt, - address(this) - ) - ) + address proxyAddress = deployEmailAuthProxy( + recoveredAccount, + emailAuthMsg.proof.accountSalt ); - guardianEmailAuth = EmailAuth(address(proxy)); - // } + guardianEmailAuth = EmailAuth(proxyAddress); guardianEmailAuth.initDKIMRegistry(dkim()); guardianEmailAuth.initVerifier(verifier()); for ( diff --git a/packages/contracts/src/EmailAccountRecoveryZKSync.sol b/packages/contracts/src/EmailAccountRecoveryZKSync.sol new file mode 100644 index 00000000..fbd14a68 --- /dev/null +++ b/packages/contracts/src/EmailAccountRecoveryZKSync.sol @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.12; + +import {EmailAuth} from "./EmailAuth.sol"; +import {EmailAccountRecovery} from "./EmailAccountRecovery.sol"; +import {ZKSyncCreate2Factory} from "./utils/ZKSyncCreate2Factory.sol"; + +/// @title Email Account Recovery Contract +/// @notice Provides mechanisms for email-based account recovery, leveraging guardians and template-based email verification. +/// @dev This contract is abstract and requires implementation of several methods for configuring a new guardian and recovering an account contract. +abstract contract EmailAccountRecoveryZKSync is EmailAccountRecovery { + + // This is the address of the zkSync factory contract + address public factoryAddr; + // The bytecodeHash is hardcoded here because type(ERC1967Proxy).creationCode doesn't work on eraVM currently + // If you failed some test cases, check the bytecodeHash by yourself + // see, test/ComputeCreate2Address.t.sol + bytes32 public constant proxyBytecodeHash = 0x0100008338d33e12c716a5b695c6f7f4e526cf162a9378c0713eea5386c09951; + + /// @notice Returns the address of the zkSyncfactory contract. + /// @dev This function is virtual and can be overridden by inheriting contracts. + /// @return address The address of the zkSync factory contract. + function factory() public view virtual returns (address) { + return factoryAddr; + } + + /// @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 override returns (address) { + // If on zksync, we use another logic to calculate create2 address. + return ZKSyncCreate2Factory(factory()).computeAddress( + accountSalt, + proxyBytecodeHash, + abi.encode( + emailAuthImplementation(), + abi.encodeCall( + EmailAuth.initialize, + (recoveredAccount, accountSalt, address(this)) + ) + ) + ); + } + + /// @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 override returns (address) { + (bool success, bytes memory returnData) = ZKSyncCreate2Factory(factory()).deploy( + accountSalt, + proxyBytecodeHash, + abi.encode( + emailAuthImplementation(), + abi.encodeCall( + EmailAuth.initialize, + ( + recoveredAccount, + accountSalt, + address(this) + ) + ) + ) + ); + address payable proxyAddress = abi.decode(returnData, (address)); + return proxyAddress; + } +} \ No newline at end of file diff --git a/packages/contracts/src/utils/ZKSyncCreate2Factory.sol b/packages/contracts/src/utils/ZKSyncCreate2Factory.sol new file mode 100644 index 00000000..ee3ba883 --- /dev/null +++ b/packages/contracts/src/utils/ZKSyncCreate2Factory.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.12; + +import {ZKSyncCreate2FactoryBase} from "./ZKSyncCreate2FactoryBase.sol"; +// // FOR_ZKSYNC:START +// import {L2ContractHelper} from "@matterlabs/zksync-contracts/l2/contracts/L2ContractHelper.sol"; +// import {SystemContractsCaller} from "@matterlabs/zksync-contracts/l2/system-contracts/libraries/SystemContractsCaller.sol"; +// import {DEPLOYER_SYSTEM_CONTRACT} from "@matterlabs/zksync-contracts/l2/system-contracts/Constants.sol"; +// // FOR_ZKSYNC:END + +contract ZKSyncCreate2Factory is ZKSyncCreate2FactoryBase { + // // FOR_ZKSYNC:START + // function computeAddress(bytes32 salt, bytes32 bytecodeHash, bytes memory input) external view returns (address) { + // return L2ContractHelper.computeCreate2Address(address(this), salt, bytes32(bytecodeHash), keccak256(input)); + // } + + // function deploy(bytes32 salt, bytes32 bytecodeHash, bytes memory input) + // external + // returns (bool success, bytes memory returnData) + // { + // (success, returnData) = SystemContractsCaller.systemCallWithReturndata( + // uint32(gasleft()), + // address(DEPLOYER_SYSTEM_CONTRACT), + // uint128(0), + // abi.encodeCall(DEPLOYER_SYSTEM_CONTRACT.create2, (salt, bytecodeHash, input)) + // ); + // } + // // FOR_ZKSYNC:END +} diff --git a/packages/contracts/src/utils/ZKSyncCreate2FactoryBase.sol b/packages/contracts/src/utils/ZKSyncCreate2FactoryBase.sol new file mode 100644 index 00000000..f97447e7 --- /dev/null +++ b/packages/contracts/src/utils/ZKSyncCreate2FactoryBase.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.12; + +contract ZKSyncCreate2FactoryBase { + function computeAddress(bytes32 salt, bytes32 bytecodeHash, bytes memory input) external virtual view returns (address) { + return address(0); + } + + function deploy(bytes32 salt, bytes32 bytecodeHash, bytes memory input) + external + virtual + returns (bool success, bytes memory returnData) + { + return (false, ""); + } +} diff --git a/packages/contracts/test/ComputeCreate2Address.t.sol b/packages/contracts/test/ComputeCreate2Address.t.sol deleted file mode 100644 index abd02898..00000000 --- a/packages/contracts/test/ComputeCreate2Address.t.sol +++ /dev/null @@ -1,93 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.12; - -import "forge-std/Test.sol"; -import "forge-std/console.sol"; - -import {L2ContractHelper} from "@matterlabs/zksync-contracts/l2/contracts/L2ContractHelper.sol"; -import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; -// import {SystemContractsCaller} from "@matterlabs/zksync-contracts/l2/system-contracts/libraries/SystemContractsCaller.sol"; -// import {DEPLOYER_SYSTEM_CONTRACT} from "@matterlabs/zksync-contracts/l2/system-contracts/Constants.sol"; - -import "../src/EmailAuth.sol"; -import "./helpers/StructHelper.sol"; - -contract ComputeCreate2AddressTest is StructHelper { - constructor() {} - - function testComputeCreate2Address() public { - // This test is not neccessary for non zkSync chains - if (block.chainid != 324 && block.chainid != 300) { - console.log("skip"); - return; - } - - address recoveredAccount = address(0x1); - bytes32 accountSalt = 0x0; - - // See the example code - // https://github.com/matter-labs/foundry-zksync/blob/13497a550e4a097c57bec7430435ab810a6d10fc/zk-tests/src/Contracts.t.sol#L195 - string memory artifact = vm.readFile( - "zkout/ERC1967Proxy.sol/ERC1967Proxy.json" - ); - bytes32 bytecodeHash = vm.parseJsonBytes32( - artifact, - '.hash' - ); - console.log("bytecodeHash"); - console.logBytes32(bytes32(bytecodeHash)); - address computedAddress = L2ContractHelper.computeCreate2Address( - address(this), - accountSalt, - bytes32(bytecodeHash), - keccak256( - abi.encode( - address(emailAuth), - abi.encodeCall( - EmailAuth.initialize, - (recoveredAccount, accountSalt, address(this)) - ) - ) - ) - ); - - console.log("computedAddress", computedAddress); - - // This is the previous way to deploy the proxy -> test has been passed but not working actually by clave side - // ERC1967Proxy proxy = new ERC1967Proxy{salt: accountSalt}( - // address(emailAuth), - // abi.encodeCall( - // EmailAuth.initialize, - // (recoveredAccount, accountSalt, address(this)) - // ) - // ); - // console.log("proxy", address(proxy)); - // assertEq(computedAddress, address(proxy)); - - // (bool success, bytes memory returnData) = SystemContractsCaller - // .systemCallWithReturndata( - // uint32(gasleft()), - // address(DEPLOYER_SYSTEM_CONTRACT), - // uint128(0), - // abi.encodeCall( - // DEPLOYER_SYSTEM_CONTRACT.create2, - // ( - // accountSalt, - // bytecodeHash, - // abi.encode( - // address(emailAuth), - // abi.encodeCall( - // EmailAuth.initialize, - // ( - // recoveredAccount, - // accountSalt, - // address(this) - // ) - // ) - // ) - // ) - // ) - // ); - // address payable proxyAddress = abi.decode(returnData, (address)); - } -} diff --git a/packages/contracts/test/ComputeCreate2AddressZKSync.t.sol b/packages/contracts/test/ComputeCreate2AddressZKSync.t.sol new file mode 100644 index 00000000..eb68cbc9 --- /dev/null +++ b/packages/contracts/test/ComputeCreate2AddressZKSync.t.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.12; + +import "forge-std/Test.sol"; +import "forge-std/console.sol"; + +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {ZKSyncCreate2Factory} from "../src/utils/ZKSyncCreate2Factory.sol"; +import "../src/EmailAuth.sol"; +import "./helpers/StructHelper.sol"; + +contract ComputeCreate2AddressZKSyncTest is StructHelper { + constructor() {} + + function testComputeCreate2Address() public { + // This test is not neccessary for non zkSync chains + if (block.chainid != 324 && block.chainid != 300) { + console.log("skip"); + return; + } + + address recoveredAccount = address(0x1); + bytes32 accountSalt = 0x0; + ZKSyncCreate2Factory factory = new ZKSyncCreate2Factory(); + bytes memory emailAuthInit = abi.encode( + address(emailAuth), abi.encodeCall(EmailAuth.initialize, (recoveredAccount, accountSalt, address(this))) + ); + + // See the example code + // https://github.com/matter-labs/foundry-zksync/blob/13497a550e4a097c57bec7430435ab810a6d10fc/zk-tests/src/Contracts.t.sol#L195 + string memory artifact = vm.readFile("zkout/ERC1967Proxy.sol/ERC1967Proxy.json"); + bytes32 bytecodeHash = vm.parseJsonBytes32(artifact, ".hash"); + console.log("bytecodeHash"); + console.logBytes32(bytes32(bytecodeHash)); + + address computedAddress = factory.computeAddress(accountSalt, bytecodeHash, emailAuthInit); + console.log("computedAddress", computedAddress); + + (bool success, bytes memory returnData) = factory.deploy(accountSalt, bytecodeHash, emailAuthInit); + + address payable proxyAddress = abi.decode(returnData, (address)); + assertEq(proxyAddress, computedAddress); + } +} \ No newline at end of file diff --git a/packages/contracts/test/EmailAccountRecovery/EmailAccountRecovery_acceptanceSubjectTemplates.t.sol b/packages/contracts/test/EmailAccountRecovery/EmailAccountRecovery_acceptanceSubjectTemplates.t.sol new file mode 100644 index 00000000..7e596e79 --- /dev/null +++ b/packages/contracts/test/EmailAccountRecovery/EmailAccountRecovery_acceptanceSubjectTemplates.t.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.12; + +import "forge-std/Test.sol"; +import "forge-std/console.sol"; +import {EmailAuth, EmailAuthMsg} from "../../src/EmailAuth.sol"; +import {RecoveryController} from "../helpers/RecoveryController.sol"; +import {StructHelper} from "../helpers/StructHelper.sol"; +import {SimpleWallet} from "../helpers/SimpleWallet.sol"; +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; + +contract EmailAccountRecoveryTest_acceptanceCommandTemplates is StructHelper { + constructor() {} + + function setUp() public override { + super.setUp(); + } + + function testAcceptanceCommandTemplates() public { + skipIfZkSync(); + + setUp(); + string[][] memory res = recoveryController.acceptanceCommandTemplates(); + assertEq(res[0][0], "Accept"); + assertEq(res[0][1], "guardian"); + assertEq(res[0][2], "request"); + assertEq(res[0][3], "for"); + assertEq(res[0][4], "{ethAddr}"); + } +} diff --git a/packages/contracts/test/EmailAccountRecovery/EmailAccountRecovery_completeRecovery.t.sol b/packages/contracts/test/EmailAccountRecovery/EmailAccountRecovery_completeRecovery.t.sol new file mode 100644 index 00000000..00ba5401 --- /dev/null +++ b/packages/contracts/test/EmailAccountRecovery/EmailAccountRecovery_completeRecovery.t.sol @@ -0,0 +1,237 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.12; + +import "forge-std/Test.sol"; +import "forge-std/console.sol"; +import {EmailAuth, EmailAuthMsg} from "../../src/EmailAuth.sol"; +import {RecoveryController} from "../helpers/RecoveryController.sol"; +import {StructHelper} from "../helpers/StructHelper.sol"; +import {SimpleWallet} from "../helpers/SimpleWallet.sol"; +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; + +contract EmailAccountRecoveryTest_completeRecovery is StructHelper { + constructor() {} + + function setUp() public override { + super.setUp(); + } + + function requestGuardian() public { + setUp(); + require( + recoveryController.guardians(guardian) == + RecoveryController.GuardianStatus.NONE + ); + + vm.startPrank(deployer); + recoveryController.requestGuardian(guardian); + vm.stopPrank(); + + require( + recoveryController.guardians(guardian) == + RecoveryController.GuardianStatus.REQUESTED + ); + } + + function handleAcceptance() public { + requestGuardian(); + + console.log("guardian", guardian); + + require( + recoveryController.guardians(guardian) == + RecoveryController.GuardianStatus.REQUESTED + ); + + uint templateIdx = 0; + + EmailAuthMsg memory emailAuthMsg = buildEmailAuthMsg(); + uint templateId = recoveryController.computeAcceptanceTemplateId( + templateIdx + ); + emailAuthMsg.templateId = templateId; + bytes[] memory commandParamsForAcceptance = new bytes[](1); + commandParamsForAcceptance[0] = abi.encode(address(simpleWallet)); + emailAuthMsg.commandParams = commandParamsForAcceptance; + + vm.mockCall( + address(recoveryController.emailAuthImplementationAddr()), + abi.encodeWithSelector(EmailAuth.authEmail.selector, emailAuthMsg), + abi.encode(0x0) + ); + + // acceptGuardian is internal, we call handleAcceptance, which calls acceptGuardian internally. + vm.startPrank(someRelayer); + recoveryController.handleAcceptance(emailAuthMsg, templateIdx); + vm.stopPrank(); + + require( + recoveryController.guardians(guardian) == + RecoveryController.GuardianStatus.ACCEPTED + ); + } + + function handleRecovery() public { + handleAcceptance(); + + assertEq(recoveryController.isRecovering(address(simpleWallet)), false); + assertEq( + recoveryController.currentTimelockOfAccount(address(simpleWallet)), + 0 + ); + assertEq(simpleWallet.owner(), deployer); + assertEq( + recoveryController.newSignerCandidateOfAccount( + address(simpleWallet) + ), + address(0x0) + ); + + uint templateIdx = 0; + + EmailAuthMsg memory emailAuthMsg = buildEmailAuthMsg(); + uint templateId = recoveryController.computeRecoveryTemplateId( + templateIdx + ); + emailAuthMsg.templateId = templateId; + bytes[] memory commandParamsForRecovery = new bytes[](2); + commandParamsForRecovery[0] = abi.encode(simpleWallet); + commandParamsForRecovery[1] = abi.encode(newSigner); + emailAuthMsg.commandParams = commandParamsForRecovery; + + vm.mockCall( + address(recoveryController.emailAuthImplementationAddr()), + abi.encodeWithSelector(EmailAuth.authEmail.selector, emailAuthMsg), + abi.encode(0x0) + ); + + vm.startPrank(someRelayer); + recoveryController.handleRecovery(emailAuthMsg, templateIdx); + vm.stopPrank(); + + assertEq(recoveryController.isRecovering(address(simpleWallet)), true); + assertEq(simpleWallet.owner(), deployer); + assertEq( + recoveryController.newSignerCandidateOfAccount( + address(simpleWallet) + ), + newSigner + ); + assertEq( + recoveryController.currentTimelockOfAccount(address(simpleWallet)), + block.timestamp + + recoveryController.timelockPeriodOfAccount( + address(simpleWallet) + ) + ); + } + + function testCompleteRecovery() public { + skipIfZkSync(); + + handleRecovery(); + + assertEq(recoveryController.isRecovering(address(simpleWallet)), true); + assertEq( + recoveryController.currentTimelockOfAccount(address(simpleWallet)), + block.timestamp + + recoveryController.timelockPeriodOfAccount( + address(simpleWallet) + ) + ); + assertEq(simpleWallet.owner(), deployer); + assertEq( + recoveryController.newSignerCandidateOfAccount( + address(simpleWallet) + ), + newSigner + ); + + vm.startPrank(someRelayer); + vm.warp(4 days); + recoveryController.completeRecovery( + address(simpleWallet), + new bytes(0) + ); + vm.stopPrank(); + + assertEq(recoveryController.isRecovering(address(simpleWallet)), false); + assertEq( + recoveryController.currentTimelockOfAccount(address(simpleWallet)), + 0 + ); + assertEq(simpleWallet.owner(), newSigner); + assertEq( + recoveryController.newSignerCandidateOfAccount( + address(simpleWallet) + ), + address(0x0) + ); + } + + function testExpectRevertCompleteRecoveryRecoveryNotInProgress() public { + skipIfZkSync(); + + handleAcceptance(); + + assertEq(recoveryController.isRecovering(address(simpleWallet)), false); + assertEq( + recoveryController.currentTimelockOfAccount(address(simpleWallet)), + 0 + ); + assertEq(simpleWallet.owner(), deployer); + assertEq( + recoveryController.newSignerCandidateOfAccount( + address(simpleWallet) + ), + address(0x0) + ); + + vm.startPrank(someRelayer); + vm.warp(4 days); + vm.expectRevert(bytes("recovery not in progress")); + bytes memory recoveryCalldata; + recoveryController.completeRecovery( + address(simpleWallet), + recoveryCalldata + ); + + vm.stopPrank(); + } + + function testExpectRevertCompleteRecovery() public { + skipIfZkSync(); + + vm.warp(block.timestamp + 3 days); + + handleRecovery(); + + assertEq(recoveryController.isRecovering(address(simpleWallet)), true); + assertEq( + recoveryController.currentTimelockOfAccount(address(simpleWallet)), + block.timestamp + + recoveryController.timelockPeriodOfAccount( + address(simpleWallet) + ) + ); + assertEq(simpleWallet.owner(), deployer); + assertEq( + recoveryController.newSignerCandidateOfAccount( + address(simpleWallet) + ), + newSigner + ); + + vm.warp(0); + + vm.startPrank(someRelayer); + vm.expectRevert(bytes("timelock not expired")); + bytes memory recoveryCalldata; + recoveryController.completeRecovery( + address(simpleWallet), + recoveryCalldata + ); + + vm.stopPrank(); + } +} diff --git a/packages/contracts/test/EmailAccountRecovery/EmailAccountRecovery_handleAcceptance.t.sol b/packages/contracts/test/EmailAccountRecovery/EmailAccountRecovery_handleAcceptance.t.sol new file mode 100644 index 00000000..5d3e3428 --- /dev/null +++ b/packages/contracts/test/EmailAccountRecovery/EmailAccountRecovery_handleAcceptance.t.sol @@ -0,0 +1,218 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.12; + +import "forge-std/Test.sol"; +import "forge-std/console.sol"; +import {EmailAuth, EmailAuthMsg} from "../../src/EmailAuth.sol"; +import {RecoveryController} from "../helpers/RecoveryController.sol"; +import {StructHelper} from "../helpers/StructHelper.sol"; +import {SimpleWallet} from "../helpers/SimpleWallet.sol"; +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; + +contract EmailAccountRecoveryTest_handleAcceptance is StructHelper { + constructor() {} + + function setUp() public override { + super.setUp(); + } + + function requestGuardian() public { + skipIfZkSync(); + + setUp(); + require( + recoveryController.guardians(guardian) == + RecoveryController.GuardianStatus.NONE + ); + + vm.startPrank(deployer); + recoveryController.requestGuardian(guardian); + vm.stopPrank(); + + require( + recoveryController.guardians(guardian) == + RecoveryController.GuardianStatus.REQUESTED + ); + } + + function testHandleAcceptance() public { + skipIfZkSync(); + + requestGuardian(); + + console.log("guardian", guardian); + + require( + recoveryController.guardians(guardian) == + RecoveryController.GuardianStatus.REQUESTED + ); + + uint templateIdx = 0; + + EmailAuthMsg memory emailAuthMsg = buildEmailAuthMsg(); + uint templateId = recoveryController.computeAcceptanceTemplateId( + templateIdx + ); + emailAuthMsg.templateId = templateId; + bytes[] memory commandParamsForAcceptance = new bytes[](1); + commandParamsForAcceptance[0] = abi.encode(address(simpleWallet)); + emailAuthMsg.commandParams = commandParamsForAcceptance; + + vm.mockCall( + address(recoveryController.emailAuthImplementationAddr()), + abi.encodeWithSelector(EmailAuth.authEmail.selector, emailAuthMsg), + abi.encode(0x0) + ); + + // acceptGuardian is internal, we call handleAcceptance, which calls acceptGuardian internally. + vm.startPrank(someRelayer); + recoveryController.handleAcceptance(emailAuthMsg, templateIdx); + vm.stopPrank(); + + require( + recoveryController.guardians(guardian) == + RecoveryController.GuardianStatus.ACCEPTED + ); + } + + // Can not test recovery in progress using handleAcceptance + // Can not test invalid guardian using handleAcceptance + + function testExpectRevertHandleAcceptanceGuardianStatusMustBeRequested() + public + { + skipIfZkSync(); + + requestGuardian(); + + require( + recoveryController.guardians(guardian) == + RecoveryController.GuardianStatus.REQUESTED + ); + + uint templateIdx = 0; + + EmailAuthMsg memory emailAuthMsg = buildEmailAuthMsg(); + uint templateId = recoveryController.computeAcceptanceTemplateId( + templateIdx + ); + emailAuthMsg.templateId = templateId; + bytes[] memory commandParamsForAcceptance = new bytes[](1); + commandParamsForAcceptance[0] = abi.encode(address(simpleWallet)); + emailAuthMsg.commandParams = commandParamsForAcceptance; + emailAuthMsg.proof.accountSalt = 0x0; + + vm.mockCall( + address(recoveryController.emailAuthImplementationAddr()), + abi.encodeWithSelector(EmailAuth.authEmail.selector, emailAuthMsg), + abi.encode(0x0) + ); + + vm.startPrank(someRelayer); + vm.expectRevert(bytes("guardian status must be REQUESTED")); + recoveryController.handleAcceptance(emailAuthMsg, templateIdx); + vm.stopPrank(); + } + + function testExpectRevertHandleAcceptanceInvalidTemplateIndex() public { + skipIfZkSync(); + + requestGuardian(); + + require( + recoveryController.guardians(guardian) == + RecoveryController.GuardianStatus.REQUESTED + ); + + uint templateIdx = 1; + + EmailAuthMsg memory emailAuthMsg = buildEmailAuthMsg(); + uint templateId = recoveryController.computeAcceptanceTemplateId( + templateIdx + ); + emailAuthMsg.templateId = templateId; + bytes[] memory commandParamsForAcceptance = new bytes[](1); + commandParamsForAcceptance[0] = abi.encode(address(simpleWallet)); + emailAuthMsg.commandParams = commandParamsForAcceptance; + + vm.mockCall( + address(recoveryController.emailAuthImplementationAddr()), + abi.encodeWithSelector(EmailAuth.authEmail.selector, emailAuthMsg), + abi.encode(0x0) + ); + + vm.startPrank(someRelayer); + vm.expectRevert(bytes("invalid template index")); + recoveryController.handleAcceptance(emailAuthMsg, templateIdx); + vm.stopPrank(); + } + + function testExpectRevertHandleAcceptanceInvalidCommandParams() public { + skipIfZkSync(); + + requestGuardian(); + + require( + recoveryController.guardians(guardian) == + RecoveryController.GuardianStatus.REQUESTED + ); + + uint templateIdx = 0; + + EmailAuthMsg memory emailAuthMsg = buildEmailAuthMsg(); + uint templateId = recoveryController.computeAcceptanceTemplateId( + templateIdx + ); + emailAuthMsg.templateId = templateId; + bytes[] memory commandParamsForAcceptance = new bytes[](2); + commandParamsForAcceptance[0] = abi.encode(address(simpleWallet)); + commandParamsForAcceptance[1] = abi.encode(address(simpleWallet)); + emailAuthMsg.commandParams = commandParamsForAcceptance; + + vm.mockCall( + address(recoveryController.emailAuthImplementationAddr()), + abi.encodeWithSelector(EmailAuth.authEmail.selector, emailAuthMsg), + abi.encode(0x0) + ); + + vm.startPrank(someRelayer); + vm.expectRevert(bytes("invalid command params")); + recoveryController.handleAcceptance(emailAuthMsg, templateIdx); + vm.stopPrank(); + } + + function testExpectRevertHandleAcceptanceInvalidWalletAddressInEmail() + public + { + skipIfZkSync(); + + requestGuardian(); + + require( + recoveryController.guardians(guardian) == + RecoveryController.GuardianStatus.REQUESTED + ); + + uint templateIdx = 0; + + EmailAuthMsg memory emailAuthMsg = buildEmailAuthMsg(); + uint templateId = recoveryController.computeAcceptanceTemplateId( + templateIdx + ); + emailAuthMsg.templateId = templateId; + bytes[] memory commandParamsForAcceptance = new bytes[](1); + commandParamsForAcceptance[0] = abi.encode(address(0x0)); + emailAuthMsg.commandParams = commandParamsForAcceptance; + + vm.mockCall( + address(recoveryController.emailAuthImplementationAddr()), + abi.encodeWithSelector(EmailAuth.authEmail.selector, emailAuthMsg), + abi.encode(0x0) + ); + + vm.startPrank(someRelayer); + vm.expectRevert(bytes("invalid account in email")); + recoveryController.handleAcceptance(emailAuthMsg, templateIdx); + vm.stopPrank(); + } +} diff --git a/packages/contracts/test/EmailAccountRecovery.t.sol b/packages/contracts/test/EmailAccountRecovery/EmailAccountRecovery_handleRecovery.t.sol similarity index 54% rename from packages/contracts/test/EmailAccountRecovery.t.sol rename to packages/contracts/test/EmailAccountRecovery/EmailAccountRecovery_handleRecovery.t.sol index 834e52e7..ccaca48e 100644 --- a/packages/contracts/test/EmailAccountRecovery.t.sol +++ b/packages/contracts/test/EmailAccountRecovery/EmailAccountRecovery_handleRecovery.t.sol @@ -3,130 +3,20 @@ pragma solidity ^0.8.12; import "forge-std/Test.sol"; import "forge-std/console.sol"; -import {EmailAuthMsg} from "../src/EmailAuth.sol"; -import "./helpers/StructHelper.sol"; -import "./helpers/SimpleWallet.sol"; - +import {EmailAuth, EmailAuthMsg} from "../../src/EmailAuth.sol"; +import {RecoveryController} from "../helpers/RecoveryController.sol"; +import {StructHelper} from "../helpers/StructHelper.sol"; +import {SimpleWallet} from "../helpers/SimpleWallet.sol"; import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; -contract EmailAccountRecoveryTest is StructHelper { +contract EmailAccountRecoveryTest_handleRecovery is StructHelper { constructor() {} function setUp() public override { super.setUp(); } - function testTransfer() public { - setUp(); - - assertEq(address(simpleWallet).balance, 1 ether); - assertEq(receiver.balance, 0 ether); - - vm.startPrank(deployer); - simpleWallet.transfer(receiver, 1 ether); - vm.stopPrank(); - - assertEq(address(simpleWallet).balance, 0 ether); - assertEq(receiver.balance, 1 ether); - } - - function testExpectRevertTransferOnlyOwner() public { - setUp(); - - assertEq(address(simpleWallet).balance, 1 ether); - assertEq(receiver.balance, 0 ether); - - vm.startPrank(receiver); - vm.expectRevert( - abi.encodeWithSelector( - OwnableUpgradeable.OwnableUnauthorizedAccount.selector, - receiver - ) - ); - simpleWallet.transfer(receiver, 1 ether); - vm.stopPrank(); - } - - function testExpectRevertTransferOnlyOwnerInsufficientBalance() public { - setUp(); - - assertEq(address(simpleWallet).balance, 1 ether); - assertEq(receiver.balance, 0 ether); - - vm.startPrank(deployer); - assertEq(receiver.balance, 0 ether); - vm.expectRevert(bytes("insufficient balance")); - simpleWallet.transfer(receiver, 2 ether); - vm.stopPrank(); - } - - function testWithdraw() public { - setUp(); - - assertEq(address(simpleWallet).balance, 1 ether); - assertEq(deployer.balance, 0 ether); - - vm.startPrank(deployer); - simpleWallet.withdraw(1 ether); - vm.stopPrank(); - - assertEq(address(simpleWallet).balance, 0 ether); - assertEq(deployer.balance, 1 ether); - } - - function testExpectRevertWithdrawOnlyOwner() public { - setUp(); - - assertEq(address(simpleWallet).balance, 1 ether); - assertEq(deployer.balance, 0 ether); - - vm.startPrank(receiver); - vm.expectRevert( - abi.encodeWithSelector( - OwnableUpgradeable.OwnableUnauthorizedAccount.selector, - address(receiver) - ) - ); - simpleWallet.withdraw(1 ether); - vm.stopPrank(); - } - - function testExpectRevertWithdrawInsufficientBalance() public { - setUp(); - - assertEq(address(simpleWallet).balance, 1 ether); - assertEq(deployer.balance, 0 ether); - - vm.startPrank(deployer); - vm.expectRevert(bytes("insufficient balance")); - simpleWallet.withdraw(10 ether); - vm.stopPrank(); - } - - function testAcceptanceCommandTemplates() public { - setUp(); - string[][] memory res = recoveryController.acceptanceCommandTemplates(); - assertEq(res[0][0], "Accept"); - assertEq(res[0][1], "guardian"); - assertEq(res[0][2], "request"); - assertEq(res[0][3], "for"); - assertEq(res[0][4], "{ethAddr}"); - } - - function testRecoveryCommandTemplates() public { - setUp(); - string[][] memory res = recoveryController.recoveryCommandTemplates(); - assertEq(res[0][0], "Set"); - assertEq(res[0][1], "the"); - assertEq(res[0][2], "new"); - assertEq(res[0][3], "signer"); - assertEq(res[0][4], "of"); - assertEq(res[0][5], "{ethAddr}"); - assertEq(res[0][6], "to"); - assertEq(res[0][7], "{ethAddr}"); - } - - function testRequestGuardian() public { + function requestGuardian() public { setUp(); require( recoveryController.guardians(guardian) == @@ -143,55 +33,10 @@ contract EmailAccountRecoveryTest is StructHelper { ); } - // function testRequestGuardianNotOwner() public { - // setUp(); - - // require( - // recoveryController.guardians(guardian) == - // recoveryController.GuardianStatus.NONE - // ); - - // vm.startPrank(receiver); - // recoveryController.requestGuardian(guardian); - // vm.stopPrank(); - - // require( - // recoveryController.guardians(guardian) == - // recoveryController.GuardianStatus.NONE - // ); - // } - - function testExpectRevertRequestGuardianInvalidGuardian() public { - setUp(); + function handleAcceptance() public { + skipIfZkSync(); - require( - recoveryController.guardians(guardian) == - RecoveryController.GuardianStatus.NONE - ); - - vm.startPrank(deployer); - vm.expectRevert(bytes("invalid guardian")); - recoveryController.requestGuardian(address(0x0)); - vm.stopPrank(); - } - - function testExpectRevertRequestGuardianGuardianStatusMustBeNone() public { - setUp(); - - require( - recoveryController.guardians(guardian) == - RecoveryController.GuardianStatus.NONE - ); - - vm.startPrank(deployer); - recoveryController.requestGuardian(guardian); - vm.expectRevert(bytes("guardian status must be NONE")); - recoveryController.requestGuardian(guardian); - vm.stopPrank(); - } - - function testHandleAcceptance() public { - testRequestGuardian(); + requestGuardian(); console.log("guardian", guardian); @@ -228,141 +73,10 @@ contract EmailAccountRecoveryTest is StructHelper { ); } - // Can not test recovery in progress using handleAcceptance - // Can not test invalid guardian using handleAcceptance - - function testExpectRevertHandleAcceptanceGuardianStatusMustBeRequested() - public - { - testRequestGuardian(); - - require( - recoveryController.guardians(guardian) == - RecoveryController.GuardianStatus.REQUESTED - ); - - uint templateIdx = 0; - - EmailAuthMsg memory emailAuthMsg = buildEmailAuthMsg(); - uint templateId = recoveryController.computeAcceptanceTemplateId( - templateIdx - ); - emailAuthMsg.templateId = templateId; - bytes[] memory commandParamsForAcceptance = new bytes[](1); - commandParamsForAcceptance[0] = abi.encode(address(simpleWallet)); - emailAuthMsg.commandParams = commandParamsForAcceptance; - emailAuthMsg.proof.accountSalt = 0x0; - - vm.mockCall( - address(recoveryController.emailAuthImplementationAddr()), - abi.encodeWithSelector(EmailAuth.authEmail.selector, emailAuthMsg), - abi.encode(0x0) - ); - - vm.startPrank(someRelayer); - vm.expectRevert(bytes("guardian status must be REQUESTED")); - recoveryController.handleAcceptance(emailAuthMsg, templateIdx); - vm.stopPrank(); - } - - function testExpectRevertHandleAcceptanceInvalidTemplateIndex() public { - testRequestGuardian(); - - require( - recoveryController.guardians(guardian) == - RecoveryController.GuardianStatus.REQUESTED - ); - - uint templateIdx = 1; - - EmailAuthMsg memory emailAuthMsg = buildEmailAuthMsg(); - uint templateId = recoveryController.computeAcceptanceTemplateId( - templateIdx - ); - emailAuthMsg.templateId = templateId; - bytes[] memory commandParamsForAcceptance = new bytes[](1); - commandParamsForAcceptance[0] = abi.encode(address(simpleWallet)); - emailAuthMsg.commandParams = commandParamsForAcceptance; - - vm.mockCall( - address(recoveryController.emailAuthImplementationAddr()), - abi.encodeWithSelector(EmailAuth.authEmail.selector, emailAuthMsg), - abi.encode(0x0) - ); - - vm.startPrank(someRelayer); - vm.expectRevert(bytes("invalid template index")); - recoveryController.handleAcceptance(emailAuthMsg, templateIdx); - vm.stopPrank(); - } - - function testExpectRevertHandleAcceptanceInvalidCommandParams() public { - testRequestGuardian(); - - require( - recoveryController.guardians(guardian) == - RecoveryController.GuardianStatus.REQUESTED - ); - - uint templateIdx = 0; - - EmailAuthMsg memory emailAuthMsg = buildEmailAuthMsg(); - uint templateId = recoveryController.computeAcceptanceTemplateId( - templateIdx - ); - emailAuthMsg.templateId = templateId; - bytes[] memory commandParamsForAcceptance = new bytes[](2); - commandParamsForAcceptance[0] = abi.encode(address(simpleWallet)); - commandParamsForAcceptance[1] = abi.encode(address(simpleWallet)); - emailAuthMsg.commandParams = commandParamsForAcceptance; - - vm.mockCall( - address(recoveryController.emailAuthImplementationAddr()), - abi.encodeWithSelector(EmailAuth.authEmail.selector, emailAuthMsg), - abi.encode(0x0) - ); - - vm.startPrank(someRelayer); - vm.expectRevert(bytes("invalid command params")); - recoveryController.handleAcceptance(emailAuthMsg, templateIdx); - vm.stopPrank(); - } - - function testExpectRevertHandleAcceptanceInvalidWalletAddressInEmail() - public - { - testRequestGuardian(); - - require( - recoveryController.guardians(guardian) == - RecoveryController.GuardianStatus.REQUESTED - ); - - uint templateIdx = 0; - - EmailAuthMsg memory emailAuthMsg = buildEmailAuthMsg(); - uint templateId = recoveryController.computeAcceptanceTemplateId( - templateIdx - ); - emailAuthMsg.templateId = templateId; - bytes[] memory commandParamsForAcceptance = new bytes[](1); - commandParamsForAcceptance[0] = abi.encode(address(0x0)); - emailAuthMsg.commandParams = commandParamsForAcceptance; - - vm.mockCall( - address(recoveryController.emailAuthImplementationAddr()), - abi.encodeWithSelector(EmailAuth.authEmail.selector, emailAuthMsg), - abi.encode(0x0) - ); - - vm.startPrank(someRelayer); - vm.expectRevert(bytes("invalid account in email")); - recoveryController.handleAcceptance(emailAuthMsg, templateIdx); - vm.stopPrank(); - } - function testHandleRecovery() public { - testHandleAcceptance(); + skipIfZkSync(); + + handleAcceptance(); assertEq(recoveryController.isRecovering(address(simpleWallet)), false); assertEq( @@ -417,7 +131,9 @@ contract EmailAccountRecoveryTest is StructHelper { } function testExpectRevertHandleRecoveryGuardianIsNotDeployed() public { - testHandleAcceptance(); + skipIfZkSync(); + + handleAcceptance(); assertEq(recoveryController.isRecovering(address(simpleWallet)), false); assertEq( @@ -458,7 +174,9 @@ contract EmailAccountRecoveryTest is StructHelper { } function testExpectRevertHandleRecoveryInvalidTemplateId() public { - testHandleAcceptance(); + skipIfZkSync(); + + handleAcceptance(); assertEq(recoveryController.isRecovering(address(simpleWallet)), false); assertEq( @@ -499,7 +217,9 @@ contract EmailAccountRecoveryTest is StructHelper { function testExpectRevertHandleRecoveryGuardianStatusMustBeAccepted() public { - testHandleAcceptance(); + skipIfZkSync(); + + handleAcceptance(); assertEq(recoveryController.isRecovering(address(simpleWallet)), false); assertEq( @@ -553,7 +273,9 @@ contract EmailAccountRecoveryTest is StructHelper { } function testExpectRevertHandleRecoveryInvalidTemplateIndex() public { - testHandleAcceptance(); + skipIfZkSync(); + + handleAcceptance(); assertEq(recoveryController.isRecovering(address(simpleWallet)), false); assertEq( @@ -592,7 +314,9 @@ contract EmailAccountRecoveryTest is StructHelper { } function testExpectRevertHandleRecoveryInvalidCommandParams() public { - testHandleAcceptance(); + skipIfZkSync(); + + handleAcceptance(); assertEq(recoveryController.isRecovering(address(simpleWallet)), false); assertEq( @@ -632,7 +356,7 @@ contract EmailAccountRecoveryTest is StructHelper { } // function testExpectRevertHandleRecoveryInvalidGuardianInEmail() public { - // testHandleAcceptance(); + // handleAcceptance(); // assertEq(recoveryController.isRecovering(address(simpleWallet)), false); // assertEq( @@ -667,7 +391,9 @@ contract EmailAccountRecoveryTest is StructHelper { // } function testExpectRevertHandleRecoveryInvalidNewSigner() public { - testHandleAcceptance(); + skipIfZkSync(); + + handleAcceptance(); assertEq(recoveryController.isRecovering(address(simpleWallet)), false); assertEq( @@ -704,70 +430,4 @@ contract EmailAccountRecoveryTest is StructHelper { recoveryController.handleRecovery(emailAuthMsg, templateIdx); vm.stopPrank(); } - - function testExpectRevertRejectRecoveryOwnableUnauthorizedAccount() public { - testHandleRecovery(); - - assertEq(recoveryController.isRecovering(address(simpleWallet)), true); - assertEq( - recoveryController.currentTimelockOfAccount(address(simpleWallet)), - block.timestamp + - recoveryController.timelockPeriodOfAccount( - address(simpleWallet) - ) - ); - assertEq(simpleWallet.owner(), deployer); - assertEq( - recoveryController.newSignerCandidateOfAccount( - address(simpleWallet) - ), - newSigner - ); - - vm.startPrank(deployer); - vm.expectRevert("recovery not in progress"); - recoveryController.rejectRecovery(); - vm.stopPrank(); - } - - function testCompleteRecovery() public { - testHandleRecovery(); - - assertEq(recoveryController.isRecovering(address(simpleWallet)), true); - assertEq( - recoveryController.currentTimelockOfAccount(address(simpleWallet)), - block.timestamp + - recoveryController.timelockPeriodOfAccount( - address(simpleWallet) - ) - ); - assertEq(simpleWallet.owner(), deployer); - assertEq( - recoveryController.newSignerCandidateOfAccount( - address(simpleWallet) - ), - newSigner - ); - - vm.startPrank(someRelayer); - vm.warp(4 days); - recoveryController.completeRecovery( - address(simpleWallet), - new bytes(0) - ); - vm.stopPrank(); - - assertEq(recoveryController.isRecovering(address(simpleWallet)), false); - assertEq( - recoveryController.currentTimelockOfAccount(address(simpleWallet)), - 0 - ); - assertEq(simpleWallet.owner(), newSigner); - assertEq( - recoveryController.newSignerCandidateOfAccount( - address(simpleWallet) - ), - address(0x0) - ); - } } diff --git a/packages/contracts/test/EmailAccountRecovery/EmailAccountRecovery_recoverySubjectTemplates.t.sol b/packages/contracts/test/EmailAccountRecovery/EmailAccountRecovery_recoverySubjectTemplates.t.sol new file mode 100644 index 00000000..db7a925c --- /dev/null +++ b/packages/contracts/test/EmailAccountRecovery/EmailAccountRecovery_recoverySubjectTemplates.t.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.12; + +import "forge-std/Test.sol"; +import "forge-std/console.sol"; +import {EmailAuth, EmailAuthMsg} from "../../src/EmailAuth.sol"; +import {RecoveryController} from "../helpers/RecoveryController.sol"; +import {StructHelper} from "../helpers/StructHelper.sol"; +import {SimpleWallet} from "../helpers/SimpleWallet.sol"; +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; + +contract EmailAccountRecoveryTest_recoveryCommandTemplates is StructHelper { + constructor() {} + + function setUp() public override { + super.setUp(); + } + + function testRecoveryCommandTemplates() public { + skipIfZkSync(); + + setUp(); + string[][] memory res = recoveryController.recoveryCommandTemplates(); + assertEq(res[0][0], "Set"); + assertEq(res[0][1], "the"); + assertEq(res[0][2], "new"); + assertEq(res[0][3], "signer"); + assertEq(res[0][4], "of"); + assertEq(res[0][5], "{ethAddr}"); + assertEq(res[0][6], "to"); + assertEq(res[0][7], "{ethAddr}"); + } +} diff --git a/packages/contracts/test/EmailAccountRecoveryForRejectRecovery.t.sol b/packages/contracts/test/EmailAccountRecovery/EmailAccountRecovery_rejectRecovery.t.sol similarity index 63% rename from packages/contracts/test/EmailAccountRecoveryForRejectRecovery.t.sol rename to packages/contracts/test/EmailAccountRecovery/EmailAccountRecovery_rejectRecovery.t.sol index d534a325..d11b52c3 100644 --- a/packages/contracts/test/EmailAccountRecoveryForRejectRecovery.t.sol +++ b/packages/contracts/test/EmailAccountRecovery/EmailAccountRecovery_rejectRecovery.t.sol @@ -3,13 +3,15 @@ pragma solidity ^0.8.12; import "forge-std/Test.sol"; import "forge-std/console.sol"; -import {EmailAuthMsg} from "../src/EmailAuth.sol"; -import "./helpers/StructHelper.sol"; -import "./helpers/SimpleWallet.sol"; - +import {EmailAuth, EmailAuthMsg} from "../../src/EmailAuth.sol"; +import {RecoveryController} from "../helpers/RecoveryController.sol"; +import {StructHelper} from "../helpers/StructHelper.sol"; +import {SimpleWallet} from "../helpers/SimpleWallet.sol"; import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; -contract EmailAccountRecoveryForRejectRecoveryTest is StructHelper { +contract EmailAccountRecoveryForRejectRecoveryTest_rejectRecovery is + StructHelper +{ constructor() {} function setUp() public override { @@ -140,6 +142,8 @@ contract EmailAccountRecoveryForRejectRecoveryTest is StructHelper { } function testRejectRecovery() public { + skipIfZkSync(); + vm.warp(block.timestamp + 3 days); handleRecovery(); @@ -181,6 +185,8 @@ contract EmailAccountRecoveryForRejectRecoveryTest is StructHelper { } function testExpectRevertRejectRecoveryRecoveryNotInProgress() public { + skipIfZkSync(); + handleAcceptance(); assertEq(recoveryController.isRecovering(address(simpleWallet)), false); @@ -203,6 +209,8 @@ contract EmailAccountRecoveryForRejectRecoveryTest is StructHelper { } function testExpectRevertRejectRecovery() public { + skipIfZkSync(); + vm.warp(block.timestamp + 1 days); handleRecovery(); @@ -230,150 +238,9 @@ contract EmailAccountRecoveryForRejectRecoveryTest is StructHelper { vm.stopPrank(); } - function testCompleteRecovery() public { - handleRecovery(); - - assertEq(recoveryController.isRecovering(address(simpleWallet)), true); - assertEq( - recoveryController.currentTimelockOfAccount(address(simpleWallet)), - block.timestamp + - recoveryController.timelockPeriodOfAccount( - address(simpleWallet) - ) - ); - assertEq(simpleWallet.owner(), deployer); - assertEq( - recoveryController.newSignerCandidateOfAccount( - address(simpleWallet) - ), - newSigner - ); - - vm.startPrank(someRelayer); - vm.warp(4 days); - bytes memory recoveryCalldata; - recoveryController.completeRecovery( - address(simpleWallet), - recoveryCalldata - ); - vm.stopPrank(); - - assertEq(recoveryController.isRecovering(address(simpleWallet)), false); - assertEq( - recoveryController.currentTimelockOfAccount(address(simpleWallet)), - 0 - ); - assertEq(simpleWallet.owner(), newSigner); - assertEq( - recoveryController.newSignerCandidateOfAccount( - address(simpleWallet) - ), - address(0x0) - ); - } - - function testExpectRevertCompleteRecoveryRecoveryNotInProgress() public { - handleAcceptance(); - - assertEq(recoveryController.isRecovering(address(simpleWallet)), false); - assertEq( - recoveryController.currentTimelockOfAccount(address(simpleWallet)), - 0 - ); - assertEq(simpleWallet.owner(), deployer); - assertEq( - recoveryController.newSignerCandidateOfAccount( - address(simpleWallet) - ), - address(0x0) - ); - - vm.startPrank(someRelayer); - vm.warp(4 days); - vm.expectRevert(bytes("recovery not in progress")); - bytes memory recoveryCalldata; - recoveryController.completeRecovery( - address(simpleWallet), - recoveryCalldata - ); - - vm.stopPrank(); - } - - function testExpectRevertCompleteRecovery() public { - vm.warp(block.timestamp + 3 days); - - handleRecovery(); - - assertEq(recoveryController.isRecovering(address(simpleWallet)), true); - assertEq( - recoveryController.currentTimelockOfAccount(address(simpleWallet)), - block.timestamp + - recoveryController.timelockPeriodOfAccount( - address(simpleWallet) - ) - ); - assertEq(simpleWallet.owner(), deployer); - assertEq( - recoveryController.newSignerCandidateOfAccount( - address(simpleWallet) - ), - newSigner - ); - - vm.warp(0); - - vm.startPrank(someRelayer); - vm.expectRevert(bytes("timelock not expired")); - bytes memory recoveryCalldata; - recoveryController.completeRecovery( - address(simpleWallet), - recoveryCalldata - ); - - vm.stopPrank(); - } - - function testExpectRevertHandleRecoveryInvalidNewSigner() public { - handleAcceptance(); - - assertEq(recoveryController.isRecovering(address(simpleWallet)), false); - assertEq( - recoveryController.currentTimelockOfAccount(address(simpleWallet)), - 0 - ); - assertEq(simpleWallet.owner(), deployer); - assertEq( - recoveryController.newSignerCandidateOfAccount( - address(simpleWallet) - ), - address(0x0) - ); - uint templateIdx = 0; - - EmailAuthMsg memory emailAuthMsg = buildEmailAuthMsg(); - uint templateId = recoveryController.computeRecoveryTemplateId( - templateIdx - ); - emailAuthMsg.templateId = templateId; - bytes[] memory commandParamsForRecovery = new bytes[](2); - commandParamsForRecovery[0] = abi.encode(simpleWallet); - commandParamsForRecovery[1] = abi.encode(address(0x0)); - emailAuthMsg.commandParams = commandParamsForRecovery; - - vm.mockCall( - address(recoveryController.emailAuthImplementationAddr()), - abi.encodeWithSelector(EmailAuth.authEmail.selector, emailAuthMsg), - abi.encode(0x0) - ); - - vm.startPrank(someRelayer); - vm.expectRevert(bytes("invalid new signer")); - recoveryController.handleRecovery(emailAuthMsg, templateIdx); - vm.stopPrank(); - } - function testExpectRevertRejectRecoveryOwnableUnauthorizedAccount() public { + skipIfZkSync(); + handleRecovery(); assertEq(recoveryController.isRecovering(address(simpleWallet)), true); diff --git a/packages/contracts/test/EmailAccountRecovery/EmailAccountRecovery_requestGuardian.t.sol b/packages/contracts/test/EmailAccountRecovery/EmailAccountRecovery_requestGuardian.t.sol new file mode 100644 index 00000000..e03bdaf3 --- /dev/null +++ b/packages/contracts/test/EmailAccountRecovery/EmailAccountRecovery_requestGuardian.t.sol @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.12; + +import "forge-std/Test.sol"; +import "forge-std/console.sol"; +import {EmailAuth, EmailAuthMsg} from "../../src/EmailAuth.sol"; +import {RecoveryController} from "../helpers/RecoveryController.sol"; +import {StructHelper} from "../helpers/StructHelper.sol"; +import {SimpleWallet} from "../helpers/SimpleWallet.sol"; +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; + +contract EmailAccountRecoveryTest_requestGuardian is StructHelper { + constructor() {} + + function setUp() public override { + super.setUp(); + } + + function testRequestGuardian() public { + skipIfZkSync(); + + setUp(); + require( + recoveryController.guardians(guardian) == + RecoveryController.GuardianStatus.NONE + ); + + vm.startPrank(deployer); + recoveryController.requestGuardian(guardian); + vm.stopPrank(); + + require( + recoveryController.guardians(guardian) == + RecoveryController.GuardianStatus.REQUESTED + ); + } + + // function testRequestGuardianNotOwner() public { + // setUp(); + + // require( + // recoveryController.guardians(guardian) == + // recoveryController.GuardianStatus.NONE + // ); + + // vm.startPrank(receiver); + // recoveryController.requestGuardian(guardian); + // vm.stopPrank(); + + // require( + // recoveryController.guardians(guardian) == + // recoveryController.GuardianStatus.NONE + // ); + // } + + function testExpectRevertRequestGuardianInvalidGuardian() public { + skipIfZkSync(); + + setUp(); + + require( + recoveryController.guardians(guardian) == + RecoveryController.GuardianStatus.NONE + ); + + vm.startPrank(deployer); + vm.expectRevert(bytes("invalid guardian")); + recoveryController.requestGuardian(address(0x0)); + vm.stopPrank(); + } + + function testExpectRevertRequestGuardianGuardianStatusMustBeNone() public { + skipIfZkSync(); + + setUp(); + + require( + recoveryController.guardians(guardian) == + RecoveryController.GuardianStatus.NONE + ); + + vm.startPrank(deployer); + recoveryController.requestGuardian(guardian); + vm.expectRevert(bytes("guardian status must be NONE")); + recoveryController.requestGuardian(guardian); + vm.stopPrank(); + } +} diff --git a/packages/contracts/test/EmailAccountRecovery/EmailAccountRecovery_transfer.t.sol b/packages/contracts/test/EmailAccountRecovery/EmailAccountRecovery_transfer.t.sol new file mode 100644 index 00000000..460700a3 --- /dev/null +++ b/packages/contracts/test/EmailAccountRecovery/EmailAccountRecovery_transfer.t.sol @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.12; + +import "forge-std/Test.sol"; +import "forge-std/console.sol"; +import {EmailAuth, EmailAuthMsg} from "../../src/EmailAuth.sol"; +import {RecoveryController} from "../helpers/RecoveryController.sol"; +import {StructHelper} from "../helpers/StructHelper.sol"; +import {SimpleWallet} from "../helpers/SimpleWallet.sol"; +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; + +contract EmailAccountRecoveryTest_transfer is StructHelper { + constructor() {} + + function setUp() public override { + super.setUp(); + } + + function testTransfer() public { + skipIfZkSync(); + + setUp(); + + assertEq(address(simpleWallet).balance, 1 ether); + assertEq(receiver.balance, 0 ether); + + vm.startPrank(deployer); + simpleWallet.transfer(receiver, 1 ether); + vm.stopPrank(); + + assertEq(address(simpleWallet).balance, 0 ether); + assertEq(receiver.balance, 1 ether); + } + + function testExpectRevertTransferOnlyOwner() public { + skipIfZkSync(); + + setUp(); + + assertEq(address(simpleWallet).balance, 1 ether); + assertEq(receiver.balance, 0 ether); + + vm.startPrank(receiver); + vm.expectRevert( + abi.encodeWithSelector( + OwnableUpgradeable.OwnableUnauthorizedAccount.selector, + receiver + ) + ); + simpleWallet.transfer(receiver, 1 ether); + vm.stopPrank(); + } + + function testExpectRevertTransferOnlyOwnerInsufficientBalance() public { + skipIfZkSync(); + + setUp(); + + assertEq(address(simpleWallet).balance, 1 ether); + assertEq(receiver.balance, 0 ether); + + vm.startPrank(deployer); + assertEq(receiver.balance, 0 ether); + vm.expectRevert(bytes("insufficient balance")); + simpleWallet.transfer(receiver, 2 ether); + vm.stopPrank(); + } +} diff --git a/packages/contracts/test/EmailAccountRecovery/EmailAccountRecovery_withdraw.t.sol b/packages/contracts/test/EmailAccountRecovery/EmailAccountRecovery_withdraw.t.sol new file mode 100644 index 00000000..4eefa103 --- /dev/null +++ b/packages/contracts/test/EmailAccountRecovery/EmailAccountRecovery_withdraw.t.sol @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.12; + +import "forge-std/Test.sol"; +import "forge-std/console.sol"; +import {EmailAuth, EmailAuthMsg} from "../../src/EmailAuth.sol"; +import {RecoveryController} from "../helpers/RecoveryController.sol"; +import {StructHelper} from "../helpers/StructHelper.sol"; +import {SimpleWallet} from "../helpers/SimpleWallet.sol"; +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; + +contract EmailAccountRecoveryTest_withdraw is StructHelper { + constructor() {} + + function setUp() public override { + super.setUp(); + } + + function testWithdraw() public { + skipIfZkSync(); + + setUp(); + + assertEq(address(simpleWallet).balance, 1 ether); + assertEq(deployer.balance, 0 ether); + + vm.startPrank(deployer); + simpleWallet.withdraw(1 ether); + vm.stopPrank(); + + assertEq(address(simpleWallet).balance, 0 ether); + assertEq(deployer.balance, 1 ether); + } + + function testExpectRevertWithdrawOnlyOwner() public { + skipIfZkSync(); + + setUp(); + + assertEq(address(simpleWallet).balance, 1 ether); + assertEq(deployer.balance, 0 ether); + + vm.startPrank(receiver); + vm.expectRevert( + abi.encodeWithSelector( + OwnableUpgradeable.OwnableUnauthorizedAccount.selector, + address(receiver) + ) + ); + simpleWallet.withdraw(1 ether); + vm.stopPrank(); + } + + function testExpectRevertWithdrawInsufficientBalance() public { + skipIfZkSync(); + + setUp(); + + assertEq(address(simpleWallet).balance, 1 ether); + assertEq(deployer.balance, 0 ether); + + vm.startPrank(deployer); + vm.expectRevert(bytes("insufficient balance")); + simpleWallet.withdraw(10 ether); + vm.stopPrank(); + } +} diff --git a/packages/contracts/test/EmailAccountRecoveryZkSync/EmailAccountRecoveryZKSync_completeRecovery.t.sol b/packages/contracts/test/EmailAccountRecoveryZkSync/EmailAccountRecoveryZKSync_completeRecovery.t.sol new file mode 100644 index 00000000..5d006eae --- /dev/null +++ b/packages/contracts/test/EmailAccountRecoveryZkSync/EmailAccountRecoveryZKSync_completeRecovery.t.sol @@ -0,0 +1,265 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.12; + +import "forge-std/Test.sol"; +import "forge-std/console.sol"; +import {EmailAuth, EmailAuthMsg} from "../../src/EmailAuth.sol"; +import {RecoveryControllerZKSync} from "../helpers/RecoveryControllerZKSync.sol"; +import {StructHelper} from "../helpers/StructHelper.sol"; +import {SimpleWallet} from "../helpers/SimpleWallet.sol"; +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; + +contract EmailAccountRecoveryZKSyncTest_completeRecovery is StructHelper { + constructor() {} + + function setUp() public override { + super.setUp(); + } + + function requestGuardian() public { + setUp(); + require( + recoveryControllerZKSync.guardians(guardian) == + RecoveryControllerZKSync.GuardianStatus.NONE + ); + + vm.startPrank(deployer); + recoveryControllerZKSync.requestGuardian(guardian); + vm.stopPrank(); + + require( + recoveryControllerZKSync.guardians(guardian) == + RecoveryControllerZKSync.GuardianStatus.REQUESTED + ); + } + + function handleAcceptance() public { + requestGuardian(); + + console.log("guardian", guardian); + + require( + recoveryControllerZKSync.guardians(guardian) == + RecoveryControllerZKSync.GuardianStatus.REQUESTED + ); + + uint templateIdx = 0; + + EmailAuthMsg memory emailAuthMsg = buildEmailAuthMsg(); + uint templateId = recoveryControllerZKSync.computeAcceptanceTemplateId( + templateIdx + ); + emailAuthMsg.templateId = templateId; + bytes[] memory subjectParamsForAcceptance = new bytes[](1); + subjectParamsForAcceptance[0] = abi.encode(address(simpleWallet)); + emailAuthMsg.subjectParams = subjectParamsForAcceptance; + + vm.mockCall( + address(recoveryControllerZKSync.emailAuthImplementationAddr()), + abi.encodeWithSelector(EmailAuth.authEmail.selector, emailAuthMsg), + abi.encode(0x0) + ); + + // acceptGuardian is internal, we call handleAcceptance, which calls acceptGuardian internally. + vm.startPrank(someRelayer); + recoveryControllerZKSync.handleAcceptance(emailAuthMsg, templateIdx); + vm.stopPrank(); + + require( + recoveryControllerZKSync.guardians(guardian) == + RecoveryControllerZKSync.GuardianStatus.ACCEPTED + ); + } + + function handleRecovery() public { + handleAcceptance(); + + assertEq( + recoveryControllerZKSync.isRecovering(address(simpleWallet)), + false + ); + assertEq( + recoveryControllerZKSync.currentTimelockOfAccount( + address(simpleWallet) + ), + 0 + ); + assertEq(simpleWallet.owner(), deployer); + assertEq( + recoveryControllerZKSync.newSignerCandidateOfAccount( + address(simpleWallet) + ), + address(0x0) + ); + + uint templateIdx = 0; + + EmailAuthMsg memory emailAuthMsg = buildEmailAuthMsg(); + uint templateId = recoveryControllerZKSync.computeRecoveryTemplateId( + templateIdx + ); + emailAuthMsg.templateId = templateId; + bytes[] memory subjectParamsForRecovery = new bytes[](2); + subjectParamsForRecovery[0] = abi.encode(simpleWallet); + subjectParamsForRecovery[1] = abi.encode(newSigner); + emailAuthMsg.subjectParams = subjectParamsForRecovery; + + vm.mockCall( + address(recoveryControllerZKSync.emailAuthImplementationAddr()), + abi.encodeWithSelector(EmailAuth.authEmail.selector, emailAuthMsg), + abi.encode(0x0) + ); + + vm.startPrank(someRelayer); + recoveryControllerZKSync.handleRecovery(emailAuthMsg, templateIdx); + vm.stopPrank(); + + assertEq( + recoveryControllerZKSync.isRecovering(address(simpleWallet)), + true + ); + assertEq(simpleWallet.owner(), deployer); + assertEq( + recoveryControllerZKSync.newSignerCandidateOfAccount( + address(simpleWallet) + ), + newSigner + ); + assertEq( + recoveryControllerZKSync.currentTimelockOfAccount( + address(simpleWallet) + ), + block.timestamp + + recoveryControllerZKSync.timelockPeriodOfAccount( + address(simpleWallet) + ) + ); + } + + function testCompleteRecovery() public { + skipIfNotZkSync(); + + handleRecovery(); + + assertEq( + recoveryControllerZKSync.isRecovering(address(simpleWallet)), + true + ); + assertEq( + recoveryControllerZKSync.currentTimelockOfAccount( + address(simpleWallet) + ), + block.timestamp + + recoveryControllerZKSync.timelockPeriodOfAccount( + address(simpleWallet) + ) + ); + assertEq(simpleWallet.owner(), deployer); + assertEq( + recoveryControllerZKSync.newSignerCandidateOfAccount( + address(simpleWallet) + ), + newSigner + ); + + vm.startPrank(someRelayer); + vm.warp(4 days); + recoveryControllerZKSync.completeRecovery( + address(simpleWallet), + new bytes(0) + ); + vm.stopPrank(); + + assertEq( + recoveryControllerZKSync.isRecovering(address(simpleWallet)), + false + ); + assertEq( + recoveryControllerZKSync.currentTimelockOfAccount( + address(simpleWallet) + ), + 0 + ); + assertEq(simpleWallet.owner(), newSigner); + assertEq( + recoveryControllerZKSync.newSignerCandidateOfAccount( + address(simpleWallet) + ), + address(0x0) + ); + } + + function testExpectRevertCompleteRecoveryRecoveryNotInProgress() public { + skipIfNotZkSync(); + + handleAcceptance(); + + assertEq( + recoveryControllerZKSync.isRecovering(address(simpleWallet)), + false + ); + assertEq( + recoveryControllerZKSync.currentTimelockOfAccount( + address(simpleWallet) + ), + 0 + ); + assertEq(simpleWallet.owner(), deployer); + assertEq( + recoveryControllerZKSync.newSignerCandidateOfAccount( + address(simpleWallet) + ), + address(0x0) + ); + + vm.startPrank(someRelayer); + vm.warp(4 days); + vm.expectRevert(bytes("recovery not in progress")); + bytes memory recoveryCalldata; + recoveryControllerZKSync.completeRecovery( + address(simpleWallet), + recoveryCalldata + ); + + vm.stopPrank(); + } + + function testExpectRevertCompleteRecovery() public { + vm.warp(block.timestamp + 3 days); + + handleRecovery(); + + assertEq( + recoveryControllerZKSync.isRecovering(address(simpleWallet)), + true + ); + assertEq( + recoveryControllerZKSync.currentTimelockOfAccount( + address(simpleWallet) + ), + block.timestamp + + recoveryControllerZKSync.timelockPeriodOfAccount( + address(simpleWallet) + ) + ); + assertEq(simpleWallet.owner(), deployer); + assertEq( + recoveryControllerZKSync.newSignerCandidateOfAccount( + address(simpleWallet) + ), + newSigner + ); + + vm.warp(0); + + vm.startPrank(someRelayer); + vm.expectRevert(bytes("timelock not expired")); + bytes memory recoveryCalldata; + recoveryControllerZKSync.completeRecovery( + address(simpleWallet), + recoveryCalldata + ); + + vm.stopPrank(); + } +} diff --git a/packages/contracts/test/EmailAccountRecoveryZkSync/EmailAccountRecoveryZKSync_handleAcceptance.t.sol b/packages/contracts/test/EmailAccountRecoveryZkSync/EmailAccountRecoveryZKSync_handleAcceptance.t.sol new file mode 100644 index 00000000..9db78895 --- /dev/null +++ b/packages/contracts/test/EmailAccountRecoveryZkSync/EmailAccountRecoveryZKSync_handleAcceptance.t.sol @@ -0,0 +1,218 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.12; + +import "forge-std/Test.sol"; +import "forge-std/console.sol"; +import {EmailAuth, EmailAuthMsg} from "../../src/EmailAuth.sol"; +import {RecoveryControllerZKSync} from "../helpers/RecoveryControllerZKSync.sol"; +import {StructHelper} from "../helpers/StructHelper.sol"; +import {SimpleWallet} from "../helpers/SimpleWallet.sol"; +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; + +contract EmailAccountRecoveryZKSyncTest_handleAcceptance is StructHelper { + constructor() {} + + function setUp() public override { + super.setUp(); + } + + function requestGuardian() public { + skipIfNotZkSync(); + + setUp(); + require( + recoveryControllerZKSync.guardians(guardian) == + RecoveryControllerZKSync.GuardianStatus.NONE + ); + + vm.startPrank(deployer); + recoveryControllerZKSync.requestGuardian(guardian); + vm.stopPrank(); + + require( + recoveryControllerZKSync.guardians(guardian) == + RecoveryControllerZKSync.GuardianStatus.REQUESTED + ); + } + + function testHandleAcceptance() public { + skipIfNotZkSync(); + + requestGuardian(); + + console.log("guardian", guardian); + + require( + recoveryControllerZKSync.guardians(guardian) == + RecoveryControllerZKSync.GuardianStatus.REQUESTED + ); + + uint templateIdx = 0; + + EmailAuthMsg memory emailAuthMsg = buildEmailAuthMsg(); + uint templateId = recoveryControllerZKSync.computeAcceptanceTemplateId( + templateIdx + ); + emailAuthMsg.templateId = templateId; + bytes[] memory subjectParamsForAcceptance = new bytes[](1); + subjectParamsForAcceptance[0] = abi.encode(address(simpleWallet)); + emailAuthMsg.subjectParams = subjectParamsForAcceptance; + + vm.mockCall( + address(recoveryControllerZKSync.emailAuthImplementationAddr()), + abi.encodeWithSelector(EmailAuth.authEmail.selector, emailAuthMsg), + abi.encode(0x0) + ); + + // acceptGuardian is internal, we call handleAcceptance, which calls acceptGuardian internally. + vm.startPrank(someRelayer); + recoveryControllerZKSync.handleAcceptance(emailAuthMsg, templateIdx); + vm.stopPrank(); + + require( + recoveryControllerZKSync.guardians(guardian) == + RecoveryControllerZKSync.GuardianStatus.ACCEPTED + ); + } + + // Can not test recovery in progress using handleAcceptance + // Can not test invalid guardian using handleAcceptance + + function testExpectRevertHandleAcceptanceGuardianStatusMustBeRequested() + public + { + skipIfNotZkSync(); + + requestGuardian(); + + require( + recoveryControllerZKSync.guardians(guardian) == + RecoveryControllerZKSync.GuardianStatus.REQUESTED + ); + + uint templateIdx = 0; + + EmailAuthMsg memory emailAuthMsg = buildEmailAuthMsg(); + uint templateId = recoveryControllerZKSync.computeAcceptanceTemplateId( + templateIdx + ); + emailAuthMsg.templateId = templateId; + bytes[] memory subjectParamsForAcceptance = new bytes[](1); + subjectParamsForAcceptance[0] = abi.encode(address(simpleWallet)); + emailAuthMsg.subjectParams = subjectParamsForAcceptance; + emailAuthMsg.proof.accountSalt = 0x0; + + vm.mockCall( + address(recoveryControllerZKSync.emailAuthImplementationAddr()), + abi.encodeWithSelector(EmailAuth.authEmail.selector, emailAuthMsg), + abi.encode(0x0) + ); + + vm.startPrank(someRelayer); + vm.expectRevert(bytes("guardian status must be REQUESTED")); + recoveryControllerZKSync.handleAcceptance(emailAuthMsg, templateIdx); + vm.stopPrank(); + } + + function testExpectRevertHandleAcceptanceInvalidTemplateIndex() public { + skipIfNotZkSync(); + + requestGuardian(); + + require( + recoveryControllerZKSync.guardians(guardian) == + RecoveryControllerZKSync.GuardianStatus.REQUESTED + ); + + uint templateIdx = 1; + + EmailAuthMsg memory emailAuthMsg = buildEmailAuthMsg(); + uint templateId = recoveryControllerZKSync.computeAcceptanceTemplateId( + templateIdx + ); + emailAuthMsg.templateId = templateId; + bytes[] memory subjectParamsForAcceptance = new bytes[](1); + subjectParamsForAcceptance[0] = abi.encode(address(simpleWallet)); + emailAuthMsg.subjectParams = subjectParamsForAcceptance; + + vm.mockCall( + address(recoveryControllerZKSync.emailAuthImplementationAddr()), + abi.encodeWithSelector(EmailAuth.authEmail.selector, emailAuthMsg), + abi.encode(0x0) + ); + + vm.startPrank(someRelayer); + vm.expectRevert(bytes("invalid template index")); + recoveryControllerZKSync.handleAcceptance(emailAuthMsg, templateIdx); + vm.stopPrank(); + } + + function testExpectRevertHandleAcceptanceInvalidSubjectParams() public { + skipIfNotZkSync(); + + requestGuardian(); + + require( + recoveryControllerZKSync.guardians(guardian) == + RecoveryControllerZKSync.GuardianStatus.REQUESTED + ); + + uint templateIdx = 0; + + EmailAuthMsg memory emailAuthMsg = buildEmailAuthMsg(); + uint templateId = recoveryControllerZKSync.computeAcceptanceTemplateId( + templateIdx + ); + emailAuthMsg.templateId = templateId; + bytes[] memory subjectParamsForAcceptance = new bytes[](2); + subjectParamsForAcceptance[0] = abi.encode(address(simpleWallet)); + subjectParamsForAcceptance[1] = abi.encode(address(simpleWallet)); + emailAuthMsg.subjectParams = subjectParamsForAcceptance; + + vm.mockCall( + address(recoveryControllerZKSync.emailAuthImplementationAddr()), + abi.encodeWithSelector(EmailAuth.authEmail.selector, emailAuthMsg), + abi.encode(0x0) + ); + + vm.startPrank(someRelayer); + vm.expectRevert(bytes("invalid subject params")); + recoveryControllerZKSync.handleAcceptance(emailAuthMsg, templateIdx); + vm.stopPrank(); + } + + function testExpectRevertHandleAcceptanceInvalidWalletAddressInEmail() + public + { + skipIfNotZkSync(); + + requestGuardian(); + + require( + recoveryControllerZKSync.guardians(guardian) == + RecoveryControllerZKSync.GuardianStatus.REQUESTED + ); + + uint templateIdx = 0; + + EmailAuthMsg memory emailAuthMsg = buildEmailAuthMsg(); + uint templateId = recoveryControllerZKSync.computeAcceptanceTemplateId( + templateIdx + ); + emailAuthMsg.templateId = templateId; + bytes[] memory subjectParamsForAcceptance = new bytes[](1); + subjectParamsForAcceptance[0] = abi.encode(address(0x0)); + emailAuthMsg.subjectParams = subjectParamsForAcceptance; + + vm.mockCall( + address(recoveryControllerZKSync.emailAuthImplementationAddr()), + abi.encodeWithSelector(EmailAuth.authEmail.selector, emailAuthMsg), + abi.encode(0x0) + ); + + vm.startPrank(someRelayer); + vm.expectRevert(bytes("invalid account in email")); + recoveryControllerZKSync.handleAcceptance(emailAuthMsg, templateIdx); + vm.stopPrank(); + } +} diff --git a/packages/contracts/test/EmailAccountRecoveryZkSync/EmailAccountRecoveryZKSync_handleRecovery.t.sol b/packages/contracts/test/EmailAccountRecoveryZkSync/EmailAccountRecoveryZKSync_handleRecovery.t.sol new file mode 100644 index 00000000..3a55ec9c --- /dev/null +++ b/packages/contracts/test/EmailAccountRecoveryZkSync/EmailAccountRecoveryZKSync_handleRecovery.t.sol @@ -0,0 +1,433 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.12; + +import "forge-std/Test.sol"; +import "forge-std/console.sol"; +import {EmailAuth, EmailAuthMsg} from "../../src/EmailAuth.sol"; +import {RecoveryControllerZKSync} from "../helpers/RecoveryControllerZKSync.sol"; +import {StructHelper} from "../helpers/StructHelper.sol"; +import {SimpleWallet} from "../helpers/SimpleWallet.sol"; +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; + +contract EmailAccountRecoveryZKSyncTest_handleRecovery is StructHelper { + constructor() {} + + function setUp() public override { + super.setUp(); + } + + function requestGuardian() public { + setUp(); + require( + recoveryControllerZKSync.guardians(guardian) == + RecoveryControllerZKSync.GuardianStatus.NONE + ); + + vm.startPrank(deployer); + recoveryControllerZKSync.requestGuardian(guardian); + vm.stopPrank(); + + require( + recoveryControllerZKSync.guardians(guardian) == + RecoveryControllerZKSync.GuardianStatus.REQUESTED + ); + } + + function handleAcceptance() public { + skipIfNotZkSync(); + + requestGuardian(); + + console.log("guardian", guardian); + + require( + recoveryControllerZKSync.guardians(guardian) == + RecoveryControllerZKSync.GuardianStatus.REQUESTED + ); + + uint templateIdx = 0; + + EmailAuthMsg memory emailAuthMsg = buildEmailAuthMsg(); + uint templateId = recoveryControllerZKSync.computeAcceptanceTemplateId( + templateIdx + ); + emailAuthMsg.templateId = templateId; + bytes[] memory subjectParamsForAcceptance = new bytes[](1); + subjectParamsForAcceptance[0] = abi.encode(address(simpleWallet)); + emailAuthMsg.subjectParams = subjectParamsForAcceptance; + + vm.mockCall( + address(recoveryControllerZKSync.emailAuthImplementationAddr()), + abi.encodeWithSelector(EmailAuth.authEmail.selector, emailAuthMsg), + abi.encode(0x0) + ); + + // acceptGuardian is internal, we call handleAcceptance, which calls acceptGuardian internally. + vm.startPrank(someRelayer); + recoveryControllerZKSync.handleAcceptance(emailAuthMsg, templateIdx); + vm.stopPrank(); + + require( + recoveryControllerZKSync.guardians(guardian) == + RecoveryControllerZKSync.GuardianStatus.ACCEPTED + ); + } + + function testHandleRecovery() public { + skipIfNotZkSync(); + + handleAcceptance(); + + assertEq(recoveryControllerZKSync.isRecovering(address(simpleWallet)), false); + assertEq( + recoveryControllerZKSync.currentTimelockOfAccount(address(simpleWallet)), + 0 + ); + assertEq(simpleWallet.owner(), deployer); + assertEq( + recoveryControllerZKSync.newSignerCandidateOfAccount( + address(simpleWallet) + ), + address(0x0) + ); + + uint templateIdx = 0; + + EmailAuthMsg memory emailAuthMsg = buildEmailAuthMsg(); + uint templateId = recoveryControllerZKSync.computeRecoveryTemplateId( + templateIdx + ); + emailAuthMsg.templateId = templateId; + bytes[] memory subjectParamsForRecovery = new bytes[](2); + subjectParamsForRecovery[0] = abi.encode(simpleWallet); + subjectParamsForRecovery[1] = abi.encode(newSigner); + emailAuthMsg.subjectParams = subjectParamsForRecovery; + + vm.mockCall( + address(recoveryControllerZKSync.emailAuthImplementationAddr()), + abi.encodeWithSelector(EmailAuth.authEmail.selector, emailAuthMsg), + abi.encode(0x0) + ); + + vm.startPrank(someRelayer); + recoveryControllerZKSync.handleRecovery(emailAuthMsg, templateIdx); + vm.stopPrank(); + + assertEq(recoveryControllerZKSync.isRecovering(address(simpleWallet)), true); + assertEq(simpleWallet.owner(), deployer); + assertEq( + recoveryControllerZKSync.newSignerCandidateOfAccount( + address(simpleWallet) + ), + newSigner + ); + assertEq( + recoveryControllerZKSync.currentTimelockOfAccount(address(simpleWallet)), + block.timestamp + + recoveryControllerZKSync.timelockPeriodOfAccount( + address(simpleWallet) + ) + ); + } + + function testExpectRevertHandleRecoveryGuardianIsNotDeployed() public { + skipIfNotZkSync(); + + handleAcceptance(); + + assertEq(recoveryControllerZKSync.isRecovering(address(simpleWallet)), false); + assertEq( + recoveryControllerZKSync.currentTimelockOfAccount(address(simpleWallet)), + 0 + ); + assertEq(simpleWallet.owner(), deployer); + assertEq( + recoveryControllerZKSync.newSignerCandidateOfAccount( + address(simpleWallet) + ), + address(0x0) + ); + + uint templateIdx = 0; + + EmailAuthMsg memory emailAuthMsg = buildEmailAuthMsg(); + uint templateId = recoveryControllerZKSync.computeRecoveryTemplateId( + templateIdx + ); + emailAuthMsg.templateId = templateId; + bytes[] memory subjectParamsForRecovery = new bytes[](2); + subjectParamsForRecovery[0] = abi.encode(simpleWallet); + subjectParamsForRecovery[1] = abi.encode(newSigner); + emailAuthMsg.subjectParams = subjectParamsForRecovery; + emailAuthMsg.proof.accountSalt = 0x0; + + vm.mockCall( + address(recoveryControllerZKSync.emailAuthImplementationAddr()), + abi.encodeWithSelector(EmailAuth.authEmail.selector, emailAuthMsg), + abi.encode(0x0) + ); + + vm.startPrank(someRelayer); + vm.expectRevert(bytes("guardian is not deployed")); + recoveryControllerZKSync.handleRecovery(emailAuthMsg, templateIdx); + vm.stopPrank(); + } + + function testExpectRevertHandleRecoveryInvalidTemplateId() public { + skipIfNotZkSync(); + + handleAcceptance(); + + assertEq(recoveryControllerZKSync.isRecovering(address(simpleWallet)), false); + assertEq( + recoveryControllerZKSync.currentTimelockOfAccount(address(simpleWallet)), + 0 + ); + assertEq(simpleWallet.owner(), deployer); + assertEq( + recoveryControllerZKSync.newSignerCandidateOfAccount( + address(simpleWallet) + ), + address(0x0) + ); + + uint templateIdx = 0; + + EmailAuthMsg memory emailAuthMsg = buildEmailAuthMsg(); + bytes[] memory subjectParamsForRecovery = new bytes[](2); + subjectParamsForRecovery[0] = abi.encode(simpleWallet); + subjectParamsForRecovery[1] = abi.encode(newSigner); + emailAuthMsg.subjectParams = subjectParamsForRecovery; + + vm.mockCall( + address(recoveryControllerZKSync.emailAuthImplementationAddr()), + abi.encodeWithSelector(EmailAuth.authEmail.selector, emailAuthMsg), + abi.encode(0x0) + ); + + vm.startPrank(someRelayer); + vm.expectRevert(bytes("invalid template id")); + recoveryControllerZKSync.handleRecovery(emailAuthMsg, templateIdx); + vm.stopPrank(); + } + + // Can not test recovery in progress using handleRecovery + // Can not test invalid guardian using handleRecovery + + function testExpectRevertHandleRecoveryGuardianStatusMustBeAccepted() + public + { + skipIfNotZkSync(); + + handleAcceptance(); + + assertEq(recoveryControllerZKSync.isRecovering(address(simpleWallet)), false); + assertEq( + recoveryControllerZKSync.currentTimelockOfAccount(address(simpleWallet)), + 0 + ); + assertEq(simpleWallet.owner(), deployer); + assertEq( + recoveryControllerZKSync.newSignerCandidateOfAccount( + address(simpleWallet) + ), + address(0x0) + ); + + uint templateIdx = 0; + + EmailAuthMsg memory emailAuthMsg = buildEmailAuthMsg(); + uint templateId = recoveryControllerZKSync.computeRecoveryTemplateId( + templateIdx + ); + emailAuthMsg.templateId = templateId; + bytes[] memory subjectParamsForRecovery = new bytes[](2); + subjectParamsForRecovery[0] = abi.encode(simpleWallet); + subjectParamsForRecovery[1] = abi.encode(newSigner); + emailAuthMsg.subjectParams = subjectParamsForRecovery; + emailAuthMsg.proof.accountSalt = 0x0; + + // vm.mockCall( + // address(simpleWallet.emailAuthImplementationAddr()), + // abi.encodeWithSelector(EmailAuth.authEmail.selector, emailAuthMsg), + // abi.encode(0x0) + // ); + + // // Deploy mock guardian, that status is NONE + // address mockCallAddress; + // if(block.chainid == 300) { + // mockCallAddress = address(0x889170C6bEe9053626f8460A9875d22Cf6DE0782); + // } else { + // mockCallAddress = address(0x2Cfb66029975B1c8881adaa3b79c5Caa4FEB84B5); + // } + // vm.mockCall( + // mockCallAddress, + // abi.encodeWithSelector(EmailAuth.authEmail.selector, emailAuthMsg), + // abi.encode(0x0) + // ); + + vm.startPrank(someRelayer); + vm.expectRevert(bytes("guardian is not deployed")); + recoveryControllerZKSync.handleRecovery(emailAuthMsg, templateIdx); + vm.stopPrank(); + } + + function testExpectRevertHandleRecoveryInvalidTemplateIndex() public { + skipIfNotZkSync(); + + handleAcceptance(); + + assertEq(recoveryControllerZKSync.isRecovering(address(simpleWallet)), false); + assertEq( + recoveryControllerZKSync.currentTimelockOfAccount(address(simpleWallet)), + 0 + ); + assertEq(simpleWallet.owner(), deployer); + assertEq( + recoveryControllerZKSync.newSignerCandidateOfAccount( + address(simpleWallet) + ), + address(0x0) + ); + uint templateIdx = 1; + + EmailAuthMsg memory emailAuthMsg = buildEmailAuthMsg(); + uint templateId = recoveryControllerZKSync.computeRecoveryTemplateId( + templateIdx + ); + emailAuthMsg.templateId = templateId; + bytes[] memory subjectParamsForRecovery = new bytes[](2); + subjectParamsForRecovery[0] = abi.encode(simpleWallet); + subjectParamsForRecovery[1] = abi.encode(newSigner); + emailAuthMsg.subjectParams = subjectParamsForRecovery; + + vm.mockCall( + address(recoveryControllerZKSync.emailAuthImplementationAddr()), + abi.encodeWithSelector(EmailAuth.authEmail.selector, emailAuthMsg), + abi.encode(0x0) + ); + + vm.startPrank(someRelayer); + vm.expectRevert(bytes("invalid template index")); + recoveryControllerZKSync.handleRecovery(emailAuthMsg, templateIdx); + vm.stopPrank(); + } + + function testExpectRevertHandleRecoveryInvalidSubjectParams() public { + skipIfNotZkSync(); + + handleAcceptance(); + + assertEq(recoveryControllerZKSync.isRecovering(address(simpleWallet)), false); + assertEq( + recoveryControllerZKSync.currentTimelockOfAccount(address(simpleWallet)), + 0 + ); + assertEq(simpleWallet.owner(), deployer); + assertEq( + recoveryControllerZKSync.newSignerCandidateOfAccount( + address(simpleWallet) + ), + address(0x0) + ); + uint templateIdx = 0; + + EmailAuthMsg memory emailAuthMsg = buildEmailAuthMsg(); + uint templateId = recoveryControllerZKSync.computeRecoveryTemplateId( + templateIdx + ); + emailAuthMsg.templateId = templateId; + bytes[] memory subjectParamsForRecovery = new bytes[](3); + subjectParamsForRecovery[0] = abi.encode(simpleWallet); + subjectParamsForRecovery[1] = abi.encode(newSigner); + subjectParamsForRecovery[1] = abi.encode(address(0x0)); + emailAuthMsg.subjectParams = subjectParamsForRecovery; + + vm.mockCall( + address(recoveryControllerZKSync.emailAuthImplementationAddr()), + abi.encodeWithSelector(EmailAuth.authEmail.selector, emailAuthMsg), + abi.encode(0x0) + ); + + vm.startPrank(someRelayer); + vm.expectRevert(bytes("invalid subject params")); + recoveryControllerZKSync.handleRecovery(emailAuthMsg, templateIdx); + vm.stopPrank(); + } + + // function testExpectRevertHandleRecoveryInvalidGuardianInEmail() public { + // handleAcceptance(); + + // assertEq(recoveryControllerZKSync.isRecovering(address(simpleWallet)), false); + // assertEq( + // recoveryControllerZKSync.currentTimelockOfAccount(address(simpleWallet)), + // 0 + // ); + // assertEq(simpleWallet.owner(), deployer); + // assertEq( + // recoveryControllerZKSync.newSignerCandidateOfAccount(address(simpleWallet)), + // address(0x0) + // ); + // uint templateIdx = 0; + + // EmailAuthMsg memory emailAuthMsg = buildEmailAuthMsg(); + // uint templateId = recoveryControllerZKSync.computeRecoveryTemplateId(templateIdx); + // emailAuthMsg.templateId = templateId; + // bytes[] memory subjectParamsForRecovery = new bytes[](2); + // subjectParamsForRecovery[0] = abi.encode(address(0x0)); + // subjectParamsForRecovery[1] = abi.encode(newSigner); + // emailAuthMsg.subjectParams = subjectParamsForRecovery; + + // vm.mockCall( + // address(recoveryControllerZKSync.emailAuthImplementationAddr()), + // abi.encodeWithSelector(EmailAuth.authEmail.selector, emailAuthMsg), + // abi.encode(0x0) + // ); + + // vm.startPrank(someRelayer); + // vm.expectRevert(bytes("invalid guardian in email")); + // recoveryControllerZKSync.handleRecovery(emailAuthMsg, templateIdx); + // vm.stopPrank(); + // } + + function testExpectRevertHandleRecoveryInvalidNewSigner() public { + skipIfNotZkSync(); + + handleAcceptance(); + + assertEq(recoveryControllerZKSync.isRecovering(address(simpleWallet)), false); + assertEq( + recoveryControllerZKSync.currentTimelockOfAccount(address(simpleWallet)), + 0 + ); + assertEq(simpleWallet.owner(), deployer); + assertEq( + recoveryControllerZKSync.newSignerCandidateOfAccount( + address(simpleWallet) + ), + address(0x0) + ); + uint templateIdx = 0; + + EmailAuthMsg memory emailAuthMsg = buildEmailAuthMsg(); + uint templateId = recoveryControllerZKSync.computeRecoveryTemplateId( + templateIdx + ); + emailAuthMsg.templateId = templateId; + bytes[] memory subjectParamsForRecovery = new bytes[](2); + subjectParamsForRecovery[0] = abi.encode(simpleWallet); + subjectParamsForRecovery[1] = abi.encode(address(0x0)); + emailAuthMsg.subjectParams = subjectParamsForRecovery; + + vm.mockCall( + address(recoveryControllerZKSync.emailAuthImplementationAddr()), + abi.encodeWithSelector(EmailAuth.authEmail.selector, emailAuthMsg), + abi.encode(0x0) + ); + + vm.startPrank(someRelayer); + vm.expectRevert(bytes("invalid new signer")); + recoveryControllerZKSync.handleRecovery(emailAuthMsg, templateIdx); + vm.stopPrank(); + } +} diff --git a/packages/contracts/test/EmailAccountRecoveryZkSync/EmailAccountRecoveryZKSync_rejectRecovery.t.sol b/packages/contracts/test/EmailAccountRecoveryZkSync/EmailAccountRecoveryZKSync_rejectRecovery.t.sol new file mode 100644 index 00000000..8293060b --- /dev/null +++ b/packages/contracts/test/EmailAccountRecoveryZkSync/EmailAccountRecoveryZKSync_rejectRecovery.t.sol @@ -0,0 +1,265 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.12; + +import "forge-std/Test.sol"; +import "forge-std/console.sol"; +import {EmailAuth, EmailAuthMsg} from "../../src/EmailAuth.sol"; +import {RecoveryControllerZKSync} from "../helpers/RecoveryControllerZKSync.sol"; +import {StructHelper} from "../helpers/StructHelper.sol"; +import {SimpleWallet} from "../helpers/SimpleWallet.sol"; +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; + +contract EmailAccountRecoveryZKSyncTest_rejectRecovery is StructHelper { + constructor() {} + + function setUp() public override { + super.setUp(); + } + + /** + * Set up functions + */ + function requestGuardian() public { + setUp(); + + require( + recoveryControllerZKSync.guardians(guardian) == + RecoveryControllerZKSync.GuardianStatus.NONE + ); + + vm.startPrank(deployer); + recoveryControllerZKSync.requestGuardian(guardian); + vm.stopPrank(); + + require( + recoveryControllerZKSync.guardians(guardian) == + RecoveryControllerZKSync.GuardianStatus.REQUESTED + ); + } + + function handleAcceptance() public { + requestGuardian(); + + require( + recoveryControllerZKSync.guardians(guardian) == + RecoveryControllerZKSync.GuardianStatus.REQUESTED + ); + + console.log("guardian", guardian); + uint templateIdx = 0; + + EmailAuthMsg memory emailAuthMsg = buildEmailAuthMsg(); + bytes[] memory subjectParamsForAcceptance = new bytes[](1); + subjectParamsForAcceptance[0] = abi.encode(address(simpleWallet)); + emailAuthMsg.subjectParams = subjectParamsForAcceptance; + address recoveredAccount = recoveryControllerZKSync + .extractRecoveredAccountFromAcceptanceSubject( + emailAuthMsg.subjectParams, + templateIdx + ); + address computedGuardian = recoveryControllerZKSync.computeEmailAuthAddress( + recoveredAccount, + emailAuthMsg.proof.accountSalt + ); + console.log("computed guardian", computedGuardian); + uint templateId = recoveryControllerZKSync.computeAcceptanceTemplateId( + templateIdx + ); + emailAuthMsg.templateId = templateId; + + vm.mockCall( + address(recoveryControllerZKSync.emailAuthImplementationAddr()), + abi.encodeWithSelector(EmailAuth.authEmail.selector, emailAuthMsg), + abi.encode(0x0) + ); + + // acceptGuardian is internal, we call handleAcceptance, which calls acceptGuardian internally. + vm.startPrank(someRelayer); + recoveryControllerZKSync.handleAcceptance(emailAuthMsg, templateIdx); + vm.stopPrank(); + + require( + recoveryControllerZKSync.guardians(guardian) == + RecoveryControllerZKSync.GuardianStatus.ACCEPTED + ); + } + + function handleRecovery() public { + handleAcceptance(); + + assertEq(simpleWallet.owner(), deployer); + assertEq(recoveryControllerZKSync.isRecovering(address(simpleWallet)), false); + assertEq( + recoveryControllerZKSync.currentTimelockOfAccount(address(simpleWallet)), + 0 + ); + assertEq( + recoveryControllerZKSync.newSignerCandidateOfAccount( + address(simpleWallet) + ), + address(0x0) + ); + + uint templateIdx = 0; + + EmailAuthMsg memory emailAuthMsg = buildEmailAuthMsg(); + uint templateId = recoveryControllerZKSync.computeRecoveryTemplateId( + templateIdx + ); + emailAuthMsg.templateId = templateId; + bytes[] memory subjectParamsForRecovery = new bytes[](2); + subjectParamsForRecovery[0] = abi.encode(simpleWallet); + subjectParamsForRecovery[1] = abi.encode(newSigner); + emailAuthMsg.subjectParams = subjectParamsForRecovery; + + vm.mockCall( + address(recoveryControllerZKSync.emailAuthImplementationAddr()), + abi.encodeWithSelector(EmailAuth.authEmail.selector, emailAuthMsg), + abi.encode(0x0) + ); + + vm.startPrank(someRelayer); + recoveryControllerZKSync.handleRecovery(emailAuthMsg, templateIdx); + vm.stopPrank(); + + assertEq(recoveryControllerZKSync.isRecovering(address(simpleWallet)), true); + assertEq(simpleWallet.owner(), deployer); + assertEq( + recoveryControllerZKSync.newSignerCandidateOfAccount( + address(simpleWallet) + ), + newSigner + ); + assertEq( + recoveryControllerZKSync.currentTimelockOfAccount(address(simpleWallet)), + block.timestamp + + recoveryControllerZKSync.timelockPeriodOfAccount( + address(simpleWallet) + ) + ); + } + + function testRejectRecovery() public { + skipIfNotZkSync(); + + vm.warp(block.timestamp + 3 days); + + handleRecovery(); + + assertEq(recoveryControllerZKSync.isRecovering(address(simpleWallet)), true); + assertEq( + recoveryControllerZKSync.currentTimelockOfAccount(address(simpleWallet)), + block.timestamp + + recoveryControllerZKSync.timelockPeriodOfAccount( + address(simpleWallet) + ) + ); + assertEq(simpleWallet.owner(), deployer); + assertEq( + recoveryControllerZKSync.newSignerCandidateOfAccount( + address(simpleWallet) + ), + newSigner + ); + + vm.warp(0); + + vm.startPrank(address(simpleWallet)); + recoveryControllerZKSync.rejectRecovery(); + vm.stopPrank(); + + assertEq(recoveryControllerZKSync.isRecovering(address(simpleWallet)), false); + assertEq( + recoveryControllerZKSync.currentTimelockOfAccount(address(simpleWallet)), + 0 + ); + assertEq(simpleWallet.owner(), deployer); + assertEq( + recoveryControllerZKSync.newSignerCandidateOfAccount( + address(simpleWallet) + ), + address(0x0) + ); + } + + function testExpectRevertRejectRecoveryRecoveryNotInProgress() public { + skipIfNotZkSync(); + + handleAcceptance(); + + assertEq(recoveryControllerZKSync.isRecovering(address(simpleWallet)), false); + assertEq( + recoveryControllerZKSync.currentTimelockOfAccount(address(simpleWallet)), + 0 + ); + assertEq(simpleWallet.owner(), deployer); + assertEq( + recoveryControllerZKSync.newSignerCandidateOfAccount( + address(simpleWallet) + ), + address(0x0) + ); + + vm.startPrank(deployer); + vm.expectRevert(bytes("recovery not in progress")); + recoveryControllerZKSync.rejectRecovery(); + vm.stopPrank(); + } + + function testExpectRevertRejectRecovery() public { + skipIfNotZkSync(); + + vm.warp(block.timestamp + 1 days); + + handleRecovery(); + + assertEq(recoveryControllerZKSync.isRecovering(address(simpleWallet)), true); + assertEq( + recoveryControllerZKSync.currentTimelockOfAccount(address(simpleWallet)), + block.timestamp + + recoveryControllerZKSync.timelockPeriodOfAccount( + address(simpleWallet) + ) + ); + assertEq(simpleWallet.owner(), deployer); + assertEq( + recoveryControllerZKSync.newSignerCandidateOfAccount( + address(simpleWallet) + ), + newSigner + ); + + vm.startPrank(address(simpleWallet)); + vm.warp(block.timestamp + 4 days); + vm.expectRevert(bytes("timelock expired")); + recoveryControllerZKSync.rejectRecovery(); + vm.stopPrank(); + } + + function testExpectRevertRejectRecoveryOwnableUnauthorizedAccount() public { + skipIfNotZkSync(); + + handleRecovery(); + + assertEq(recoveryControllerZKSync.isRecovering(address(simpleWallet)), true); + assertEq( + recoveryControllerZKSync.currentTimelockOfAccount(address(simpleWallet)), + block.timestamp + + recoveryControllerZKSync.timelockPeriodOfAccount( + address(simpleWallet) + ) + ); + assertEq(simpleWallet.owner(), deployer); + assertEq( + recoveryControllerZKSync.newSignerCandidateOfAccount( + address(simpleWallet) + ), + newSigner + ); + + vm.startPrank(deployer); + vm.expectRevert("recovery not in progress"); + recoveryControllerZKSync.rejectRecovery(); + vm.stopPrank(); + } +} diff --git a/packages/contracts/test/EmailAccountRecoveryZkSync/EmailAccountRecoveryZKSync_requestGuardian.t.sol b/packages/contracts/test/EmailAccountRecoveryZkSync/EmailAccountRecoveryZKSync_requestGuardian.t.sol new file mode 100644 index 00000000..03418653 --- /dev/null +++ b/packages/contracts/test/EmailAccountRecoveryZkSync/EmailAccountRecoveryZKSync_requestGuardian.t.sol @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.12; + +import "forge-std/Test.sol"; +import "forge-std/console.sol"; +import {EmailAuth, EmailAuthMsg} from "../../src/EmailAuth.sol"; +import {RecoveryControllerZKSync} from "../helpers/RecoveryControllerZKSync.sol"; +import {StructHelper} from "../helpers/StructHelper.sol"; +import {SimpleWallet} from "../helpers/SimpleWallet.sol"; +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; + +contract EmailAccountRecoveryZKSyncTest_requestGuardian is StructHelper { + constructor() {} + + function setUp() public override { + super.setUp(); + } + + function testRequestGuardian() public { + skipIfNotZkSync(); + + setUp(); + require( + recoveryControllerZKSync.guardians(guardian) == + RecoveryControllerZKSync.GuardianStatus.NONE + ); + + vm.startPrank(deployer); + recoveryControllerZKSync.requestGuardian(guardian); + vm.stopPrank(); + + require( + recoveryControllerZKSync.guardians(guardian) == + RecoveryControllerZKSync.GuardianStatus.REQUESTED + ); + } + + // function testRequestGuardianNotOwner() public { + // setUp(); + + // require( + // recoveryControllerZKSync.guardians(guardian) == + // recoveryControllerZKSync.GuardianStatus.NONE + // ); + + // vm.startPrank(receiver); + // recoveryControllerZKSync.requestGuardian(guardian); + // vm.stopPrank(); + + // require( + // recoveryControllerZKSync.guardians(guardian) == + // recoveryControllerZKSync.GuardianStatus.NONE + // ); + // } + + function testExpectRevertRequestGuardianInvalidGuardian() public { + skipIfNotZkSync(); + + setUp(); + + require( + recoveryControllerZKSync.guardians(guardian) == + RecoveryControllerZKSync.GuardianStatus.NONE + ); + + vm.startPrank(deployer); + vm.expectRevert(bytes("invalid guardian")); + recoveryControllerZKSync.requestGuardian(address(0x0)); + vm.stopPrank(); + } + + function testExpectRevertRequestGuardianGuardianStatusMustBeNone() public { + skipIfNotZkSync(); + + setUp(); + + require( + recoveryControllerZKSync.guardians(guardian) == + RecoveryControllerZKSync.GuardianStatus.NONE + ); + + vm.startPrank(deployer); + recoveryControllerZKSync.requestGuardian(guardian); + vm.expectRevert(bytes("guardian status must be NONE")); + recoveryControllerZKSync.requestGuardian(guardian); + vm.stopPrank(); + } +} diff --git a/packages/contracts/test/EmailAccountRecoveryZkSync/EmailAccountRecoveryZKSync_transfer.t.sol b/packages/contracts/test/EmailAccountRecoveryZkSync/EmailAccountRecoveryZKSync_transfer.t.sol new file mode 100644 index 00000000..b784847d --- /dev/null +++ b/packages/contracts/test/EmailAccountRecoveryZkSync/EmailAccountRecoveryZKSync_transfer.t.sol @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.12; + +import "forge-std/Test.sol"; +import "forge-std/console.sol"; +import {EmailAuth, EmailAuthMsg} from "../../src/EmailAuth.sol"; +import {RecoveryControllerZKSync} from "../helpers/RecoveryControllerZKSync.sol"; +import {StructHelper} from "../helpers/StructHelper.sol"; +import {SimpleWallet} from "../helpers/SimpleWallet.sol"; +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; + +contract EmailAccountRecoveryZKSyncTest_transfer is StructHelper { + constructor() {} + + function setUp() public override { + super.setUp(); + } + + function testTransfer() public { + skipIfNotZkSync(); + + setUp(); + + assertEq(address(simpleWallet).balance, 1 ether); + assertEq(receiver.balance, 0 ether); + + vm.startPrank(deployer); + simpleWallet.transfer(receiver, 1 ether); + vm.stopPrank(); + + assertEq(address(simpleWallet).balance, 0 ether); + assertEq(receiver.balance, 1 ether); + } + + function testExpectRevertTransferOnlyOwner() public { + skipIfNotZkSync(); + + setUp(); + + assertEq(address(simpleWallet).balance, 1 ether); + assertEq(receiver.balance, 0 ether); + + vm.startPrank(receiver); + vm.expectRevert( + abi.encodeWithSelector( + OwnableUpgradeable.OwnableUnauthorizedAccount.selector, + receiver + ) + ); + simpleWallet.transfer(receiver, 1 ether); + vm.stopPrank(); + } + + function testExpectRevertTransferOnlyOwnerInsufficientBalance() public { + skipIfNotZkSync(); + + setUp(); + + assertEq(address(simpleWallet).balance, 1 ether); + assertEq(receiver.balance, 0 ether); + + vm.startPrank(deployer); + assertEq(receiver.balance, 0 ether); + vm.expectRevert(bytes("insufficient balance")); + simpleWallet.transfer(receiver, 2 ether); + vm.stopPrank(); + } +} diff --git a/packages/contracts/test/EmailAccountRecoveryZkSync/EmailAccountRecoveryZKSync_withdraw.t.sol b/packages/contracts/test/EmailAccountRecoveryZkSync/EmailAccountRecoveryZKSync_withdraw.t.sol new file mode 100644 index 00000000..07d347d6 --- /dev/null +++ b/packages/contracts/test/EmailAccountRecoveryZkSync/EmailAccountRecoveryZKSync_withdraw.t.sol @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.12; + +import "forge-std/Test.sol"; +import "forge-std/console.sol"; +import {EmailAuth, EmailAuthMsg} from "../../src/EmailAuth.sol"; +import {RecoveryControllerZKSync} from "../helpers/RecoveryControllerZKSync.sol"; +import {StructHelper} from "../helpers/StructHelper.sol"; +import {SimpleWallet} from "../helpers/SimpleWallet.sol"; +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; + +contract EmailAccountRecoveryZKSyncTest_withdraw is StructHelper { + constructor() {} + + function setUp() public override { + super.setUp(); + } + + function testWithdraw() public { + skipIfNotZkSync(); + + setUp(); + + assertEq(address(simpleWallet).balance, 1 ether); + assertEq(deployer.balance, 0 ether); + + vm.startPrank(deployer); + simpleWallet.withdraw(1 ether); + vm.stopPrank(); + + assertEq(address(simpleWallet).balance, 0 ether); + assertEq(deployer.balance, 1 ether); + } + + function testExpectRevertWithdrawOnlyOwner() public { + skipIfNotZkSync(); + + setUp(); + + assertEq(address(simpleWallet).balance, 1 ether); + assertEq(deployer.balance, 0 ether); + + vm.startPrank(receiver); + vm.expectRevert( + abi.encodeWithSelector( + OwnableUpgradeable.OwnableUnauthorizedAccount.selector, + address(receiver) + ) + ); + simpleWallet.withdraw(1 ether); + vm.stopPrank(); + } + + function testExpectRevertWithdrawInsufficientBalance() public { + skipIfNotZkSync(); + + setUp(); + + assertEq(address(simpleWallet).balance, 1 ether); + assertEq(deployer.balance, 0 ether); + + vm.startPrank(deployer); + vm.expectRevert(bytes("insufficient balance")); + simpleWallet.withdraw(10 ether); + vm.stopPrank(); + } +} diff --git a/packages/contracts/test/EmailAccountRecoveryZkSync/EmailAccountRecoveryZkSync_acceptanceCommandTemplates.t.sol b/packages/contracts/test/EmailAccountRecoveryZkSync/EmailAccountRecoveryZkSync_acceptanceCommandTemplates.t.sol new file mode 100644 index 00000000..81d9a67c --- /dev/null +++ b/packages/contracts/test/EmailAccountRecoveryZkSync/EmailAccountRecoveryZkSync_acceptanceCommandTemplates.t.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.12; + +import "forge-std/Test.sol"; +import "forge-std/console.sol"; +import {EmailAuth, EmailAuthMsg} from "../../src/EmailAuth.sol"; +import {RecoveryControllerZKSync} from "../helpers/RecoveryControllerZKSync.sol"; +import {StructHelper} from "../helpers/StructHelper.sol"; +import {SimpleWallet} from "../helpers/SimpleWallet.sol"; +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; + +contract EmailAccountRecoveryZKSyncTest_acceptanceCommandTemplates is + StructHelper +{ + constructor() {} + + function setUp() public override { + super.setUp(); + } + + function testAcceptanceCommandTemplates() public { + skipIfNotZkSync(); + + setUp(); + string[][] memory res = recoveryController.acceptanceCommandTemplates(); + assertEq(res[0][0], "Accept"); + assertEq(res[0][1], "guardian"); + assertEq(res[0][2], "request"); + assertEq(res[0][3], "for"); + assertEq(res[0][4], "{ethAddr}"); + } +} diff --git a/packages/contracts/test/EmailAccountRecoveryZkSync/EmailAccountRecoveryZkSync_recoveryCommandTemplates.t.sol b/packages/contracts/test/EmailAccountRecoveryZkSync/EmailAccountRecoveryZkSync_recoveryCommandTemplates.t.sol new file mode 100644 index 00000000..62271e43 --- /dev/null +++ b/packages/contracts/test/EmailAccountRecoveryZkSync/EmailAccountRecoveryZkSync_recoveryCommandTemplates.t.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.12; + +import "forge-std/Test.sol"; +import "forge-std/console.sol"; +import {EmailAuth, EmailAuthMsg} from "../../src/EmailAuth.sol"; +import {RecoveryController} from "../helpers/RecoveryController.sol"; +import {StructHelper} from "../helpers/StructHelper.sol"; +import {SimpleWallet} from "../helpers/SimpleWallet.sol"; +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; + +contract EmailAccountRecoveryZKSyncTest_recoveryCommandTemplates is + StructHelper +{ + constructor() {} + + function setUp() public override { + super.setUp(); + } + + function testRecoveryCommandTemplates() public { + skipIfNotZkSync(); + + setUp(); + string[][] memory res = recoveryController.recoveryCommandTemplates(); + assertEq(res[0][0], "Set"); + assertEq(res[0][1], "the"); + assertEq(res[0][2], "new"); + assertEq(res[0][3], "signer"); + assertEq(res[0][4], "of"); + assertEq(res[0][5], "{ethAddr}"); + assertEq(res[0][6], "to"); + assertEq(res[0][7], "{ethAddr}"); + } +} diff --git a/packages/contracts/test/Integration.t.sol b/packages/contracts/test/Integration.t.sol index 647d3d9c..007c1623 100644 --- a/packages/contracts/test/Integration.t.sol +++ b/packages/contracts/test/Integration.t.sol @@ -14,6 +14,7 @@ import "../src/utils/ECDSAOwnedDKIMRegistry.sol"; import "./helpers/SimpleWallet.sol"; import "./helpers/RecoveryController.sol"; import "forge-std/console.sol"; +import "../src/utils/ZKSyncCreate2Factory.sol"; contract IntegrationTest is Test { using Strings for *; @@ -38,16 +39,8 @@ contract IntegrationTest is Test { 0x0ea9c777dc7110e5a9e89b13f0cfc540e3845ba120b2b6dc24024d61488d4788; uint256 startTimestamp = 1723443691; // September 11, 2024, 17:34:51 UTC - bool isZksync = false; - function setUp() public { - if (block.chainid == 300) { - vm.createSelectFork("https://sepolia.era.zksync.dev"); - isZksync = true; - } else { - vm.createSelectFork("https://mainnet.base.org"); - isZksync = false; - } + vm.createSelectFork("https://mainnet.base.org"); vm.warp(startTimestamp); @@ -100,6 +93,11 @@ contract IntegrationTest is Test { console.log("emailAuthImpl"); console.logAddress(address(emailAuthImpl)); + // Create zkSync Factory + ZKSyncCreate2Factory factoryImpl = new ZKSyncCreate2Factory(); + console.log("factoryImpl"); + console.logAddress(address(factoryImpl)); + // Create RecoveryController as EmailAccountRecovery implementation RecoveryController recoveryControllerImpl = new RecoveryController(); ERC1967Proxy recoveryControllerProxy = new ERC1967Proxy( @@ -146,17 +144,10 @@ contract IntegrationTest is Test { vm.deal(address(relayer), 1 ether); console.log("SimpleWallet is at ", address(simpleWallet)); - if (isZksync) { - assertEq( - address(simpleWallet), - 0x05A78D3dB903a58B5FA373E07e5044B95B12aec4 - ); - } else { - assertEq( - address(simpleWallet), - 0x0C06688e61C06466E2a5C6fE4E15c359260a33f3 - ); - } + assertEq( + address(simpleWallet), + 0xeb8E21A363Dce22ff6057dEEF7c074062037F571 + ); address simpleWalletOwner = simpleWallet.owner(); // Verify the email proof for acceptance @@ -189,13 +180,8 @@ contract IntegrationTest is Test { emailProof.domainName = "gmail.com"; emailProof.publicKeyHash = bytes32(vm.parseUint(pubSignals[9])); emailProof.timestamp = vm.parseUint(pubSignals[11]); - if (isZksync) { - emailProof - .maskedCommand = "Accept guardian request for 0x05A78D3dB903a58B5FA373E07e5044B95B12aec4"; - } else { - emailProof - .maskedCommand = "Accept guardian request for 0x0C06688e61C06466E2a5C6fE4E15c359260a33f3"; - } + emailProof + .maskedCommand = "Accept guardian request for 0xeb8E21A363Dce22ff6057dEEF7c074062037F571"; emailProof.emailNullifier = bytes32(vm.parseUint(pubSignals[10])); emailProof.accountSalt = bytes32(vm.parseUint(pubSignals[32])); accountSalt = emailProof.accountSalt; @@ -275,13 +261,8 @@ contract IntegrationTest is Test { emailProof.timestamp = vm.parseUint(pubSignals[11]); // 0xa0Ee7A142d267C1f36714E4a8F75612F20a79720 is account 9 - if (isZksync) { - emailProof - .maskedCommand = "Set the new signer of 0x05A78D3dB903a58B5FA373E07e5044B95B12aec4 to 0xa0Ee7A142d267C1f36714E4a8F75612F20a79720"; - } else { - emailProof - .maskedCommand = "Set the new signer of 0x0C06688e61C06466E2a5C6fE4E15c359260a33f3 to 0xa0Ee7A142d267C1f36714E4a8F75612F20a79720"; - } + emailProof + .maskedCommand = "Set the new signer of 0xeb8E21A363Dce22ff6057dEEF7c074062037F571 to 0xa0Ee7A142d267C1f36714E4a8F75612F20a79720"; emailProof.emailNullifier = bytes32(vm.parseUint(pubSignals[10])); emailProof.accountSalt = bytes32(vm.parseUint(pubSignals[32])); diff --git a/packages/contracts/test/IntegrationZKSync.t.sol b/packages/contracts/test/IntegrationZKSync.t.sol new file mode 100644 index 00000000..f30cc72b --- /dev/null +++ b/packages/contracts/test/IntegrationZKSync.t.sol @@ -0,0 +1,377 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.12; + +import "forge-std/Test.sol"; +import "forge-std/console.sol"; +import "@openzeppelin/contracts/utils/Strings.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import "@zk-email/contracts/DKIMRegistry.sol"; +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "../src/EmailAuth.sol"; +import "../src/utils/Verifier.sol"; +import "../src/utils/ECDSAOwnedDKIMRegistry.sol"; +import "./helpers/SimpleWallet.sol"; +import "./helpers/RecoveryControllerZKSync.sol"; +import "forge-std/console.sol"; +import "../src/utils/ZKSyncCreate2Factory.sol"; + +contract IntegrationZKSyncTest is Test { + using Strings for *; + using console for *; + + EmailAuth emailAuth; + Verifier verifier; + ECDSAOwnedDKIMRegistry dkim; + + RecoveryControllerZKSync recoveryControllerZKSync; + SimpleWallet simpleWallet; + + address deployer = vm.addr(1); + address receiver = vm.addr(2); + address guardian = vm.addr(3); + address relayer = deployer; + + bytes32 accountSalt; + string selector = "12345"; + string domainName = "gmail.com"; + bytes32 publicKeyHash = + 0x0ea9c777dc7110e5a9e89b13f0cfc540e3845ba120b2b6dc24024d61488d4788; + uint256 startTimestamp = 1723443691; // September 11, 2024, 17:34:51 UTC + + function setUp() public { + vm.createSelectFork("http://127.0.0.1:8011"); + + vm.warp(startTimestamp); + + vm.startPrank(deployer); + address signer = deployer; + + // Create DKIM registry + { + ECDSAOwnedDKIMRegistry ecdsaDkimImpl = new ECDSAOwnedDKIMRegistry(); + ERC1967Proxy ecdsaDkimProxy = new ERC1967Proxy( + address(ecdsaDkimImpl), + abi.encodeCall(ecdsaDkimImpl.initialize, (msg.sender, signer)) + ); + dkim = ECDSAOwnedDKIMRegistry(address(ecdsaDkimProxy)); + } + string memory signedMsg = dkim.computeSignedMsg( + dkim.SET_PREFIX(), + selector, + domainName, + publicKeyHash + ); + bytes32 digest = MessageHashUtils.toEthSignedMessageHash( + bytes(signedMsg) + ); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(1, digest); + bytes memory signature = abi.encodePacked(r, s, v); + dkim.setDKIMPublicKeyHash( + selector, + domainName, + publicKeyHash, + signature + ); + + // Create Verifier + { + Verifier verifierImpl = new Verifier(); + ERC1967Proxy verifierProxy = new ERC1967Proxy( + address(verifierImpl), + abi.encodeCall(verifierImpl.initialize, (msg.sender)) + ); + verifier = Verifier(address(verifierProxy)); + } + + // Create EmailAuth + EmailAuth emailAuthImpl = new EmailAuth(); + console.log("emailAuthImpl"); + console.logAddress(address(emailAuthImpl)); + + // Create zkSync Factory + ZKSyncCreate2Factory factoryImpl = new ZKSyncCreate2Factory(); + console.log("factoryImpl"); + console.logAddress(address(factoryImpl)); + + // Create RecoveryController as EmailAccountRecovery implementation + RecoveryControllerZKSync recoveryControllerZKSyncImpl = new RecoveryControllerZKSync(); + ERC1967Proxy recoveryControllerZKSyncProxy = new ERC1967Proxy( + address(recoveryControllerZKSyncImpl), + abi.encodeCall( + recoveryControllerZKSyncImpl.initialize, + ( + signer, + address(verifier), + address(dkim), + address(emailAuthImpl), + address(factoryImpl) + ) + ) + ); + recoveryControllerZKSync = RecoveryControllerZKSync( + payable(address(recoveryControllerZKSyncProxy)) + ); + + // Create SimpleWallet as EmailAccountRecovery implementation + SimpleWallet simpleWalletImpl = new SimpleWallet(); + ERC1967Proxy simpleWalletProxy = new ERC1967Proxy( + address(simpleWalletImpl), + abi.encodeCall( + simpleWalletImpl.initialize, + (signer, address(recoveryControllerZKSync)) + ) + ); + simpleWallet = SimpleWallet(payable(address(simpleWalletProxy))); + // console.log( + // "emailAuthImplementation", + // simpleWallet.emailAuthImplementation() + // ); + + vm.stopPrank(); + } + + function testIntegration_Account_Recovery_ZkSync() public { + console.log("testIntegration_Account_Recovery_ZKSync"); + + bytes32 accountCode = 0x1162ebff40918afe5305e68396f0283eb675901d0387f97d21928d423aaa0b54; + uint templateIdx = 0; + + vm.startPrank(relayer); + vm.deal(address(relayer), 1 ether); + + console.log("SimpleWallet is at ", address(simpleWallet)); + assertEq( + address(simpleWallet), + 0x7c5E4b26643682AF77A196781A851c9Fe769472d + ); + address simpleWalletOwner = simpleWallet.owner(); + + // Verify the email proof for acceptance + string[] memory inputGenerationInput = new string[](3); + inputGenerationInput[0] = string.concat( + vm.projectRoot(), + "/test/bin/accept.sh" + ); + inputGenerationInput[1] = string.concat( + vm.projectRoot(), + "/test/emails/", + block.chainid.toString(), + "/accept.eml" + ); + inputGenerationInput[2] = uint256(accountCode).toHexString(32); + vm.ffi(inputGenerationInput); + + string memory publicInputFile = vm.readFile( + string.concat( + vm.projectRoot(), + "/test/build_integration/email_auth_public.json" + ) + ); + string[] memory pubSignals = abi.decode( + vm.parseJson(publicInputFile), + (string[]) + ); + + EmailProof memory emailProof; + emailProof.domainName = "gmail.com"; + emailProof.publicKeyHash = bytes32(vm.parseUint(pubSignals[9])); + emailProof.timestamp = vm.parseUint(pubSignals[11]); + emailProof + .maskedSubject = "Accept guardian request for 0x7c5E4b26643682AF77A196781A851c9Fe769472d"; + emailProof.emailNullifier = bytes32(vm.parseUint(pubSignals[10])); + emailProof.accountSalt = bytes32(vm.parseUint(pubSignals[32])); + accountSalt = emailProof.accountSalt; + emailProof.isCodeExist = vm.parseUint(pubSignals[33]) == 1; + emailProof.proof = proofToBytes( + string.concat( + vm.projectRoot(), + "/test/build_integration/email_auth_proof.json" + ) + ); + + console.log("dkim public key hash: "); + console.logBytes32(bytes32(vm.parseUint(pubSignals[9]))); + console.log("email nullifier: "); + console.logBytes32(bytes32(vm.parseUint(pubSignals[10]))); + console.log("timestamp: ", vm.parseUint(pubSignals[11])); + console.log("account salt: "); + console.logBytes32(bytes32(vm.parseUint(pubSignals[32]))); + console.log("is code exist: ", vm.parseUint(pubSignals[33])); + + // Call Request guardian -> GuardianStatus.REQUESTED + guardian = recoveryControllerZKSync.computeEmailAuthAddress( + address(simpleWallet), + accountSalt + ); + recoveryControllerZKSync.requestGuardian(guardian); + require( + recoveryControllerZKSync.guardians(guardian) == + RecoveryControllerZKSync.GuardianStatus.REQUESTED, + "GuardianStatus should be REQUESTED" + ); + + // Call handleAcceptance -> GuardianStatus.ACCEPTED + bytes[] memory subjectParamsForAcceptance = new bytes[](1); + subjectParamsForAcceptance[0] = abi.encode(address(simpleWallet)); + EmailAuthMsg memory emailAuthMsg = EmailAuthMsg({ + templateId: recoveryControllerZKSync.computeAcceptanceTemplateId( + templateIdx + ), + subjectParams: subjectParamsForAcceptance, + skipedSubjectPrefix: 0, + proof: emailProof + }); + recoveryControllerZKSync.handleAcceptance(emailAuthMsg, templateIdx); + require( + recoveryControllerZKSync.guardians(guardian) == + RecoveryControllerZKSync.GuardianStatus.ACCEPTED, + "GuardianStatus should be ACCEPTED" + ); + + // Verify the email proof for recovery + inputGenerationInput = new string[](3); + inputGenerationInput[0] = string.concat( + vm.projectRoot(), + "/test/bin/recovery.sh" + ); + inputGenerationInput[1] = string.concat( + vm.projectRoot(), + "/test/emails/", + block.chainid.toString(), + "/recovery.eml" + ); + inputGenerationInput[2] = uint256(accountCode).toHexString(32); + vm.ffi(inputGenerationInput); + + publicInputFile = vm.readFile( + string.concat( + vm.projectRoot(), + "/test/build_integration/email_auth_public.json" + ) + ); + pubSignals = abi.decode(vm.parseJson(publicInputFile), (string[])); + + // EmailProof memory emailProof; + emailProof.domainName = "gmail.com"; + emailProof.publicKeyHash = bytes32(vm.parseUint(pubSignals[9])); + emailProof.timestamp = vm.parseUint(pubSignals[11]); + + // 0xa0Ee7A142d267C1f36714E4a8F75612F20a79720 is account 9 + emailProof + .maskedSubject = "Set the new signer of 0x7c5E4b26643682AF77A196781A851c9Fe769472d to 0xa0Ee7A142d267C1f36714E4a8F75612F20a79720"; + + emailProof.emailNullifier = bytes32(vm.parseUint(pubSignals[10])); + emailProof.accountSalt = bytes32(vm.parseUint(pubSignals[32])); + require( + emailProof.accountSalt == accountSalt, + "accountSalt should be the same" + ); + emailProof.isCodeExist = vm.parseUint(pubSignals[33]) == 1; + emailProof.proof = proofToBytes( + string.concat( + vm.projectRoot(), + "/test/build_integration/email_auth_proof.json" + ) + ); + + console.log("dkim public key hash: "); + console.logBytes32(bytes32(vm.parseUint(pubSignals[9]))); + console.log("email nullifier: "); + console.logBytes32(bytes32(vm.parseUint(pubSignals[10]))); + console.log("timestamp: ", vm.parseUint(pubSignals[11])); + console.log("account salt: "); + console.logBytes32(bytes32(vm.parseUint(pubSignals[32]))); + console.log("is code exist: ", vm.parseUint(pubSignals[33])); + + // Call handleRecovery -> isRecovering = true; + bytes[] memory subjectParamsForRecovery = new bytes[](2); + subjectParamsForRecovery[0] = abi.encode(address(simpleWallet)); + subjectParamsForRecovery[1] = abi.encode( + address(0xa0Ee7A142d267C1f36714E4a8F75612F20a79720) + ); + emailAuthMsg = EmailAuthMsg({ + templateId: recoveryControllerZKSync.computeRecoveryTemplateId( + templateIdx + ), + subjectParams: subjectParamsForRecovery, + skipedSubjectPrefix: 0, + proof: emailProof + }); + recoveryControllerZKSync.handleRecovery(emailAuthMsg, templateIdx); + require( + recoveryControllerZKSync.isRecovering(address(simpleWallet)), + "isRecovering should be set" + ); + require( + recoveryControllerZKSync.newSignerCandidateOfAccount( + address(simpleWallet) + ) == 0xa0Ee7A142d267C1f36714E4a8F75612F20a79720, + "newSignerCandidate should be set" + ); + require( + recoveryControllerZKSync.currentTimelockOfAccount(address(simpleWallet)) > + 0, + "timelock should be set" + ); + require( + simpleWallet.owner() == simpleWalletOwner, + "simpleWallet owner should be old one" + ); + + // Call completeRecovery + // Warp at 3 days + 10 seconds later + vm.warp(startTimestamp + (3 * 24 * 60 * 60) + 10); + recoveryControllerZKSync.completeRecovery( + address(simpleWallet), + new bytes(0) + ); + console.log("simpleWallet owner: ", simpleWallet.owner()); + require( + !recoveryControllerZKSync.isRecovering(address(simpleWallet)), + "isRecovering should be reset" + ); + require( + recoveryControllerZKSync.newSignerCandidateOfAccount( + address(simpleWallet) + ) == address(0), + "newSignerCandidate should be reset" + ); + require( + recoveryControllerZKSync.currentTimelockOfAccount( + address(simpleWallet) + ) == 0, + "timelock should be reset" + ); + require( + simpleWallet.owner() == 0xa0Ee7A142d267C1f36714E4a8F75612F20a79720, + "simpleWallet owner should be new one" + ); + vm.stopPrank(); + } + + function proofToBytes( + string memory proofPath + ) internal view returns (bytes memory) { + string memory proofFile = vm.readFile(proofPath); + string[] memory pi_a = abi.decode( + vm.parseJson(proofFile, ".pi_a"), + (string[]) + ); + uint256[2] memory pA = [vm.parseUint(pi_a[0]), vm.parseUint(pi_a[1])]; + string[][] memory pi_b = abi.decode( + vm.parseJson(proofFile, ".pi_b"), + (string[][]) + ); + uint256[2][2] memory pB = [ + [vm.parseUint(pi_b[0][1]), vm.parseUint(pi_b[0][0])], + [vm.parseUint(pi_b[1][1]), vm.parseUint(pi_b[1][0])] + ]; + string[] memory pi_c = abi.decode( + vm.parseJson(proofFile, ".pi_c"), + (string[]) + ); + uint256[2] memory pC = [vm.parseUint(pi_c[0]), vm.parseUint(pi_c[1])]; + bytes memory proof = abi.encode(pA, pB, pC); + return proof; + } +} diff --git a/packages/contracts/test/emails/300/accept.eml b/packages/contracts/test/emails/300/accept.eml index f7340adc..7e10ce7a 100644 --- a/packages/contracts/test/emails/300/accept.eml +++ b/packages/contracts/test/emails/300/accept.eml @@ -1,90 +1,90 @@ Delivered-To: rrelayerbob@gmail.com -Received: by 2002:a50:45c6:0:b0:264:9270:cc66 with SMTP id c6csp1312972ecu; - Mon, 12 Aug 2024 07:26:02 -0700 (PDT) -X-Received: by 2002:a05:6214:4388:b0:6b0:90b4:1ca9 with SMTP id 6a1803df08f44-6bf4f66c992mr4233056d6.6.1723472762703; - Mon, 12 Aug 2024 07:26:02 -0700 (PDT) -ARC-Seal: i=1; a=rsa-sha256; t=1723472762; cv=none; +Received: by 2002:a05:6400:2670:b0:264:9270:cc66 with SMTP id jy48csp750570ecb; + Wed, 28 Aug 2024 00:53:22 -0700 (PDT) +X-Received: by 2002:a05:6214:5b0a:b0:6bf:8cb9:420 with SMTP id 6a1803df08f44-6c3362e69d4mr13631006d6.28.1724831602260; + Wed, 28 Aug 2024 00:53:22 -0700 (PDT) +ARC-Seal: i=1; a=rsa-sha256; t=1724831602; cv=none; d=google.com; s=arc-20160816; - b=vcvdTjMIx6jVaoyYdgwc/lQQLkGzwzJkejj++NHvdRMrTAUrmBcq4P3uRlETlg4QI/ - YZ5UTmDkqn+nsu0P1zNU9zoe8CiQk6bJQnaKsEqN2o6GAVf28p1K2b5HGxYYjbwPPjns - pVrwGO4oV3m91G7tDoKAcHTJ02K1yz4Ge7uOQd2LF3c8a/6m4PIxCD30+5gg1/qWjAfk - Obt40LmhxX4/yfqcuWf0F3SdXl3krrABX343FDLAewOw07JQwQHIQlBY+Y+2qolt0Grc - xuLA3sOJVUC5uNOaDJzA1G4TisX9DIL/tfjweqF/d9Q5va13JvruTptBqRowwilXqX5P - CLZw== + b=XHYkOsPNkoh6Z/JlWBpmbftiVMlvTrQleXhtrViXaWIdnKIvwTAPXdaICvrHd/Gx6P + 3oVxnHb8Cuhi3cJ+ctMJ40RfT+f2+DRWXG2Csihny9ayIWeJ4mhPEy1Y6ZXkCEr3Gud8 + mVeHCXLthekdgQly8uhWxC6vn3wXtCEvx49iJM0gyfcyAI4Nt1eYDS0hr2gH6XNY4F+Q + mEiHfFbZj6s95egRsp2ZipfGz7yojKoKDTUWupDDPF4YpM9TxHrKyqVhk7mgT5PHaAWm + xkgUr8fWf+2/a+ini06UovznocNQCwGEHbRyPcIQ8SssjV21Xh0P3vwUSdKQKZYJJvTD + wUIg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=to:subject:message-id:date:from:mime-version:dkim-signature; - bh=dAcGAR4My5Lv5E1hBYZpLjIn3d3hkexlA4mfbt+aoNg=; + bh=wfoUswYdacWo98C2NPNyTM5htDNHLSMk6+9pc8+wtuY=; fh=OYPr0JdXtRRlM3Htj/E6+khbD9huvtdqkRTmPoW0wko=; - b=id73VLGPEdCHON1/1BV42acaxe0SQj63bwZMLiqIPI/ull9ayrGcrO77bSO37r2v1t - XiP4Bnb+5+EmBOOno3mOJwwlE5F+MoD1OXJOtYnaRw/wsjz0x4O+OYyfFL9v7SPkfQDV - NhjnFbxOyrXs9rfsfi79misBR4LUmJQCdgiLs+9QWDUq0ckYtOrXJUs6fke3yijTZEkm - +oECBc4vh5apTkfJXlerRzjRD8UQUdPfHW5lO3/JHRJj4MIgumabeb97gcBoh++VcEZO - qTX8uOx0/j+0fl/Qz1WPnKEsHlN/OitzCbDxjZ4KiIYSGKM13Jm0rEwmQl3fkaP2zeZK - be9g==; + b=H8LtD87fAH1qc7wN2hssgsTYQ10genWfiNzNwL+379b1AP/AMubRu3Ekep9uRlkgle + K7AUiX3xkCtX0jmtFAm4EgRqByv9EgAaedHzXbrc/jOBWUq7g0zJM+uk6QffM7ETNNvt + aI263gbooi401SQK+epiPYQ+yF4nXX6cNidm/HyGR0LzxEKWio3NPblQUxihlUKbwN/T + 4MWYgM18G4/ebFrdkbe9707tKHDK6P/BuK9P4mWH6DJwHmGVK40Pyu7xKCtr0/L74/Zu + U04dV6Rho5On1vS0fiEjdhSJdQ5Co1UkyMhn2s8Oalxk9pm95yN6EIcMM+81l2l6dI4+ + GkbA==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; - dkim=pass header.i=@gmail.com header.s=20230601 header.b=AbjyPZA+; + dkim=pass header.i=@gmail.com header.s=20230601 header.b=kOFwnIGQ; spf=pass (google.com: domain of emailwalletrelayer987@gmail.com designates 209.85.220.41 as permitted sender) smtp.mailfrom=emailwalletrelayer987@gmail.com; dmarc=pass (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com; dara=pass header.i=@gmail.com Return-Path: Received: from mail-sor-f41.google.com (mail-sor-f41.google.com. [209.85.220.41]) - by mx.google.com with SMTPS id 6a1803df08f44-6bd82e7d133sor35657466d6.10.2024.08.12.07.26.02 + by mx.google.com with SMTPS id 6a1803df08f44-6c162e8aa5dsor71159546d6.9.2024.08.28.00.53.22 for (Google Transport Security); - Mon, 12 Aug 2024 07:26:02 -0700 (PDT) + Wed, 28 Aug 2024 00:53:22 -0700 (PDT) Received-SPF: pass (google.com: domain of emailwalletrelayer987@gmail.com designates 209.85.220.41 as permitted sender) client-ip=209.85.220.41; Authentication-Results: mx.google.com; - dkim=pass header.i=@gmail.com header.s=20230601 header.b=AbjyPZA+; + dkim=pass header.i=@gmail.com header.s=20230601 header.b=kOFwnIGQ; spf=pass (google.com: domain of emailwalletrelayer987@gmail.com designates 209.85.220.41 as permitted sender) smtp.mailfrom=emailwalletrelayer987@gmail.com; dmarc=pass (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com; dara=pass header.i=@gmail.com DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; - d=gmail.com; s=20230601; t=1723472762; x=1724077562; dara=google.com; + d=gmail.com; s=20230601; t=1724831601; x=1725436401; dara=google.com; h=to:subject:message-id:date:from:mime-version:from:to:cc:subject :date:message-id:reply-to; - bh=dAcGAR4My5Lv5E1hBYZpLjIn3d3hkexlA4mfbt+aoNg=; - b=AbjyPZA+Xu7/aQThoDilu0ukN07IBlywWk8F4S+yMXD6q+GOaXB90gTU2subhJts26 - H5SYRTNnvcXMqPOJtb88TzynpxUpej5DLR48ROGtpR7X3q1iQQyRRRZFEUOSS6Utxt7D - S1Jwx2kGOu/OSN3xamxMyuAWoi7ikWQJSjJ9g/b2IWmIly9v2FE+gl8doPPrZCumR3/F - 1WwDcqh6aL0MLgUPy5Uz+FVlU4aK3Md2SFKDRipW9whWoxun6dSjCHMLiZZSL0bV/ZHJ - loxolU3peA+0YhVd/Q7ZISaQ4DTg/qjNtVI6OmK09P475EhMNX39et6189GgbwVndGFq - hgCw== + bh=wfoUswYdacWo98C2NPNyTM5htDNHLSMk6+9pc8+wtuY=; + b=kOFwnIGQ8CiIR0+qd/kNcIr9AyL0k/UMnrM2nWMnTJPHgOVyYzJt1v9dGuP1MqNvrA + kHAigFUd6kPv6vYZna755EG9ciFpvWXvdb0IgigVfKH35jBxKA2kTdGfbgkfHlZA3KSj + fwsytkQrkSVFpp/5ZuOIZ7WWqRbLYjJ75ICYYjmzpCQwjAA6MDpMvSpYHOlxmXxBzE+c + 9VPDfEIhD8HsVMgTB3NSYRPkSX7M8Sl3aFOUWhNW6KU7W5yqgNXq28P2PiyiFGzsh2B3 + dTx6SR/HadkLihJGpkC5C8qNgeCoUf1crLTNjbGESClqzNHIyjJ5Q6wt3MuKlgu80olN + xJmg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; - d=1e100.net; s=20230601; t=1723472762; x=1724077562; + d=1e100.net; s=20230601; t=1724831601; x=1725436401; h=to:subject:message-id:date:from:mime-version:x-gm-message-state :from:to:cc:subject:date:message-id:reply-to; - bh=dAcGAR4My5Lv5E1hBYZpLjIn3d3hkexlA4mfbt+aoNg=; - b=Q8hiZxgOauaTndhqxya7bEZJoH/1U8K7nrW/yizA+fUpTGrejdh3/SfmTb/OJNHpZQ - fTsGJqEHEDIhJds1PdK6/qbMD/C1OY88BVScJ/UauzGid9Gis9AbpmwzOARnhV8+HgaL - kWQBl36PHOhbXtaX8UxEQrk7jE83lgXqvDVjdcE618P505L92eT+EzTCM+jWB3ShAK7i - 92e57Qr7M4JhLIj/W0BUoRjEDl7GEpjv4uLeHoIPONI9UOt7rYlKTEZtBJuCCt8I5tuO - zMY2T2QYcOASwc2TCWJ3rijf8WZaoxpSK4cJFtq7Drz8xSFHPfdcIc+onRVF5wQbVEbm - eZGg== -X-Gm-Message-State: AOJu0Yw7LeKO/8TWPHdDWwfvkXjVBzc7O37fDC2MDvtMq/djeJ1X+zXy - t90nn2xwzgEV3VUxU4CsA3Dfz4sJgUtgwkhOseIQ5pWRGWJc2DEOHZaqosy+EKtcd74uRf9Z4O/ - oBDfsRhNOdyvhHyJqjnkmedzxZN/y -X-Google-Smtp-Source: AGHT+IFMoDCfSFZzXjpvIXfeTd4IrAJgBhwtjJEGUhl8i59puhKhpNeQizDSDGG/AOZxbcx06wG5xnYU/yO39Qn8VJ0= -X-Received: by 2002:a05:6214:5f0b:b0:6b7:980b:e0ac with SMTP id - 6a1803df08f44-6bf4f7e2315mr3150516d6.32.1723472761940; Mon, 12 Aug 2024 - 07:26:01 -0700 (PDT) + bh=wfoUswYdacWo98C2NPNyTM5htDNHLSMk6+9pc8+wtuY=; + b=P8nKrBqskVA91k6q3XbmEdSc5BVCGGg2Utz+Ire/PS7MN3jnT1sQ5HxVqnWq/HiiDS + tVz4/wRFtUDmkkCNEzQ4DzLr2674IcS+vQ23hRaovrYEKos9yu2TKyV9xtNI/+zP3NNU + BmtuwCazEpMzK1BmOZ4NDX+Jd3DAha1uodiTxyfmKNxpoVg1QTluPlKtWALVc56Rk2tT + sGZXH6rpDalEF32vsi4hcGj7sA9n7pZIexLtjd1BZSILtJ0c0ImA2yyZIJwotFV/7Yn4 + DvC0VgQj5dtdrSI707fhUYXPZ+z1tEaVtINPNKz7X9Cy7TmTZ8dOdj05DaTA4eCixgFM + mgPQ== +X-Gm-Message-State: AOJu0YwrGQCEe9+PJ6Zepa+H3q3Cq2LyydJZATE3K+17Co3Iom94DUhW + vD7YwjhQbaLqC1niaQEOlycvMfVNNnTluvbVzbuYqqE/Kk5SBwcM7zJBcW5OBmSMV81PM0fPTWK + mLt1/FTNquOh5CWZ8GAsjw2p44F0k +X-Google-Smtp-Source: AGHT+IF9+HGRRMj6SjPxYPjB7WJiyoxKQSEitAal6QSa9IbQzWwfeh3hSAdLj9EMUwnFSJLGcixB1gy28cyAeKaW51A= +X-Received: by 2002:a05:6214:4306:b0:6bf:7474:348f with SMTP id + 6a1803df08f44-6c3362d2fa8mr9836466d6.21.1724831601609; Wed, 28 Aug 2024 + 00:53:21 -0700 (PDT) MIME-Version: 1.0 From: "emailwallet.relayer.2" -Date: Mon, 12 Aug 2024 23:25:51 +0900 -Message-ID: -Subject: Accept guardian request for 0x05A78D3dB903a58B5FA373E07e5044B95B12aec4 +Date: Wed, 28 Aug 2024 16:53:10 +0900 +Message-ID: +Subject: Accept guardian request for 0x7c5E4b26643682AF77A196781A851c9Fe769472d Code 1162ebff40918afe5305e68396f0283eb675901d0387f97d21928d423aaa0b54 To: rrelayerbob@gmail.com -Content-Type: multipart/alternative; boundary="000000000000c1738a061f7d45eb" +Content-Type: multipart/alternative; boundary="000000000000e9559a0620b9a62f" ---000000000000c1738a061f7d45eb +--000000000000e9559a0620b9a62f Content-Type: text/plain; charset="UTF-8" ---000000000000c1738a061f7d45eb +--000000000000e9559a0620b9a62f Content-Type: text/html; charset="UTF-8"

---000000000000c1738a061f7d45eb-- +--000000000000e9559a0620b9a62f-- diff --git a/packages/contracts/test/emails/300/recovery.eml b/packages/contracts/test/emails/300/recovery.eml index 59d13622..5b795415 100644 --- a/packages/contracts/test/emails/300/recovery.eml +++ b/packages/contracts/test/emails/300/recovery.eml @@ -1,90 +1,90 @@ Delivered-To: rrelayerbob@gmail.com -Received: by 2002:a50:45c6:0:b0:264:9270:cc66 with SMTP id c6csp1314270ecu; - Mon, 12 Aug 2024 07:28:30 -0700 (PDT) -X-Received: by 2002:a05:6902:2082:b0:e0e:cb25:c40f with SMTP id 3f1490d57ef6-e113d27bb49mr644234276.37.1723472910560; - Mon, 12 Aug 2024 07:28:30 -0700 (PDT) -ARC-Seal: i=1; a=rsa-sha256; t=1723472910; cv=none; +Received: by 2002:a05:6400:2670:b0:264:9270:cc66 with SMTP id jy48csp750751ecb; + Wed, 28 Aug 2024 00:54:02 -0700 (PDT) +X-Received: by 2002:a05:6102:26c9:b0:492:a9f8:4a71 with SMTP id ada2fe7eead31-49a4ed30da9mr1309811137.8.1724831642136; + Wed, 28 Aug 2024 00:54:02 -0700 (PDT) +ARC-Seal: i=1; a=rsa-sha256; t=1724831642; cv=none; d=google.com; s=arc-20160816; - b=CatZtuL6lj1FU5iyQZyRHoqbpEJgvWil6eq/wWz0NfA0iY8NgqUqTOFdBHXF0phsI+ - a0uuFKHnOVJboPJbKEsQGFL1EIM2XNUP65EdUzHDqH4s/d4bl27bxRScMp1DExVM3SoG - tq+7XKb01CGjK4ydT90FhmIEWyHG7wEFDJaOqSUAjiG6V7wEkj5eXuiFPQqVWpQWrdlf - 0Pno03YAhrqPow5jhbTwG0hXl/F4OXtXU+jaO2QKIZJ2bB8eFFcHVsOHSsw5M7gjIZ13 - s2blLt12Fbzy8sX906g0Gr+zHMB67EwzM80GZoip05s8T5L1K+54e69iujlfjJGOcjVJ - P1nA== + b=cpTbX87GQyaBGaVXbNadJs8tRJ3H6JwDzUCyAR6zbLifi7OvRIHe/CqXAdOjD88MEN + S5Vri34IQ0pQX+/0KPKejM0sKlvuQXSca6+H87JzalG4kkXOChT58Wjm5zrWcY9Xslh5 + s275+Zh5T6gx7IBZZZtXLcOgtRgZgsHhpF2DkzgSiRh1FtJC35ewsspFC1VtqWZqUAZh + +41nK1p++/vFWBqpB3YZXSdWFDGsyJ7QtHFfi3vG/x8b/h6c4vHrvT0zx5lDwZB+YmV+ + xXROwXhRH2ND5375vtZz8OyfaQHhY43HVSP4dOfrHgeUbtVzcQiYW+SR3Q6FPHCUqStc + EycQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=to:subject:message-id:date:from:mime-version:dkim-signature; - bh=KcwTRtPiKk2j+vxpSLodKakyPE1kwRe/deHc2g8j5Bo=; + bh=IehaSA/JR4L/KX7NlPKK5B7zTFO/0C8UNbHTPhhIN84=; fh=OYPr0JdXtRRlM3Htj/E6+khbD9huvtdqkRTmPoW0wko=; - b=eRHm8dXx/CK2Dme025d2LDa51KZFZ5K5XhH/ffIUFez1PV5nYn/bNewv8L8ZiE5/xd - 7zh/wtjB95Wv4BXabZ+hFgMb9KDcBWoz5E17LN/FgvNuV+E7wHALYvCQm9lGYR5qfw/G - GU4QyoX5m/a7OVw2eiG+9+kF1x6mVGZsY208Yc272/RSQrfau0qQCQCACAM/EEMCwdvL - tXPSF80JlkyznrF7HDkl0zdy7D0+oYXYlluZIadDSd0S2GiqoYkD91be+xSh7GRl1c/9 - nbsJV8ifeOlXLIMuYg9rWO1Ii0fmfp6OGrLEPvkTyBIReWs552JuX1ALXXAG2qz8qVxG - kAGw==; + b=OUQ5TcZbiTNRl396nTBZH0uojnYZfc5tChwa5yntVLuQouy9qXfZXnKWHKNwRb/MiT + P2sVT7nG4DDbzSMdsOi7fG06vsBJfq4Oetaco5s1zyL7T7dCC0L4PnwJ2sRQbpSoh/Zb + YAknwfYkp1F0bjeYw2HBdfqlT93kzDLe7sI6zk2uADL1gp9oUMzKpotSMIrbzBIX6nGv + 26ASehd6ceNdxqCKCFCagi8WOuQQBNjN8E+G2m+7Zx1YjGif1LTgmzh6RxtpYT/M02gW + 4eidLLTEv6bQzFwKo9rwJq3iD1A9H9HESrEJGGWjAd5dTDxOLRQXge3igywzFjOnwPq0 + 9huw==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; - dkim=pass header.i=@gmail.com header.s=20230601 header.b=kDV3WaYT; + dkim=pass header.i=@gmail.com header.s=20230601 header.b=XTkwK+MJ; spf=pass (google.com: domain of emailwalletrelayer987@gmail.com designates 209.85.220.41 as permitted sender) smtp.mailfrom=emailwalletrelayer987@gmail.com; dmarc=pass (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com; dara=pass header.i=@gmail.com Return-Path: Received: from mail-sor-f41.google.com (mail-sor-f41.google.com. [209.85.220.41]) - by mx.google.com with SMTPS id 3f1490d57ef6-e0ec8ba44efsor3812256276.2.2024.08.12.07.28.30 + by mx.google.com with SMTPS id ada2fe7eead31-498e47e8b90sor1864165137.5.2024.08.28.00.54.02 for (Google Transport Security); - Mon, 12 Aug 2024 07:28:30 -0700 (PDT) + Wed, 28 Aug 2024 00:54:02 -0700 (PDT) Received-SPF: pass (google.com: domain of emailwalletrelayer987@gmail.com designates 209.85.220.41 as permitted sender) client-ip=209.85.220.41; Authentication-Results: mx.google.com; - dkim=pass header.i=@gmail.com header.s=20230601 header.b=kDV3WaYT; + dkim=pass header.i=@gmail.com header.s=20230601 header.b=XTkwK+MJ; spf=pass (google.com: domain of emailwalletrelayer987@gmail.com designates 209.85.220.41 as permitted sender) smtp.mailfrom=emailwalletrelayer987@gmail.com; dmarc=pass (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com; dara=pass header.i=@gmail.com DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; - d=gmail.com; s=20230601; t=1723472910; x=1724077710; dara=google.com; + d=gmail.com; s=20230601; t=1724831641; x=1725436441; dara=google.com; h=to:subject:message-id:date:from:mime-version:from:to:cc:subject :date:message-id:reply-to; - bh=KcwTRtPiKk2j+vxpSLodKakyPE1kwRe/deHc2g8j5Bo=; - b=kDV3WaYTvmK4uve2kN1AAt12vmS7jERDYJMUCWnA0AUhwYCw3UZ1POGrTyH4w3kw8c - g9+zBmjo+5eAGncdODLkuU7yDtW6AbjHUa+vJUxYXiDyh0wYZMt908iS7WJ0lJ9PDwEN - 1aF6Uu+vdtbWRA/kVrU9hup/DP8Le1d6h0mKyge1pbPA6swJqUr0XRrJ5F5Hp0xf/y5M - PKXHCKDvvxf2tKKyD2TKmXm6R9yE4aWkGvMrNCumpUfk+w//uB3J+jtldBBH8fqhPl0Q - pmMePv4xfOgPyhV94TxmsMa2gyVLyKeN4QyKMcbDFFTT7IizvwHlNk3wTzWFFu1Gh0Iz - dWLA== + bh=IehaSA/JR4L/KX7NlPKK5B7zTFO/0C8UNbHTPhhIN84=; + b=XTkwK+MJbzJP7Sy/I5oNWMkuJ7jrQZc5ovy3+4meyBwreJFQ1+F5UZOtUKd1svPC+u + v3TNvLvzlUTeXHW7TujG7U2WOxNOqqyYF18mgBIRlw5FXtgjz9j+bbALp0fo61e7gKhS + PHiRPocLWJpLgsMFy5nHod39aqcLWv0uMkCzwveMnDzlZYQFVxEKotyumIHUKLPIj2DI + BhOM5LhSml1qRzpB3eKpXGzEaLnyesPrOQNPwKB4ZfiSaP3xPYNfeAZhwohVEBTigi0h + ZnrH0DwfaJ6P1UdTTfG1YPfY1qm8BpEi4qFrYUUeZL1fK341NNq0y08egz0R6Q/CPJKL + l0KA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; - d=1e100.net; s=20230601; t=1723472910; x=1724077710; + d=1e100.net; s=20230601; t=1724831641; x=1725436441; h=to:subject:message-id:date:from:mime-version:x-gm-message-state :from:to:cc:subject:date:message-id:reply-to; - bh=KcwTRtPiKk2j+vxpSLodKakyPE1kwRe/deHc2g8j5Bo=; - b=oOYZjv7O3cgWFRDTHsdDksv1YCNyim0fd1MAlCEfA3szcL/ZJ7mz0R/5Yn5nw1BaSw - 6Z3GArs0uRjMTaqt0e0N1ndFQszK9kyHMZcQl1yC87A0GLcNJsd3Qwdnj765sQ/eVZmZ - sYeKKN2gUXvUMNxi0nq+a46izWdgYuVGD1GsIrWG9Z4C7/5R9qitls5IdZISTqW5rgwS - 4uovtFP78jD5w48SO5pusBYRFdG1LDCXJCSChM+OVQQIlgAsBEFWHPMI6Qe2IDSWp4zo - r3Hz8qi7WKtqF7W+2u+HgMDncdTbebFChirFYY3mNz1GNJ1qf/ks1d6Mv9D0KX4AB0jG - XMeQ== -X-Gm-Message-State: AOJu0YzzxHkXhnzAC1IM5u8jXOGWk1Xzrjka0M17SqnYJOY/UBZyo1bS - PQeHJKUplBNXFONu83fwYnmugr6SmRpJATslK/OcLy/tpiTd7GleyRS4wD3i9aMIRWU4TkgIAbp - 2q75XCN63+yFL/igOfdzsk6oihLjG -X-Google-Smtp-Source: AGHT+IFAWN5D9YSAVPoZbZaChqpvELVXAJFi1kcX+rzDehga6v9dV16xiu4JOgws6BCv8zr0cZOa75ffs1puG6Llm3w= -X-Received: by 2002:a05:6902:188c:b0:e08:6ce9:6e8e with SMTP id - 3f1490d57ef6-e113cec32bcmr556650276.23.1723472909924; Mon, 12 Aug 2024 - 07:28:29 -0700 (PDT) + bh=IehaSA/JR4L/KX7NlPKK5B7zTFO/0C8UNbHTPhhIN84=; + b=tlCjTjy8XCtY9U/SDWIKjLwWqYynJdQR9tV9VMWBcf37cDm9UpUVw5Ey9aScTxPLq+ + gadkjTGfA6ljpL8UsTbM1A2/b296DjfVZ2bYiCzZ58z/Httf8lSd9nInC1Tmd0iRIm3O + 8L/X9hWHkcbPGm8EhXNRiUKNBAFxshxpr24+jE2gWoPa7FVUmD1uY/nlYKMRGvvARqkq + RGQ9qpCa8LnWEPyRsE8AaV/g16kksprSEOoZocT3bwL+r/4O+s45ZNEHn9X0jEFPmb4U + 18/92ludL29pZ/PYgmz39iKbTEucY/7lZz3oKq2iNmWZPVKSjEf5kfYKMzIycQauxeN8 + lBDw== +X-Gm-Message-State: AOJu0YwP8OnzV4z4jcMcybAydVxa6xFnitsmWGjgRtqw9ENgSBo5ki6u + RGO9v2uhgEZvUHgR0fuGEYNoi7DCCDKFWePFH0ZVWNRGylHSvGShhFUZfztJ/KnOeUFE3KO+JyG + 8/1vz6i+Tu9quIYwBMxTsxItL1LJD +X-Google-Smtp-Source: AGHT+IFGI8kUAMWkx4LUP4o9Lv0V2QzLQJZlC8zeOQeO/P2VdPkPSYJrFwDo+q0Ez3VMetqJIH7d4YaBQI3E5y+sAWA= +X-Received: by 2002:a05:6102:c0b:b0:498:e21c:cc66 with SMTP id + ada2fe7eead31-49a4ecd79bemr1392977137.6.1724831641483; Wed, 28 Aug 2024 + 00:54:01 -0700 (PDT) MIME-Version: 1.0 From: "emailwallet.relayer.2" -Date: Mon, 12 Aug 2024 23:28:18 +0900 -Message-ID: -Subject: Set the new signer of 0x05A78D3dB903a58B5FA373E07e5044B95B12aec4 to +Date: Wed, 28 Aug 2024 16:53:50 +0900 +Message-ID: +Subject: Set the new signer of 0x7c5E4b26643682AF77A196781A851c9Fe769472d to 0xa0Ee7A142d267C1f36714E4a8F75612F20a79720 Code 1162ebff40918afe5305e68396f0283eb675901d0387f97d21928d423aaa0b54 To: rrelayerbob@gmail.com -Content-Type: multipart/alternative; boundary="00000000000093925d061f7d4e6d" +Content-Type: multipart/alternative; boundary="00000000000049c5260620b9a9a6" ---00000000000093925d061f7d4e6d +--00000000000049c5260620b9a9a6 Content-Type: text/plain; charset="UTF-8" ---00000000000093925d061f7d4e6d +--00000000000049c5260620b9a9a6 Content-Type: text/html; charset="UTF-8"

---00000000000093925d061f7d4e6d-- +--00000000000049c5260620b9a9a6-- diff --git a/packages/contracts/test/helpers/DeploymentHelper.sol b/packages/contracts/test/helpers/DeploymentHelper.sol index b4bcc5c5..b96894d1 100644 --- a/packages/contracts/test/helpers/DeploymentHelper.sol +++ b/packages/contracts/test/helpers/DeploymentHelper.sol @@ -5,17 +5,22 @@ import "forge-std/Test.sol"; import "forge-std/console.sol"; import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; -import "../../src/EmailAuth.sol"; -import "../../src/utils/Verifier.sol"; -import "../../src/utils/Groth16Verifier.sol"; -import "../../src/utils/ECDSAOwnedDKIMRegistry.sol"; -// import "../../src/utils/ForwardDKIMRegistry.sol"; +import {EmailAuth, EmailAuthMsg} from "../../src/EmailAuth.sol"; +import {Verifier, EmailProof} from "../../src/utils/Verifier.sol"; +import {Groth16Verifier} from "../../src/utils/Groth16Verifier.sol"; +import {ECDSAOwnedDKIMRegistry} from "../../src/utils/ECDSAOwnedDKIMRegistry.sol"; import {UserOverrideableDKIMRegistry} from "@zk-email/contracts/UserOverrideableDKIMRegistry.sol"; -import "./SimpleWallet.sol"; -import "./RecoveryController.sol"; +import {SimpleWallet} from "./SimpleWallet.sol"; +import {RecoveryController, EmailAccountRecovery} from "./RecoveryController.sol"; import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +// // FOR_ZKSYNC:START +// import {ZKSyncCreate2Factory} from "../../src/utils/ZKSyncCreate2Factory.sol"; +// import "../../src/utils/ForwardDKIMRegistry.sol"; +// import {RecoveryControllerZKSync, EmailAccountRecoveryZKSync} from "./RecoveryControllerZKSync.sol"; +// // FOR_ZKSYNC:END + contract DeploymentHelper is Test { using ECDSA for *; @@ -27,6 +32,10 @@ contract DeploymentHelper is Test { SimpleWallet simpleWalletImpl; SimpleWallet simpleWallet; + // // FOR_ZKSYNC:START + // RecoveryControllerZKSync recoveryControllerZKSync; + // // FOR_ZKSYNC:END + address deployer = vm.addr(1); address receiver = vm.addr(2); address guardian; @@ -134,11 +143,39 @@ contract DeploymentHelper is Test { // Create SimpleWallet simpleWalletImpl = new SimpleWallet(); + address recoveryControllerAddress = address(recoveryController); + + // // FOR_ZKSYNC:START + // // Create zkSync Factory implementation + // if (isZkSync()) { + // ZKSyncCreate2Factory factoryImpl = new ZKSyncCreate2Factory(); + // // Create RecoveryControllerZKSync as EmailAccountRecovery implementation + // RecoveryControllerZKSync recoveryControllerZKSyncImpl = new RecoveryControllerZKSync(); + // ERC1967Proxy recoveryControllerZKSyncProxy = new ERC1967Proxy( + // address(recoveryControllerZKSyncImpl), + // abi.encodeCall( + // recoveryControllerZKSyncImpl.initialize, + // ( + // signer, + // address(verifier), + // address(dkim), + // address(emailAuthImpl), + // address(factoryImpl) + // ) + // ) + // ); + // recoveryControllerZKSync = RecoveryControllerZKSync( + // payable(address(recoveryControllerZKSyncProxy)) + // ); + // recoveryControllerAddress = address(recoveryControllerZKSync); + // } + // // FOR_ZKSYNC:END + ERC1967Proxy simpleWalletProxy = new ERC1967Proxy( address(simpleWalletImpl), abi.encodeCall( simpleWalletImpl.initialize, - (signer, address(recoveryController)) + (signer, recoveryControllerAddress) ) ); simpleWallet = SimpleWallet(payable(address(simpleWalletProxy))); @@ -147,6 +184,33 @@ contract DeploymentHelper is Test { // Set guardian address guardian = EmailAccountRecovery(address(recoveryController)) .computeEmailAuthAddress(address(simpleWallet), accountSalt); + // // FOR_ZKSYNC:START + // if (isZkSync()) { + // guardian = EmailAccountRecoveryZKSync(address(recoveryControllerZKSync)) + // .computeEmailAuthAddress(address(simpleWallet), accountSalt); + // } + // // FOR_ZKSYNC:END + vm.stopPrank(); } + + function isZkSync() public view returns (bool) { + return block.chainid == 324 || block.chainid == 300; + } + + function skipIfZkSync() public { + if (isZkSync()) { + vm.skip(true); + } else { + vm.skip(false); + } + } + + function skipIfNotZkSync() public { + if (!isZkSync()) { + vm.skip(true); + } else { + vm.skip(false); + } + } } diff --git a/packages/contracts/test/helpers/RecoveryControllerZKSync.sol b/packages/contracts/test/helpers/RecoveryControllerZKSync.sol new file mode 100644 index 00000000..14e15a9c --- /dev/null +++ b/packages/contracts/test/helpers/RecoveryControllerZKSync.sol @@ -0,0 +1,194 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.12; + +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import {EmailAccountRecoveryZKSync} from "../../src/EmailAccountRecoveryZKSync.sol"; +import {Address} from "@openzeppelin/contracts/utils/Address.sol"; +import {SimpleWallet} from "./SimpleWallet.sol"; + +contract RecoveryControllerZKSync is + OwnableUpgradeable, + EmailAccountRecoveryZKSync +{ + enum GuardianStatus { + NONE, + REQUESTED, + ACCEPTED + } + uint public constant DEFAULT_TIMELOCK_PERIOD = 3 days; + + mapping(address => bool) public isActivatedOfAccount; + mapping(address => bool) public isRecovering; + mapping(address => address) public newSignerCandidateOfAccount; + mapping(address => GuardianStatus) public guardians; + mapping(address => uint) public timelockPeriodOfAccount; + mapping(address => uint) public currentTimelockOfAccount; + + // modifier onlyNotRecoveringOwner() { + // require(msg.sender == owner(), "only owner"); + // require(!isRecovering, "recovery in progress"); + // _; + // } + + constructor() {} + + function initialize( + address _initialOwner, + address _verifier, + address _dkim, + address _emailAuthImplementation, + address _factory + ) public initializer { + __Ownable_init(_initialOwner); + verifierAddr = _verifier; + dkimAddr = _dkim; + emailAuthImplementationAddr = _emailAuthImplementation; + factoryAddr = _factory; + } + + function isActivated( + address recoveredAccount + ) public view override returns (bool) { + return isActivatedOfAccount[recoveredAccount]; + } + + function acceptanceCommandTemplates() + public + pure + override + 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 recoveryCommandTemplates() + public + pure + override + returns (string[][] memory) + { + string[][] memory templates = new string[][](1); + templates[0] = new string[](8); + templates[0][0] = "Set"; + templates[0][1] = "the"; + templates[0][2] = "new"; + templates[0][3] = "signer"; + templates[0][4] = "of"; + templates[0][5] = "{ethAddr}"; + templates[0][6] = "to"; + templates[0][7] = "{ethAddr}"; + return templates; + } + + function extractRecoveredAccountFromAcceptanceCommand( + bytes[] memory commandParams, + uint templateIdx + ) public pure override returns (address) { + require(templateIdx == 0, "invalid template index"); + require(commandParams.length == 1, "invalid command params"); + return abi.decode(commandParams[0], (address)); + } + + function extractRecoveredAccountFromRecoveryCommand( + bytes[] memory commandParams, + uint templateIdx + ) public pure override returns (address) { + require(templateIdx == 0, "invalid template index"); + require(commandParams.length == 2, "invalid command params"); + return abi.decode(commandParams[0], (address)); + } + + function requestGuardian(address guardian) public { + address account = msg.sender; + require(!isRecovering[account], "recovery in progress"); + require(guardian != address(0), "invalid guardian"); + require( + guardians[guardian] == GuardianStatus.NONE, + "guardian status must be NONE" + ); + if (!isActivatedOfAccount[account]) { + isActivatedOfAccount[account] = true; + } + guardians[guardian] = GuardianStatus.REQUESTED; + } + + function configureTimelockPeriod(uint period) public { + timelockPeriodOfAccount[msg.sender] = period; + } + + function acceptGuardian( + address guardian, + uint templateIdx, + bytes[] memory commandParams, + bytes32 + ) internal override { + address account = abi.decode(commandParams[0], (address)); + require(!isRecovering[account], "recovery in progress"); + require(guardian != address(0), "invalid guardian"); + + require( + guardians[guardian] == GuardianStatus.REQUESTED, + "guardian status must be REQUESTED" + ); + require(templateIdx == 0, "invalid template index"); + require(commandParams.length == 1, "invalid command params"); + guardians[guardian] = GuardianStatus.ACCEPTED; + } + + function processRecovery( + address guardian, + uint templateIdx, + bytes[] memory commandParams, + bytes32 + ) internal override { + address account = abi.decode(commandParams[0], (address)); + require(!isRecovering[account], "recovery in progress"); + require(guardian != address(0), "invalid guardian"); + require( + guardians[guardian] == GuardianStatus.ACCEPTED, + "guardian status must be ACCEPTED" + ); + require(templateIdx == 0, "invalid template index"); + require(commandParams.length == 2, "invalid command params"); + address newSignerInEmail = abi.decode(commandParams[1], (address)); + require(newSignerInEmail != address(0), "invalid new signer"); + isRecovering[account] = true; + newSignerCandidateOfAccount[account] = newSignerInEmail; + currentTimelockOfAccount[account] = + block.timestamp + + timelockPeriodOfAccount[account]; + } + + function rejectRecovery() public { + address account = msg.sender; + require(isRecovering[account], "recovery not in progress"); + require( + currentTimelockOfAccount[account] > block.timestamp, + "timelock expired" + ); + isRecovering[account] = false; + newSignerCandidateOfAccount[account] = address(0); + currentTimelockOfAccount[account] = 0; + } + + function completeRecovery(address account, bytes memory) public override { + require(account != address(0), "invalid account"); + require(isRecovering[account], "recovery not in progress"); + require( + currentTimelockOfAccount[account] <= block.timestamp, + "timelock not expired" + ); + address newSigner = newSignerCandidateOfAccount[account]; + isRecovering[account] = false; + currentTimelockOfAccount[account] = 0; + newSignerCandidateOfAccount[account] = address(0); + SimpleWallet(payable(account)).changeOwner(newSigner); + } +} diff --git a/packages/contracts/test/script/ChangeOwnersScript.t.sol b/packages/contracts/test/script/ChangeOwnersScript.t.sol index 88c3f4e4..a64ef96e 100644 --- a/packages/contracts/test/script/ChangeOwnersScript.t.sol +++ b/packages/contracts/test/script/ChangeOwnersScript.t.sol @@ -8,9 +8,10 @@ import {Deploy} from "../../script/DeployCommons.s.sol"; import {Deploy as Deploy2} from "../../script/DeployForwardDKIMRegistry.s.sol"; import {ChangeOwners} from "../../script/ChangeOwners.s.sol"; import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import {StructHelper} from "../helpers/StructHelper.sol"; -contract ChangeOwnersScriptTest is Test { - function setUp() public { +contract ChangeOwnersScriptTest is StructHelper { + function setUp() public override { vm.setEnv( "PRIVATE_KEY", "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" @@ -20,6 +21,8 @@ contract ChangeOwnersScriptTest is Test { } function test_run() public { + skipIfZkSync(); + Deploy deploy = new Deploy(); deploy.run(); vm.setEnv("SOURCE_DKIM", vm.toString(vm.envAddress("ECDSA_DKIM"))); diff --git a/packages/contracts/test/script/ChangeSignerInECDSAOwnedDKIMRegistryScript.t.sol b/packages/contracts/test/script/ChangeSignerInECDSAOwnedDKIMRegistryScript.t.sol index cfa19156..46218861 100644 --- a/packages/contracts/test/script/ChangeSignerInECDSAOwnedDKIMRegistryScript.t.sol +++ b/packages/contracts/test/script/ChangeSignerInECDSAOwnedDKIMRegistryScript.t.sol @@ -7,9 +7,10 @@ import "forge-std/console.sol"; import {Deploy} from "../../script/DeployCommons.s.sol"; import {ChangeSigner} from "../../script/ChangeSignerInECDSAOwnedDKIMRegistry.s.sol"; import {ECDSAOwnedDKIMRegistry} from "../../src/utils/ECDSAOwnedDKIMRegistry.sol"; +import {StructHelper} from "../helpers/StructHelper.sol"; -contract ChangeSignerInECDSAOwnedDKIMRegistryScriptTest is Test { - function setUp() public { +contract ChangeSignerInECDSAOwnedDKIMRegistryScriptTest is StructHelper { + function setUp() public override { vm.setEnv( "PRIVATE_KEY", "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" @@ -19,6 +20,8 @@ contract ChangeSignerInECDSAOwnedDKIMRegistryScriptTest is Test { } function test_run() public { + skipIfZkSync(); + Deploy deploy = new Deploy(); deploy.run(); ChangeSigner changeSigner = new ChangeSigner(); diff --git a/packages/contracts/test/script/ChangeSourceInForwardDKIMRegistryScript.t.sol b/packages/contracts/test/script/ChangeSourceInForwardDKIMRegistryScript.t.sol index 0296578f..7b7e22b5 100644 --- a/packages/contracts/test/script/ChangeSourceInForwardDKIMRegistryScript.t.sol +++ b/packages/contracts/test/script/ChangeSourceInForwardDKIMRegistryScript.t.sol @@ -8,9 +8,10 @@ import {Deploy} from "../../script/DeployCommons.s.sol"; import {Deploy as Deploy2} from "../../script/DeployForwardDKIMRegistry.s.sol"; import {ChangeSource} from "../../script/ChangeSourceInForwardDKIMRegistry.s.sol"; import {ForwardDKIMRegistry} from "../../src/utils/ForwardDKIMRegistry.sol"; +import {StructHelper} from "../helpers/StructHelper.sol"; -contract ChangeSourceInForwardDKIMRegistryScriptTest is Test { - function setUp() public { +contract ChangeSourceInForwardDKIMRegistryScriptTest is StructHelper { + function setUp() public override { vm.setEnv( "PRIVATE_KEY", "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" @@ -20,6 +21,8 @@ contract ChangeSourceInForwardDKIMRegistryScriptTest is Test { } function test_run() public { + skipIfZkSync(); + Deploy deploy = new Deploy(); deploy.run(); vm.setEnv("SOURCE_DKIM", vm.toString(vm.envAddress("ECDSA_DKIM"))); diff --git a/packages/contracts/test/script/DeployCommonsScript.t.sol b/packages/contracts/test/script/DeployCommonsScript.t.sol index a6696c66..4767fd66 100644 --- a/packages/contracts/test/script/DeployCommonsScript.t.sol +++ b/packages/contracts/test/script/DeployCommonsScript.t.sol @@ -5,9 +5,10 @@ import "forge-std/Test.sol"; import "forge-std/console.sol"; import {Deploy} from "../../script/DeployCommons.s.sol"; +import {StructHelper} from "../helpers/StructHelper.sol"; -contract DeployCommonsScriptTest is Test { - function setUp() public { +contract DeployCommonsScriptTest is StructHelper { + function setUp() public override { vm.setEnv( "PRIVATE_KEY", "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" @@ -16,6 +17,8 @@ contract DeployCommonsScriptTest is Test { } function test_run() public { + skipIfZkSync(); + Deploy deploy = new Deploy(); deploy.run(); } diff --git a/packages/contracts/test/script/DeployRecoveryControllerScript.t.sol b/packages/contracts/test/script/DeployRecoveryControllerScript.t.sol index 00fbbe67..33294a01 100644 --- a/packages/contracts/test/script/DeployRecoveryControllerScript.t.sol +++ b/packages/contracts/test/script/DeployRecoveryControllerScript.t.sol @@ -5,10 +5,10 @@ import "forge-std/Test.sol"; import "forge-std/console.sol"; import {Deploy} from "../../script/DeployRecoveryController.s.sol"; -import "../helpers/StructHelper.sol"; +import {StructHelper} from "../helpers/StructHelper.sol"; -contract DeployRecoveryControllerScriptTest is Test { - function setUp() public { +contract DeployRecoveryControllerScriptTest is StructHelper { + function setUp() public override { vm.setEnv( "PRIVATE_KEY", "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" @@ -17,6 +17,8 @@ contract DeployRecoveryControllerScriptTest is Test { } function test_run() public { + skipIfZkSync(); + Deploy deploy = new Deploy(); deploy.run(); require( diff --git a/packages/contracts/test/script/DeploySimpleWalletScript.t.sol b/packages/contracts/test/script/DeploySimpleWalletScript.t.sol index 83219407..82bdcc2f 100644 --- a/packages/contracts/test/script/DeploySimpleWalletScript.t.sol +++ b/packages/contracts/test/script/DeploySimpleWalletScript.t.sol @@ -5,7 +5,7 @@ import "forge-std/Test.sol"; import "forge-std/console.sol"; import {Deploy} from "../../script/DeployCommons.s.sol"; -import "../helpers/StructHelper.sol"; +import {StructHelper} from "../helpers/StructHelper.sol"; contract DeploySimpleWalletScriptTest is StructHelper { function setUp() public override { @@ -23,29 +23,39 @@ contract DeploySimpleWalletScriptTest is StructHelper { } function test_run() public { + skipIfZkSync(); + Deploy deploy = new Deploy(); deploy.run(); } function test_run_no_dkim() public { + skipIfZkSync(); + vm.setEnv("DKIM", vm.toString(address(0))); Deploy deploy = new Deploy(); deploy.run(); } function test_run_no_verifier() public { + skipIfZkSync(); + vm.setEnv("VERIFIER", vm.toString(address(0))); Deploy deploy = new Deploy(); deploy.run(); } function test_run_no_email_auth() public { + skipIfZkSync(); + vm.setEnv("EMAIL_AUTH_IMPL", vm.toString(address(0))); Deploy deploy = new Deploy(); deploy.run(); } function test_run_no_simple_wallet() public { + skipIfZkSync(); + vm.setEnv("SIMPLE_WALLET_IMPL", vm.toString(address(0))); Deploy deploy = new Deploy(); deploy.run(); diff --git a/packages/contracts/test/script/RenounceOwnersScript.t.sol b/packages/contracts/test/script/RenounceOwnersScript.t.sol index cb0bbf0e..d2ec7274 100644 --- a/packages/contracts/test/script/RenounceOwnersScript.t.sol +++ b/packages/contracts/test/script/RenounceOwnersScript.t.sol @@ -8,9 +8,10 @@ import {Deploy} from "../../script/DeployCommons.s.sol"; import {Deploy as Deploy2} from "../../script/DeployForwardDKIMRegistry.s.sol"; import {RenounceOwners} from "../../script/RenounceOwners.s.sol"; import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import {StructHelper} from "../helpers/StructHelper.sol"; -contract RenounceOwnersScriptTest is Test { - function setUp() public { +contract RenounceOwnersScriptTest is StructHelper { + function setUp() public override { vm.setEnv( "PRIVATE_KEY", "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" @@ -19,6 +20,8 @@ contract RenounceOwnersScriptTest is Test { } function test_run() public { + skipIfZkSync(); + Deploy deploy = new Deploy(); deploy.run(); vm.setEnv("SOURCE_DKIM", vm.toString(vm.envAddress("ECDSA_DKIM"))); diff --git a/packages/contracts/test/script/UpgradesScript.t.sol b/packages/contracts/test/script/UpgradesScript.t.sol index 023bdc33..c6ac7b36 100644 --- a/packages/contracts/test/script/UpgradesScript.t.sol +++ b/packages/contracts/test/script/UpgradesScript.t.sol @@ -9,12 +9,13 @@ import {Deploy as Deploy2} from "../../script/DeployForwardDKIMRegistry.s.sol"; import {Upgrades} from "../../script/Upgrades.s.sol"; import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {StructHelper} from "../helpers/StructHelper.sol"; -contract UpgradesScriptTest is Test { +contract UpgradesScriptTest is StructHelper { uint256 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; - function setUp() public { + function setUp() public override { vm.setEnv( "PRIVATE_KEY", "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" @@ -23,6 +24,8 @@ contract UpgradesScriptTest is Test { } function test_run() public { + skipIfZkSync(); + Deploy deploy = new Deploy(); deploy.run(); vm.setEnv("SOURCE_DKIM", vm.toString(vm.envAddress("ECDSA_DKIM"))); diff --git a/packages/relayer/src/abis/email_account_recovery.rs b/packages/relayer/src/abis/email_account_recovery.rs index 3f458798..72cf8eb6 100644 --- a/packages/relayer/src/abis/email_account_recovery.rs +++ b/packages/relayer/src/abis/email_account_recovery.rs @@ -483,28 +483,6 @@ pub mod email_account_recovery { }, ], ), - ( - ::std::borrow::ToOwned::to_owned("proxyBytecodeHash"), - ::std::vec![ - ::ethers::core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("proxyBytecodeHash"), - inputs: ::std::vec![], - outputs: ::std::vec![ - ::ethers::core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers::core::abi::ethabi::ParamType::FixedBytes( - 32usize, - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bytes32"), - ), - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers::core::abi::ethabi::StateMutability::View, - }, - ], - ), ( ::std::borrow::ToOwned::to_owned("recoveryCommandTemplates"), ::std::vec![ @@ -776,14 +754,6 @@ pub mod email_account_recovery { .method_hash([201, 250, 167, 197], recovered_account) .expect("method not found (this should never happen)") } - ///Calls the contract's `proxyBytecodeHash` (0x85f60f7e) function - pub fn proxy_bytecode_hash( - &self, - ) -> ::ethers::contract::builders::ContractCall { - self.0 - .method_hash([133, 246, 15, 126], ()) - .expect("method not found (this should never happen)") - } ///Calls the contract's `recoveryCommandTemplates` (0x3ef01b8f) function pub fn recovery_command_templates( &self, @@ -1054,19 +1024,6 @@ pub mod email_account_recovery { pub struct IsActivatedCall { pub recovered_account: ::ethers::core::types::Address, } - ///Container type for all input parameters for the `proxyBytecodeHash` function with signature `proxyBytecodeHash()` and selector `0x85f60f7e` - #[derive( - Clone, - ::ethers::contract::EthCall, - ::ethers::contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "proxyBytecodeHash", abi = "proxyBytecodeHash()")] - pub struct ProxyBytecodeHashCall; ///Container type for all input parameters for the `recoveryCommandTemplates` function with signature `recoveryCommandTemplates()` and selector `0x3ef01b8f` #[derive( Clone, @@ -1127,7 +1084,6 @@ pub mod email_account_recovery { HandleAcceptance(HandleAcceptanceCall), HandleRecovery(HandleRecoveryCall), IsActivated(IsActivatedCall), - ProxyBytecodeHash(ProxyBytecodeHashCall), RecoveryCommandTemplates(RecoveryCommandTemplatesCall), Verifier(VerifierCall), VerifierAddr(VerifierAddrCall), @@ -1207,11 +1163,6 @@ pub mod email_account_recovery { ) { return Ok(Self::IsActivated(decoded)); } - if let Ok(decoded) = ::decode( - data, - ) { - return Ok(Self::ProxyBytecodeHash(decoded)); - } if let Ok(decoded) = ::decode( data, ) { @@ -1273,9 +1224,6 @@ pub mod email_account_recovery { Self::IsActivated(element) => { ::ethers::core::abi::AbiEncode::encode(element) } - Self::ProxyBytecodeHash(element) => { - ::ethers::core::abi::AbiEncode::encode(element) - } Self::RecoveryCommandTemplates(element) => { ::ethers::core::abi::AbiEncode::encode(element) } @@ -1321,7 +1269,6 @@ pub mod email_account_recovery { Self::HandleAcceptance(element) => ::core::fmt::Display::fmt(element, f), Self::HandleRecovery(element) => ::core::fmt::Display::fmt(element, f), Self::IsActivated(element) => ::core::fmt::Display::fmt(element, f), - Self::ProxyBytecodeHash(element) => ::core::fmt::Display::fmt(element, f), Self::RecoveryCommandTemplates(element) => { ::core::fmt::Display::fmt(element, f) } @@ -1408,11 +1355,6 @@ pub mod email_account_recovery { Self::IsActivated(value) } } - impl ::core::convert::From for EmailAccountRecoveryCalls { - fn from(value: ProxyBytecodeHashCall) -> Self { - Self::ProxyBytecodeHash(value) - } - } impl ::core::convert::From for EmailAccountRecoveryCalls { fn from(value: RecoveryCommandTemplatesCall) -> Self { @@ -1567,18 +1509,6 @@ pub mod email_account_recovery { Hash )] pub struct IsActivatedReturn(pub bool); - ///Container type for all return fields from the `proxyBytecodeHash` function with signature `proxyBytecodeHash()` and selector `0x85f60f7e` - #[derive( - Clone, - ::ethers::contract::EthAbiType, - ::ethers::contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct ProxyBytecodeHashReturn(pub [u8; 32]); ///Container type for all return fields from the `recoveryCommandTemplates` function with signature `recoveryCommandTemplates()` and selector `0x3ef01b8f` #[derive( Clone, diff --git a/yarn.lock b/yarn.lock index bcd49b69..711032ec 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2241,9 +2241,9 @@ camelcase@^6.0.0, camelcase@^6.2.0: integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== caniuse-lite@^1.0.30001646: - version "1.0.30001659" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001659.tgz#f370c311ffbc19c4965d8ec0064a3625c8aaa7af" - integrity sha512-Qxxyfv3RdHAfJcXelgf0hU4DFUVXBGTjqrBUZLUh8AtlGnsDo+CnncYtTd95+ZKfnANUOzxyIQCuU/UeBZBYoA== + version "1.0.30001660" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001660.tgz#31218de3463fabb44d0b7607b652e56edf2e2355" + integrity sha512-GacvNTTuATm26qC74pt+ad1fW15mlQ/zuTzzY1ZoIzECTP8HURDfF43kNxPgf7H1jmelCBQTTbBNxdSXOA7Bqg== cargo-cp-artifact@^0.1: version "0.1.9"