1/5
## Mastering zkSync Account Abstraction: Testing and Lifecycle Deep Dive Congratulations on reaching this significant milestone! In this lesson, we'll consolidate our understanding and celebrate the successful creation and testing of our zkSync minimal account abstraction (AA) account. We've now implemented and verified a core piece of zkSync functionality, which is an exciting achievement. Let's review what we've accomplished and the key takeaways. ### Validating Core Account Abstraction Functionality Our primary success lies in rigorously testing the fundamental functions of our zkSync AA account: * **`validateTransaction`**: The cornerstone of zkSync AA, this function is invoked during the first phase of any transaction. Its role is to confirm the transaction's validity (e.g., ensuring it's correctly signed by the account owner) and, crucially, to update the account's nonce to prevent replay attacks. We successfully tested this using `testZKValidateTransaction`: ```solidity // In ZKMinimalAccountTest.t.sol function testZKValidateTransaction() public { // Arrange: Setup transaction, including signing it with the owner's key // and preparing any other necessary prerequisites. Transaction memory transaction = /* ... create and sign transaction ... */; // Act: Simulate the call from the bootloader, which is the // msg.sender during the validation phase in zkSync. vm.prank(BOOTLOADER_FORMAL_ADDRESS); bytes4 magic = minimalAccount.validateTransaction(EMPTY_BYTES32, EMPTY_BYTES32, transaction); // Assert: Verify that the function returns the success magic value, // indicating the transaction is valid from the account's perspective. assertEq(magic, ACCOUNT_VALIDATION_SUCCESS_MAGIC, "Validation did not return success magic"); } ``` This test simulates the zkSync bootloader calling `validateTransaction` and asserts that it returns the `ACCOUNT_VALIDATION_SUCCESS_MAGIC` value, indicating the transaction has passed initial checks from the account's perspective. * **`executeTransaction` (sometimes conceptually referred to as `executeCommand`)**: Following successful validation, this function is responsible for the actual execution of the transaction's intended operations (e.g., transferring tokens, interacting with other smart contracts). Our `testZkOwnerCanExecuteCommands` confirmed its correct behavior when called by the owner: ```solidity // In ZKMinimalAccountTest.t.sol function testZkOwnerCanExecuteCommands() public { // Arrange: Prepare an unsigned transaction detailing the command to execute, // and set up the expected state (e.g., initial token balances). Transaction memory transaction = /* ... create unsigned transaction for execution ... */; // Act: Simulate the account owner initiating the execution. // This bypasses the standard validation flow if the owner directly calls it. vm.prank(minimalAccount.owner()); minimalAccount.executeTransaction(EMPTY_BYTES32, EMPTY_BYTES32, transaction); // Assert: Verify that the intended outcome of the transaction has occurred, // such as a change in token balance. assertEq(usdc.balanceOf(address(minimalAccount)), AMOUNT, "Account USDC balance mismatch after execution"); } ``` Here, we simulate the account owner calling `executeTransaction`. The `Transaction` struct contains the calldata for the operation. The assertion checks if the intended action, like a token transfer leading to a balance change, occurred as expected. In a full AA flow, the bootloader would call this after `validateTransaction`. ### Unpacking the zkSync Transaction Lifecycle (Type 0x71) To fully appreciate our testing, let's revisit the typical lifecycle of a zkSync native AA transaction (Type 113 or 0x71), as outlined in our `ZKMinimalAccount.sol` comments. This process is divided into two main phases: #### Phase 1: Validation This initial phase is orchestrated by the zkSync API client and involves several critical steps before the transaction is even considered for block inclusion. The `msg.sender` for critical calls to the account during this phase is the `BOOTLOADER_FORMAL_ADDRESS`. 1. **Submission:** The user (or their wallet) sends the transaction to a zkSync API client (e.g., a node). 2. **Nonce Check (API):** The API client queries the `NonceHolder` system contract to ensure the transaction's nonce is unique for the account, preventing replay attacks. 3. **Account Validation:** The API client calls `validateTransaction` on the target account. This function *must* verify the transaction's signature and update the account's nonce within the `NonceHolder`. 4. **Nonce Update Verification (API):** The API client confirms that the nonce was indeed updated by the `validateTransaction` call. 5. **Fee Payment:** The API client calls `payForTransaction` on the account (or `prepareForPaymaster` and `validateAndPayForPaymasterTransaction` if a paymaster is involved) to handle transaction fees. Our `ZKMinimalAccount.sol` implements a basic `payForTransaction` function: ```solidity // In ZKMinimalAccount.sol function payForTransaction( bytes32, /* _txHash */ bytes32, /* _suggestedSignedHash */ Transaction memory _transaction ) external payable { bool success = _transaction.payToTheBootloader(); if (!success) { revert ZkMinimalAccount_FailedToPay(); } } ``` This function ensures the transaction can cover its costs by transferring the required fee to the bootloader. While testing this function was skipped in our demonstration for brevity, it's an essential component to test thoroughly in a production-ready account. 6. **Bootloader Payment Verification (API):** The API client verifies that the bootloader has received the necessary payment. #### Phase 2: Execution Once a transaction has successfully passed the validation phase, it's ready for execution. 7. **Sequencer Processing:** The API client forwards the validated transaction to the main zkSync node (sequencer). 8. **Account Execution:** The main node, via the bootloader, calls `executeTransaction` on the account. This function performs the actual state changes defined in the transaction data. The `executeTransaction` function in `ZKMinimalAccount.sol` is defined as: ```solidity // In ZKMinimalAccount.sol function executeTransaction( bytes32, /* _txHash */ bytes32, /* _suggestedSignedHash */ Transaction memory _transaction ) external payable requireFromBootloaderOrOwner { // Modifier ensuring caller is bootloader or owner _executeTransaction(_transaction); // Calls internal logic } ``` The `requireFromBootloaderOrOwner` modifier is crucial here, restricting who can call this function. It ensures it's either the bootloader (as part of the standard AA flow after validation) or the account owner directly for specific privileged operations. 9. **Post-Transaction (Paymaster):** If a paymaster was utilized, its `postTransaction` method is called to finalize any fee-related logic or refunds. ### Additional Test Considerations While we covered the core AA flow, a complete test suite would also include scenarios for other functions. For instance, `executeTransactionFromOutside` in `ZKMinimalAccount.sol` allows for a combined validation and execution sequence, often used for calls initiated externally rather than through the standard zkSync AA transaction flow. Its implementation is as follows: ```solidity // In ZKMinimalAccount.sol function executeTransactionFromOutside(Transaction memory _transaction) external payable { _validateTransaction(_transaction); // Internal validation logic _executeTransaction(_transaction); // Internal execution logic } ``` Testing this function would involve verifying that both validation (including signature checks) and execution occur correctly when invoked through this specific pathway, typically by an external EOA or contract. ### Essential: Running Foundry Tests for zkSync A critical aspect of developing for zkSync is understanding how to run your tests correctly within a zkSync-emulated environment. **Test Failures in Standard EVM Mode:** If you attempt to run tests like `testZKValidateTransaction` using a standard `forge test` command without zkSync-specific configurations, they will likely fail. This is because these tests rely on zkSync-specific system contracts, addresses (such as `BOOTLOADER_FORMAL_ADDRESS`), and unique operational behaviors (like how nonces are handled by the `NonceHolder` contract and specific precompiles) that are not present in a vanilla Ethereum Virtual Machine (EVM) environment provided by default Foundry. **Correct Test Execution with zkSync Flags:** To successfully execute these tests, your Foundry environment must be made zkSync-aware. This is achieved by: 1. **Building for zkSync:** Compiling your contracts specifically for the zkSync Virtual Machine (zkVM) using `forge build --zk` (or the equivalent `zksolc` compiler invocation). This produces zkSync-compatible bytecode. 2. **Testing in zkSync Mode:** Running your tests with the `--zk` flag: `forge test --zk`. This flag instructs Foundry to use its zkSync testing capabilities, which simulate the zkSync environment, including its system contracts, specific opcodes, and transaction flow. ### Developer Resources and Best Practices To aid your development and testing journey with zkSync account abstraction, leverage these resources and tips: * **Core GitHub Repository:** The primary resource for this lesson's code, including the `ZKMinimalAccount.sol` contract and its tests (`test/zkMinimalAccountTest.t.sol`), is the Cyfrin `minimal-account-abstraction` repository: `github.com/Cyfrin/minimal-account-abstraction`. * **Conditional Logic in Tests for Versatility:** The test files within the repository, such as `ZKMinimalAccountTest.t.sol`, often employ conditional logic to adapt to different testing environments. For example, contract deployment might differ between a standard Foundry EVM setup and a zkSync emulated environment: ```solidity // Example of conditional deployment logic in ZKMinimalAccountTest.t.sol // --zksync doesn't work well with scripts for some setups with direct `forge script` if (isZkSyncChain()) { // Checks if running in zkSync mode vm.prank(user); // Uses a deployer script/contract for zkSync due to its specific deployment needs // for AA contracts, which might involve custom deployers. minimalAccount = deployer.deploy(); } else { vm.prank(user); // Direct instantiation for standard EVM testing minimalAccount = new ZkMinimalAccount(); } ``` This `isZkSyncChain()` check (often a helper function in the test setup, potentially checking `block.chainid`) allows for writing a single test suite that can be reliably executed in both vanilla Foundry and zkSync-enabled Foundry. It helps handle environment-specific nuances like contract deployment mechanisms or the availability of certain system addresses. * **Leverage the `Makefile` for Simplified Workflows:** The provided GitHub repository includes a `Makefile`. This is an invaluable tool that pre-configures common development and testing commands, abstracting away the complexities of remembering specific flags and configurations for both EVM and zkSync environments. We strongly recommend using it: * **`make test`**: Executes standard EVM tests (typically `forge test`). * **`make zktest`**: Executes zkSync-specific tests (e.g., `forge test --zk`). * **`make zkbuild`**: Compiles contracts for the zkSync environment (e.g., `forge build --zk`). * **`make zkanvil`**: Starts a local zkSync development node (often using `npx zksync-cli dev start` for Era Test Node). * **`make zkdeploy`**: Deploys contracts to zkSync. This often involves custom deployment scripts (e.g., using `yarn deploy` or Hardhat scripts) because deploying AA contracts on zkSync requires interaction with the `ContractDeployer` system contract and specific calldata formats, which may not be directly supported by a simple `forge create` for all AA scenarios. This lesson recapped the successful implementation and testing of our minimal zkSync account abstraction contract. By understanding the core functions, the zkSync transaction lifecycle, and the nuances of testing in a zkSync environment, you are now well-equipped to build more sophisticated AA solutions. Remember to utilize the provided GitHub repository and `Makefile` to streamline your development process.
A comprehensive guide to Mastering zkSync Account Abstraction: Testing and Lifecycle Deep Dive - Consolidate your understanding by testing core zkSync AA functions like `validateTransaction` and `executeTransaction`. Explore the detailed zkSync transaction lifecycle and master Foundry test execution using zkSync-specific flags for accurate simulation.
Previous lesson
Previous
Next lesson
Next
Give us feedback
Course Overview
About the course
Advanced smart contract development
How to develop a stablecoin
How to develop a DeFi protocol
How to develop a DAO
Advanced smart contracts testing
Fuzz testing
Manual verification
Web3 Developer Relations
$85,000 - $125,000 (avg. salary)
Web3 developer
$60,000 - $150,000 (avg. salary)
Smart Contract Engineer
$100,000 - $150,000 (avg. salary)
Smart Contract Auditor
$100,000 - $200,000 (avg. salary)
Security researcher
$49,999 - $120,000 (avg. salary)
Guest lecturers:
Juliette Chevalier
Lead Developer relations at Aragon
Nader Dabit
Director of developer relations at EigenLayer
Ally Haire
Developer relations at Protocol Labs
Harrison
Founder at GasliteGG
Last updated on May 12, 2025
Solidity Developer
Advanced FoundryDuration: 36min
Duration: 3h 06min
Duration: 5h 02min
Duration: 6h 02min
Duration: 2h 47min
Duration: 1h 23min
Duration: 4h 28min
Duration: 1h 19min
Duration: 1h 10min
Course Overview
About the course
Advanced smart contract development
How to develop a stablecoin
How to develop a DeFi protocol
How to develop a DAO
Advanced smart contracts testing
Fuzz testing
Manual verification
Web3 Developer Relations
$85,000 - $125,000 (avg. salary)
Web3 developer
$60,000 - $150,000 (avg. salary)
Smart Contract Engineer
$100,000 - $150,000 (avg. salary)
Smart Contract Auditor
$100,000 - $200,000 (avg. salary)
Security researcher
$49,999 - $120,000 (avg. salary)
Guest lecturers:
Juliette Chevalier
Lead Developer relations at Aragon
Nader Dabit
Director of developer relations at EigenLayer
Ally Haire
Developer relations at Protocol Labs
Harrison
Founder at GasliteGG
Last updated on May 12, 2025