1/5
## Deep Dive: The `executeTransaction` Function in zkSync Smart Accounts In the zkSync transaction lifecycle, once a transaction successfully completes Phase 1 (validation, including nonce checks and fee payments via `payForTransaction` or `prepareForPaymaster`), it progresses to Phase 2: Execution. During this phase, the validated transaction is handled by the main zkSync node or sequencer, which then invokes the `executeTransaction` function on the target account contract. This function is pivotal as it's where the actual logic intended by the user gets executed. This lesson focuses on the `executeTransaction` function within the context of a `ZkMinimalAccount.sol` contract, illustrating how it processes transactions in the zkSync Era. ### The `executeTransaction` Function: Core Logic The `executeTransaction` function is the entry point for the execution phase. It receives the transaction details, which have already been validated, and is responsible for performing the specified action (e.g., calling another contract, transferring value). #### Function Signature and Parameters The function is defined as `external payable` and initially takes three parameters: ```solidity function executeTransaction( bytes32 /*_txHash*/, bytes32 /*_suggestedSignedHash*/, Transaction memory _transaction ) external payable { // ... implementation ... } ``` For a minimal implementation, the `_txHash` and `_suggestedSignedHash` parameters are often related to advanced or future features and can be ignored. The primary focus is on the `_transaction` parameter. This `Transaction` struct (defined elsewhere, typically containing fields like `nonce`, `to`, `from`, `value`, `data`, gas parameters, and signature) carries all the necessary information from the validation phase to the execution phase. #### Extracting Transaction Parameters Inside `executeTransaction`, the essential details for the call – target address (`to`), Ether value (`value`), and call data (`data`) – are extracted from the `_transaction` struct. Special attention must be paid to type casting due to differences between how these values are stored in the `Transaction` struct (often as `uint256`) and how they are used in Solidity or by zkSync system contracts. 1. **Target Address (`to`):** The `_transaction.to` field is a `uint256`. It needs to be cast to an `address` type, which is 160 bits. ```solidity // Get the target address address to = address(uint160(_transaction.to)); ``` 2. **Value (`value`):** The `_transaction.value` is a `uint256`. However, certain operations, particularly calls to system contracts or internal EVM operations, might expect a `uint128`. To prevent overflow and ensure compatibility, a safe casting utility is used. We'll import a `Utils` library for this purpose. ```solidity // Import the Utils library import {Utils} from "lib/foundry-era-contracts/src/system-contracts/contracts/libraries/Utils.sol"; // ... inside executeTransaction ... // Get the value, safely casting to uint128 uint128 value = Utils.safeCastToUint128(_transaction.value); ``` 3. **Call Data (`data`):** The `_transaction.data` field, which contains the payload for the call, is extracted directly as `bytes memory`. ```solidity // Get the transaction data bytes memory data = _transaction.data; ``` #### Handling Calls to System Contracts The zkSync Era features special "System Contracts" that manage core protocol functionalities like nonce management or contract deployment. Calls *to* these system contracts require specific handling to ensure correct execution and proper propagation of reverts. The `SystemContractsCaller` helper library is used for this purpose. If the transaction's target (`to`) is a known system contract, such as the `DEPLOYER_SYSTEM_CONTRACT` for contract deployments, the call must be routed through `SystemContractsCaller.systemCallWithPropagatedRevert`. ```solidity // Import necessary constants and helpers import {SystemContractsCaller} from "lib/foundry-era-contracts/src/system-contracts/contracts/libraries/SystemContractsCaller.sol"; import {DEPLOYER_SYSTEM_CONTRACT} from "lib/foundry-era-contracts/src/system-contracts/contracts/Constants.sol"; // Utils is already imported from the previous step // ... inside executeTransaction, after extracting to, value, data ... if (to == address(DEPLOYER_SYSTEM_CONTRACT)) { // This is a deployment transaction. // We need to call the deployer system contract using SystemContractsCaller. uint32 gas = Utils.safeCastToUint32(gasleft()); // Get remaining gas, safely cast to uint32 SystemContractsCaller.systemCallWithPropagatedRevert(gas, to, value, data); } else { // Handle normal external calls (to non-system contracts) // ... (covered in the next section) } ``` Here, `gasleft()` provides the remaining gas for the call, which is then safely cast to `uint32` as expected by some system contract interfaces. #### Handling Normal External Calls via Assembly For calls to regular smart contracts (i.e., not system contracts), the zkSync Era's zkEVM has nuances compared to the standard EVM, especially concerning low-level call opcodes (`call`, `staticcall`, `delegatecall`). While Solidity's high-level `.call()` can be used, directly using assembly with the `call` opcode is often demonstrated for clarity on zkSync's behavior or for more fine-grained control. An assembly block is used to perform the external call. It's crucial to check the success status of this call and revert if it fails. ```solidity // Define custom error for execution failure error ZkMinimalAccount_ExecutionFailed(); // ... inside the 'else' block from the previous section ... bool success; assembly { // success := call(gas, to, value, in, insize, out, outsize) // gas(): current available gas for the call // to: the target address // value: the ETH value to send with the call // add(data, 0x20): pointer to the actual calldata (skipping the length prefix of bytes array) // mload(data): length of the calldata // 0, 0: pointer and length for return data (we are not capturing return data here) success := call(gas(), to, value, add(data, 0x20), mload(data), 0, 0) } if (!success) { revert ZkMinimalAccount_ExecutionFailed(); } ``` The `add(data, 0x20)` part is used because `data` (a `bytes memory` variable) in Solidity stores its length in the first 32 bytes (0x20 bytes). The actual calldata starts after this length prefix. `mload(data)` retrieves this length. #### Access Control: Ensuring Authorized Execution The `executeTransaction` function is a critical part of the account's logic. It should only be callable by authorized entities. In the context of zkSync Account Abstraction, this typically means the Bootloader (during the normal transaction flow initiated by a user) or the owner of the account (for direct management or recovery operations). A modifier is the standard Solidity pattern for implementing such access control. ```solidity // Import the BOOTLOADER_FORMAL_ADDRESS constant import {BOOTLOADER_FORMAL_ADDRESS} from "lib/foundry-era-contracts/src/system-contracts/contracts/Constants.sol"; // Define custom error for authorization failure error ZkMinimalAccount_NotFromBootloaderOrOwner(); // Assume 'owner()' function is defined elsewhere in your ZkMinimalAccount contract, // typically returning the address of the account owner. modifier requireFromBootloaderOrOwner() { // Allow calls only from the official Bootloader address or the account's owner. if (msg.sender != BOOTLOADER_FORMAL_ADDRESS && msg.sender != owner()) { revert ZkMinimalAccount_NotFromBootloaderOrOwner(); } _; // Proceed with function execution if the check passes } // Apply the modifier to the executeTransaction function function executeTransaction( bytes32 /*_txHash*/, bytes32 /*_suggestedSignedHash*/, Transaction memory _transaction ) external payable requireFromBootloaderOrOwner // Modifier applied here { address to = address(uint160(_transaction.to)); uint128 value = Utils.safeCastToUint128(_transaction.value); bytes memory data = _transaction.data; if (to == address(DEPLOYER_SYSTEM_CONTRACT)) { uint32 gas = Utils.safeCastToUint32(gasleft()); SystemContractsCaller.systemCallWithPropagatedRevert(gas, to, value, data); } else { bool success; assembly { success := call(gas(), to, value, add(data, 0x20), mload(data), 0, 0) } if (!success) { revert ZkMinimalAccount_ExecutionFailed(); } } } ``` ### Key Considerations in zkSync Era When implementing `executeTransaction` or similar functions interacting with the zkSync protocol: * **Type Safety:** Be vigilant about type conversions (e.g., `uint256` to `address`, `uint256` to `uint128` or `uint32`). Utilize safe casting utilities like those provided in `Utils.sol` to prevent overflows and ensure compatibility, especially when interacting with system contracts or specific EVM operations. * **System Contract Interactions:** Always use the `SystemContractsCaller` library for making calls *to* zkSync system contracts. This ensures that calls are formatted correctly and that reverts are propagated as expected within the zkSync environment. * **Low-Level Calls (`call`, `staticcall`, `delegatecall`):** Understand that these operations might behave differently or have specific recommendations for use in the zkSync zkEVM compared to the standard EVM. Using assembly for these calls, as shown, can provide more direct control and alignment with zkSync's intended patterns. Always consult the official zkSync documentation for the latest guidance on EVM instruction differences. * **Error Handling:** Check the success status of all external calls (whether via assembly `call` or Solidity's high-level `.call()`) and revert appropriately on failure using custom errors for better diagnostics. * **Access Control:** Secure critical functions like `executeTransaction` with robust access control mechanisms, typically using modifiers, to prevent unauthorized execution. By understanding these principles and the structure of the `executeTransaction` function, developers can build secure and functional smart accounts on the zkSync Era, enabling the full potential of Account Abstraction. This function forms the bridge between a user's validated intent and its actual execution on the blockchain.
A practical examination to Deep Dive: The `executeTransaction` Function in zkSync Smart Accounts - Explore the `executeTransaction` function's role in zkSync smart accounts, covering how it handles validated transactions by extracting parameters and ensuring type safety. You'll implement calls to system contracts using `SystemContractsCaller`, external calls via assembly, and secure the function with access control modifiers.
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