# foundry-zksync - ZKSync Ethereum Development Framework {/* Auto-generated benchmark table from 9/17/2025. Do not edit manually. */} Baseline: [v1.2.3](https://github.com/foundry-rs/foundry/releases/tag/v1.2.3) Latest: [v1.3.0](https://github.com/foundry-rs/foundry/releases/tag/v1.3.0) Learn more about what went into the latest release [here](/releases)
Repository Forge Test Forge Fuzz Test Forge Test (Isolated) Forge Build (No Cache) Forge Build (With Cache) Forge Coverage
[Uniswap-v4-core](https://github.com/Uniswap/v4-core) 8.14s /
7.14s
↓12.29%
8.20s /
6.90s
↓15.85%
8.61s /
7.72s
↓10.34%
2m 3.5s /
2m 4.0s
↑0.40%
0.131s /
0.132s
↑0.76%
1m 36.6s /
1m 35.2s
↓1.45%
[ithacaxyz-account](https://github.com/ithacaxyz/account) 3.53s /
3.15s
↓10.76%
3.88s /
3.05s
↓21.39%
9.25s /
9.27s
↑0.22%
0.195s /
0.198s
↑1.54%
15.79s /
15.63s
↓1.01%
[solady](https://github.com/Vectorized/solady) 2.72s /
2.30s
↓15.44%
2.93s /
2.46s
↓16.04%
2.98s /
2.57s
↓13.76%
14.59s /
14.65s
↑0.41%
0.086s /
0.089s
↑3.49%
[sparkdotfi-spark-psm](https://github.com/sparkdotfi/spark-psm) 57.18s /
47.27s
↓17.33%
3.81s /
2.99s
↓21.52%
53.01s /
45.94s
↓13.34%
13.24s /
13.21s
↓0.23%
0.181s /
0.171s
↓5.52%
3m 38.4s /
3m 50.0s
↑5.31%
import BenchmarksTable from "./benches.mdx"; ## Foundry Benchmarks This page shows the latest performance benchmarks for Foundry across different repositories. The benchmarks are automatically updated from the [Foundry repository](https://github.com/foundry-rs/foundry/blob/master/benches/LATEST.md). ### Performance Overview The table below shows benchmark results comparing different versions of Foundry. Each cell displays the baseline time / latest release time, followed by the percentage change. ### Benchmark Types #### Forge Test Command: [`forge test`](/forge/tests/overview) Measures the standard test execution time. Runs all the tests in the repository. #### Forge Fuzz Test Command: [`forge test --match-test "test[^(]*\([^)]+\)"`](/forge/advanced-testing/fuzz-testing) Measure the execution time for fuzz tests. #### Forge Test Isolated Command: [`forge test --isolate`](/forge/reference/test) Measures the execution time of tests in `isolate` mode. In isolation mode all top-level calls are executed as a separate transaction in a separate EVM context, enabling more precise gas accounting and transaction state changes. #### Forge Build (No Cache) Command: [`forge build`](/forge/reference/build) Measures compilation time on a clean repository i.e no cache available. #### Forge Build (With Cache) Command: [`forge build`](/forge/reference/build) Measures execution time for `forge build` in presense of full cache. In this case, compilation is skipped and `forge` only check whether the cache is clean or marked as dirty. #### Forge Coverage Command: [`forge coverage --ir-minimum`](/forge/reference/coverage) Measures the time taken to analyze and collect test coverage reports. It is important to note that this time includes compilation time as well. Before analyzing for coverage every project is recompiled with configuration that will result in accurate coverage reports. ## Compilation Overview [zksolc](https://github.com/matter-labs/era-compiler-solidity/releases) is the compiler ZKsync uses to convert solidity code to zkEVM-compatible bytecode. It uses the same input format as solc but the output bytecodes and their respective hashes. Internally, it uses a custom-compiled [solc](https://github.com/matter-labs/era-solidity/releases) ### Dual Compilation To allow switching back and forth between EVM and zkEVM as defined in the [Execution Overview](execution-overview), we compile the same contract with `solc` and `zksolc`. This dual-compiled contract can then be freely translated between both environments as needed. As such, every contract in Foundry ZKsync always has two bytecodes attached - EVM bytecode and zkEVM bytecode, which are not equivalent. :::info If you run the example listed in the [Getting Started](/introduction/getting-started) section at the beginning of the book, you can check them out in the `out` and `zkout` folders ::: ### Limitations See [Compilation Limitations](limitations/compilation). ### Compiler Support Policy To provide clarity and a consistent user experience, Foundry-ZKsync follows a formal zksolc version support policy. This policy ensures compatibility, minimizes user confusion, and addresses issues related to deprecated or unsupported compiler versions. #### Supported Versions * Foundry officially supports zksolc starting from **v1.5.6**: * **v1.5.6**: Serves as a baseline with structural compatibility. * **v1.5.7 and later**: Includes support for post-compile-time linkage and subsequent improvements. * **Note:** `v1.5.9` is **not supported** due to a breaking change where `AssemblyCreate` was introduced as an `ErrorType` rather than a `WarningType`. Users should upgrade to `v1.5.10` or later to resolve this issue. #### Deprecation Policy * Backward compatibility will be maintained for supported versions (**v1.5.6 and later**) unless deprecated due to critical issues (e.g., security vulnerabilities or obsolete functionality). * Versions earlier than **v1.5.6** and **v1.5.9** will not be supported. Compilation with these versions will fail. #### Behavior for Unsupported Versions * **Explicitly Specified Versions:** If an unsupported version (e.g., `zksolc = 1.5.5`) is specified, compilation will fail with a clear error message. * **Hardcoded Paths:** If an unsupported version is used via a hardcoded path (e.g., `zksolc = "path/to/zksolc-v1.5.5"`), Foundry will emit a warning to inform users without halting compilation. ## Configuration Overview Foundry-ZKsync adds some new configuration options that can be specified in the `foundry.toml`. These include the following: * [Project Configuration](/config/reference/project) * [Testing Configuration](/config/reference/testing) * [Fuzz](/config/reference/testing#no_zksync_reserved_addresses) * [Invariant](/config/reference/testing#no_zksync_reserved_addresses-1) ## Execution Overview A forge test begins its execution on the EVM, hence the need to compile `solc` artifacts (see: [solc](https://github.com/matter-labs/era-solidity)). During test execution, the test can switch over to ZKsync context in multiple ways. The following operations are performed during the switchover: 1. All `persisted_accounts` storages are migrated to ZKsync storage. 2. Any EVM bytecode deployed under the migrated account is replaced by its `zksolc` variant. 3. Solidity globals such as `block.number` and `address.balance` on the test level (which executes in EVM context) return ZKsync values. 4. The original EVM context (block environment) is preserved for the switch back from the ZKsync context. ### Switching to ZKsync Switching over to ZKsync context can be achieved in the following ways: #### CLI Flags In general, the shorthand `--zksync` flag compiles the sources for `zksolc` and does the switchover to ZKsync context on test execution. The flag is a shorthand alias for enabling the following flags: * `--zk-startup` - performs ZKsync switchover on test startup * `--zk-compile` - compiles the sources for `zksolc` #### Forking If during test execution, forking cheatcodes such as `vm.selectFork` or `vm.createSelectFork` are used to fork over to a ZKsync network, the execution switches to ZKsync context. The RPC endpoint is tested for the `zks_getBaseTokenL1Address` method; if it exists, the RPC URL is deemed to be a ZKsync-compatible endpoint. Similarly, if the selected fork URL is not a ZKsync endpoint, the test execution is set to EVM context. #### Cheatcode Override A custom cheatcode `vm.zkVm` is provided to switch the test execution to ZKsync mode manually. Passing a value of `true` enables ZKsync mode, whereas `false` switches it back to EVM mode. :::info Using `--zksync` is equivalent to having `vm.zkVm(true)` as the first statement in a test. ::: ### ZKSync mode When a test is running in ZKsync mode, any `CREATE` or `CALL` instructions encountered within the test's scope (which runs on EVM) are intercepted and simulated in zkEVM. For example, in the following scenario: ```solidity contract MyContract { function getNumber() public returns (uint256) { return 42; } } contract FooTest is Test { function testExecutionOverview() public { vm.roll(10); // EVM vm.assertEq(10, block.number); // EVM MyContract testContract = new MyContract(); // zkEVM uint256 number = testContract.getNumber(); // zkEVM vm.assertEq(42, number); // EVM } } ``` When `testExecutionOverview()` is run with `--zksync`, it is initially run in Foundry's EVM context. However, due to the presence of the `--zksync` flag, the storage switchover to the ZKsync context is performed immediately upon its execution. The cheatcode `vm.roll(10)` is then intercepted within EVM, as are all cheatcodes, but the operation is applied on ZKsync storage. Similarly, the statement `block.number` also returns the ZKsync storage value. Once we encounter `new BlockEnv()`, which is a `CREATE` operation, we intercept this within the EVM and execute it on the zkEVM instead, returning the result. Similarly, `blockEnv.getBlockNumber()`, also a `CALL` operation, is executed on the zkEVM, and the result (here: `42`) is stored in the variable. It is worth noting that any nested instructions from the above calls will always be executed within the zkEVM since the parent `CREATE` or `CALL` was dispatched to the zkEVM. :::info Only `CREATE` and `CALL` operations are executed on the zkEVM from the test scope. However, once they are dispatched to zkEVM, any internal code will always be executed in zkEVM, where we do not support cheatcodes. There can not be references to `vm` within the code executed in zkEVM. This is undefined behavior. ::: ## Forge-ZKsync Standard Library [`forge-std`](https://github.com/foundry-rs/forge-std) exports the most common constructs that allow users to write tests. However, in Foundry ZKsync, we've added some new cheatcodes (or anything we deem helpful in the future). To allow users to access these interfaces, [`forge-zksync-std`](https://github.com/Moonsong-Labs/forge-zksync-std) is provided as an add-on to `forge-std`. ### Installation ```bash forge install Moonsong-Labs/forge-zksync-std ``` ### Usage In the absence of `forge-zksync-std`, the new cheatcodes are only accessible via low-level calls: ```solidity import {Test} from "forge-std/Test.sol"; contract FooTest is Test { function testZkTraceOutputDuringCreate() public { vm.startPrank(address(65536)); // normal foundry cheatcodes new Contract1(); (bool success,) = address(vm).call(abi.encodeWithSignature("zkVmSkip()")); // additional foundry-zksync cheatcodes require(success, "zkVmSkip() call failed"); new Contract2(); } } ``` However, with the `TextExt` interface, the new cheatcodes can be accessed via `vmExt` property directly. The usual foundry cheatcodes are still available under the `vm` property. ```solidity import {Test} from "forge-std/Test.sol"; import {TestExt} from "forge-zksync-std/TestExt.sol"; contract FooTest is Test, TestExt { function testZkTraceOutputDuringCreate() public { vm.startPrank(address(65536)); // normal foundry cheatcodes new Contract1(); vmExt.zkVmSkip(); // additional foundry-zksync cheatcodes new Contract2(); } } ``` This approach ensures that the existing tests need not be modified to use a completely different package than `foundry/forge-std`, yet allowing for the additional ZKsync functionality to be included when necessary. ## Gas Overview ### Gas reported back to the EVM Foundry has an `isolate` mode for the EVM, in which all `CALL`/`CREATE` operations at the root level of a test (i.e., with depth 1) are intercepted and treated as independent transactions. This allows for accounting for the actual transaction gas, including, for example, the fixed 21000 gas cost charged to the user. Running in zkEVM mode is analogous to running in `isolate` mode, but using the zkEVM instead. Here's a short summary: * Every `CALL`/`CREATE` will be intercepted and a zkEVM transaction representing the operation built. * A VM with that transaction in the bootloader's heap will be spawned and run to simulate the execution of that transaction. * The gas used is reported back to the EVM so it will then be shown on traces and gas reports. This value represents what would be charged to the user for submitting that transaction. That value differs from the computational cost of running the called contract code and includes: 1. **Intrinsic costs**: Overhead charged on each transaction. 2. **Validation costs**: Gas spent on transaction validation. It may vary depending on the account making the transaction. See [Account Abstraction](https://docs.zksync.io/build/developer-reference/account-abstraction) docs. 3. **Execution costs**: Gas spent on marking factory dependencies and executing the transaction. 4. **Pubdata costs**: Gas spent on publishing pubdata. It is influenced by the `gasPerPubdata` network value. More info about ZKSync Era's fee model can be found [here](https://docs.zksync.io/build/developer-reference/fee-model). ### Transaction/Network values that impact gas cost The gas cost mentioned above is influenced by transaction and network values. The values are set when running the VM in the following way: 1. Transaction Params: * `max_fee_per_gas`: will be the gas price of the root EVM transaction (e.g., when running tests, the value of `--gas-price` option is used) with a minimum value of `0.26GWei`, which is the base fee used in some test environments/networks. * `gas_limit`: The sender remaining balance capped to a max of `2^31 - 1`. No matter the gas limit, the vm caps how much gas a single transaction can use to `MAX_GAS_PER_TRANSACTION`, currently set to `80_000_000`. 2. Network Params: * `fair_l2_gas_price`: set to the minimum of `max_fee_per_gas` and the base fee of the root EVM transaction (e.g., when running tests, the value of the `--base-fee` option). * `l1_gas_price`: set to the same as `fair_l2_gas_price`, with a minimum value of `1000`. #### Deriving relevant transaction gas values From the params above, we can get all gas-related values used in the transaction: * `fair_pubdata_price`: `l1_gas_price` \* `L1_GAS_PER_PUBDATA_BYTE`. * `baseFee`: Maximum value between `fair_l2_gas_price` and `(fair_pubdata_price / MAX_L2_GAS_PER_PUBDATA)`. * `gasPerPubdata`: `fairPubdataPrice / baseFee`. `L1_GAS_PER_PUBDATA_BYTE` and `MAX_L2_GAS_PER_PUBDATA` are system constants currently set to `17` and `50_000`, respectively. #### Customizing gas values Some gas parameters can be customized when broadcasting transactions with `forge script` by using the following flags: * `--with-gas-price-price`: sets `max_fee_per_gas` to be used in the transaction. * `--priority-gas-price`: sets `max_priority_fee_per_gas` to be used in the transaction. * `--zk-gas-per-pubdata`: sets `gasPerPubdata` to be used in the transaction. Example: ```sh forge script script/NFT.s.sol:MyScript --fork-url http://localhost:8545 --broadcast --with-gas-price-price 370000037 --priority-gas-price 10000 --zk-gas-per-pubdata 3241 ``` ## ZKSync Specifics This section covers the specific parts of ZKSync foundry, how it works, insight into compilation, and custom cheatcodes to help work with contracts specifically in ZKSync. ### Context Foundry-ZKsync tests are initially [executed](execution-overview) in the EVM context (the traditional execution environment for Ethereum smart contracts). This is done to maintain compatibility with Ethereum tooling and to leverage Foundry features like [Cheatcodes](limitations/cheatcodes). ![Features](/key-features.png) #### Lifetime Execution A forge test begins execution in the EVM context but can switch to the EraVM context during the test for ZKsync-specific features. Selecting the EraVM context ensures all calls and deployments are executed within ZKsync. There are mainly three steps **Step 1)** Aggregate: [Dual compiling](compilation-overview) contracts ![alt text](/lifetime/step-1.png) **Step 2)** Intercept: Overriding `CALL`s and `CREATE`s (see [limitations](limitations/overview) for more details) ![alt text](/lifetime/step-2.png) **Step 3)**: Assimilate: One-shot zkEVM execution (see [Standard Library](forge-zksync-std) for more details) ![alt text](/lifetime/step-3.png) #### Additional Information. It is essential before deep diving in to the details of how to run the tool. You significantly understand how [ZKsync](https://docs.zksync.io/) works. Don't hesitate to ask if you have specific questions regarding the tool or feature requests. #### Having issues? If you have issues with the tool or don't know where to start, we strongly suggest you look at our [repo](https://github.com/matter-labs/foundry-zksync) and [closed issues](https://github.com/matter-labs/foundry-zksync/issues?q=is%3Aissue+is%3Aclosed), where we usually get deep dive into implementation details. ## Paymaster Overview Paymasters in the ZKsync ecosystem represent a groundbreaking approach to handling transaction fees. They are special accounts designed to subsidize transaction costs for other accounts, potentially making certain transactions free for end-users. This feature is handy for dApp developers looking to improve their platform's accessibility and user experience by covering transaction fees on behalf of their users. ### How Paymasters Work Paymasters are smart contracts that implement the `IPaymaster` interface. They are designed to be used with the ZKsync network's transaction processing mechanism. The paymaster is specified in the metadata when a transaction is sent from an account. The paymaster is then responsible for paying the transaction fee and any other costs associated with the transaction. ### How to interact with a Paymaster using Foundry Currently, the ways to interact with a paymaster contract using Foundry are using `cast send`, `forge create`, or the `zkUsePaymaster` cheatcode. #### Using `cast send` `cast send` signs and publishes a transaction. The documentation can be found [here](/cast/reference/send). The command must specify the paymaster address and the encoded paymaster input to pair this with a paymaster contract. The flags for this are: `--zk-paymaster-address`\     The address where the paymaster contract is deployed. `--zk-paymaster-input`\     The encoded input for the paymaster contract. This depends on the paymaster contract implementation. To encode the paymaster input, you can use the `cast calldata` command, which can be found [here](/cast/reference/calldata). ```bash cast send 0xdb8bA5F5DfB1636361d2fE851d7D3ed93acfc487 "increment()" --rpc-url https://sepolia.era.zksync.dev --private-key --zk-paymaster-address 0x3cB2b87D10Ac01736A65688F3e0Fb1b070B3eeA3 --zk-paymaster-input $(cast calldata "approvalBased(address,uint256,bytes)" 0x31c43ac5e6A0fe62954B9056441b0A214722516e 1000000000000000000 "0x") ``` #### Using `forge create` `forge create` is a command-line tool for deploying smart contracts using the Foundry framework. The documentation can be found [here](/forge/reference/create). The paymaster contract address must be specified in the command to deploy any other contract using `forge create`. The flags for this are: `--zk-paymaster-address`\     The address where the paymaster contract is deployed. `--zk-paymaster-input`\     The encoded input for the paymaster contract. This depends on the paymaster contract implementation. To encode the paymaster input, you can also use the `cast calldata` command, which can be found [here](/cast/reference/calldata). ```bash forge create Greeter.sol:Greeter --rpc-url "https://sepolia.era.zksync.dev" --private-key --zksync --zk-paymaster-address 0x3cB2b87D10Ac01736A65688F3e0Fb1b070B3eeA3 --zk-paymaster-input $(cast calldata "approvalBased(address,uint256,bytes)" 0x31c43ac5e6A0fe62954B9056441b0A214722516e 1 "0x") ``` Also, see the [ZKsync Paymaster Documentation](https://docs.zksync.io/build/developer-reference/account-abstraction/paymasters) for more information. ## v1.3.6 \[September 16, 2025] ## Foundry v1.3.6 Foundry v1.3.6 is a bugfix release for forge. ### Forge Fixes * fix(forge): ensure broadcast account is touched ([#11661](https://github.com/foundry-rs/foundry/pull/11661)) by [@grandizzy](https://github.com/grandizzy) ### Full Changelog: [https://github.com/foundry-rs/foundry/compare/v1.3.5...v1.3.6](https://github.com/foundry-rs/foundry/compare/v1.3.5...v1.3.6) ### Previous Releases | Version | Release Date | Release Notes | | ---------- | ----------------- | --------------------------------------------------------------------- | | v1.3.5 | September 8, 2025 | [View](https://github.com/foundry-rs/foundry/releases/tag/v1.3.5) | | v1.3.4 | September 3, 2025 | [View](https://github.com/foundry-rs/foundry/releases/tag/v1.3.4) | | v1.3.3 | August 29, 2025 | [View](https://github.com/foundry-rs/foundry/releases/tag/v1.3.3) | | v1.3.2 | August 21, 2025 | [View](https://github.com/foundry-rs/foundry/releases/tag/v1.3.2) | | v1.3.1 | August 12, 2025 | [View](https://github.com/foundry-rs/foundry/releases/tag/v1.3.1) | | v1.3.0-rc4 | July 30, 2025 | [View](https://github.com/foundry-rs/foundry/releases/tag/v1.3.0-rc4) | | v1.3.0 | July 31, 2025 | [View](https://github.com/foundry-rs/foundry/releases/tag/v1.3.0) | | v1.3.0-rc3 | July 29, 2025 | [View](https://github.com/foundry-rs/foundry/releases/tag/v1.3.0-rc3) | | v1.3.0-rc2 | July 24, 2025 | [View](https://github.com/foundry-rs/foundry/releases/tag/v1.3.0-rc2) | | v1.3.0-rc1 | July 17, 2025 | [View](https://github.com/foundry-rs/foundry/releases/tag/v1.3.0-rc1) | | v1.2.3 | June 8, 2025 | [View](https://github.com/foundry-rs/foundry/releases/tag/v1.2.3) | | v1.2.2 | May 30, 2025 | [View](https://github.com/foundry-rs/foundry/releases/tag/v1.2.2) | | v1.2.1-rc | May 21, 2025 | [View](https://github.com/foundry-rs/foundry/releases/tag/v1.2.1-rc) | | v1.2.1 | May 26, 2025 | [View](https://github.com/foundry-rs/foundry/releases/tag/v1.2.1) | | v1.2.0-rc1 | May 19, 2025 | [View](https://github.com/foundry-rs/foundry/releases/tag/v1.2.0-rc1) | | v1.2.0-rc | May 13, 2025 | [View](https://github.com/foundry-rs/foundry/releases/tag/v1.2.0-rc) | | v1.2.0 | May 19, 2025 | [View](https://github.com/foundry-rs/foundry/releases/tag/v1.2.0) | | v1.1.0 | April 28, 2025 | [View](https://github.com/foundry-rs/foundry/releases/tag/v1.1.0) | | v1.0.0 | February 10, 2025 | [View](https://github.com/foundry-rs/foundry/releases/tag/v1.0.0) | *** *This page is automatically updated with the latest release information from the [Foundry GitHub repository](https://github.com/foundry-rs/foundry/releases).* ### Anvil Anvil is a fast local Ethereum development node. Anvil is part of the Foundry suite and is installed alongside `forge`, `cast` and `chisel`. If you haven't installed Foundry yet, see [Foundry installation](/introduction/installation). #### Getting started To use Anvil, simply type `anvil`. To fork against a live Ethereum network run `anvil --fork-url `. Let's fork Ethereum mainnet at the latest block: ```bash anvil --fork-url https://reth-ethereum.ithaca.xyz/rpc ``` ```bash _ _ (_) | | __ _ _ __ __ __ _ | | / _` | | '_ \ \ \ / / | | | | | (_| | | | | | \ V / | | | | \__,_| |_| |_| \_/ |_| |_| 0.2.0 (c4fcf12 2024-12-12T00:23:45.094165202Z) https://github.com/foundry-rs/foundry Available Accounts ================== (0) 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 (10000.000000000000000000 ETH) (1) 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 (10000.000000000000000000 ETH) (2) 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC (10000.000000000000000000 ETH) (3) 0x90F79bf6EB2c4f870365E785982E1f101E93b906 (10000.000000000000000000 ETH) (4) 0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65 (10000.000000000000000000 ETH) (5) 0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc (10000.000000000000000000 ETH) (6) 0x976EA74026E726554dB657fA54763abd0C3a0aa9 (10000.000000000000000000 ETH) (7) 0x14dC79964da2C08b23698B3D3cc7Ca32193d9955 (10000.000000000000000000 ETH) (8) 0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f (10000.000000000000000000 ETH) (9) 0xa0Ee7A142d267C1f36714E4a8F75612F20a79720 (10000.000000000000000000 ETH) Private Keys ================== (0) 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 (1) 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d (2) 0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a (3) 0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6 (4) 0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a (5) 0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba (6) 0x92db14e403b83dfe3df233f83dfa3a0d7096f21ca9b0d6d6b8d88b2b4ec1564e (7) 0x4bbbf85ce3377467afe5d46f804f221813b2bb87f24d81f60f1fcdbf7cbf4356 (8) 0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97 (9) 0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6 Wallet ================== Mnemonic: test test test test test test test test test test test junk Derivation path: m/44'/60'/0'/0/ Fork ================== Endpoint: https://eth.merkle.io Block number: 21387064 Block hash: 0x904aee789b82ac0412448bc2ea9bb3774d10c2dae4a0e5b7f6451ac2ecd0787a Chain ID: 1 Base Fee ================== 26049293674 Gas Limit ================== 30000000 Genesis Timestamp ================== 1734014216 Listening on 127.0.0.1:8545 ```
:::info See the [`anvil` Reference](/anvil/reference) for in depth information on Anvil and its capabilities. ::: ### Notes #### EIP-7702 and Default Accounts Since the advent of EIP-7702, Anvil's default accounts have been delegated to drainers such as [https://etherscan.io/address/0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266#authlist7702](https://etherscan.io/address/0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266#authlist7702) This can negatively impact developer experience when users are running Anvil in fork mode and are making RPC calls that involve one of the default anvil accounts. To avoid this issue, use a different mnemonic when starting Anvil: ```bash anvil --mnemonic "" --fork-url https://reth-ethereum.ithaca.xyz/rpc ``` ### anvil #### NAME anvil - Create a local testnet node for deploying and testing smart contracts. It can also be used to fork other EVM compatible networks. #### SYNOPSIS `anvil` \[*options*] #### DESCRIPTION Create a local testnet node for deploying and testing smart contracts. It can also be used to fork other EVM compatible networks. This section covers an extensive list of information about Mining Modes, Supported Transport Layers, Supported RPC Methods, Anvil flags and their usages. You can run multiple flags at the same time. ##### Mining Modes Mining modes refer to how frequent blocks are mined using Anvil. By default, it automatically generates a new block as soon as a transaction is submitted. You can change this setting to interval mining if you will, which means that a new block will be generated in a given period of time selected by the user. If you want to go for this type of mining, you can do it by adding the `--block-time ` flag, like in the following example. ```sh # Produces a new block every 10 seconds anvil --block-time 10 ``` There's also a third mining mode called never. In this case, it disables auto and interval mining, and mine on demand instead. You can do this by typing: ```sh # Enables never mining mode anvil --no-mining ``` To speed up the finalization of blocks, you can use the `--slots-in-an-epoch` flag with a value of `1` for example. This will lead to the block at height `N-2` being finalized, where `N` is the latest block. ##### Supported Transport Layers HTTP and Websocket connections are supported. The server listens on port 8545 by default, but it can be changed by running the following command: ```sh anvil --port ``` ##### Default CREATE2 Deployer Anvil, when used without forking, includes the [default CREATE2 deployer proxy](https://github.com/Arachnid/deterministic-deployment-proxy) at the address `0x4e59b44847b379578588920ca78fbf26c0b4956c`. This allows you to test CREATE2 deployments locally without forking. ##### Supported RPC Methods ##### Standard Methods The standard methods are based on [this](https://ethereum.org/en/developers/docs/apis/json-rpc/) reference. * `web3_clientVersion` * `web3_sha3` * `eth_chainId` * `eth_networkId` * `eth_gasPrice` * `eth_accounts` * `eth_blockNumber` * `eth_getBalance` * `eth_getStorageAt` * `eth_getBlockByHash` * `eth_getBlockByNumber` * `eth_getTransactionCount` * `eth_getBlockTransactionCountByHash` * `eth_getBlockTransactionCountByNumber` * `eth_getUncleCountByBlockHash` * `eth_getUncleCountByBlockNumber` * `eth_getCode` * `eth_sign` * `eth_signTypedData_v4` * `eth_sendTransaction` * `eth_sendRawTransaction` * `eth_call` * `eth_createAccessList` * `eth_estimateGas` * `eth_getTransactionByHash` * `eth_getTransactionByBlockHashAndIndex` * `eth_getTransactionByBlockNumberAndIndex` * `eth_getTransactionReceipt` * `eth_getUncleByBlockHashAndIndex` * `eth_getUncleByBlockNumberAndIndex` * `eth_getLogs` * `eth_newFilter` * `eth_getFilterChanges` * `eth_newBlockFilter` * `eth_newPendingTransactionFilter` * `eth_getFilterLogs` * `eth_uninstallFilter` * `eth_getWork` * `eth_subscribe` * `eth_unsubscribe` * `eth_syncing` * `eth_submitWork` * `eth_submitHashrate` * `eth_feeHistory` * `eth_getProof` * `debug_traceTransaction` Use `anvil --steps-tracing` to get `structLogs` * `debug_traceCall` Note that non-standard traces are not yet supported. This means you can't pass any arguments to the `trace` parameter. * `trace_transaction` * `trace_block` ##### Custom Methods The `anvil_*` namespace is an alias for `hardhat`. For more info, refer to the [Hardhat documentation](https://hardhat.org/hardhat-network/reference#hardhat-network-methods). `anvil_impersonateAccount` Send transactions impersonating an externally owned account or contract. `anvil_stopImpersonatingAccount` Stops impersonating an account or contract if previously set with `anvil_impersonateAccount`. `anvil_autoImpersonateAccount` Accepts `true` to enable auto impersonation of accounts, and `false` to disable it. When enabled, any transaction's sender will be automatically impersonated. Same as `anvil_impersonateAccount`. `anvil_getAutomine` Returns true if automatic mining is enabled, and false if it is not. `anvil_mine` Mines a series of blocks. `anvil_dropTransaction` Removes transactions from the pool. `anvil_reset` Reset the fork to a fresh forked state, and optionally update the fork config. `anvil_setRpcUrl` Sets the backend RPC URL. `anvil_setBalance` Modifies the balance of an account. `anvil_setCode` Sets the code of a contract. `anvil_setNonce` Sets the nonce of an address. `anvil_setStorageAt` Writes a single slot of the account's storage. `anvil_setCoinbase` Sets the coinbase address. `anvil_setLoggingEnabled` Enable or disable logging. `anvil_setMinGasPrice` Set the minimum gas price for the node. `anvil_setNextBlockBaseFeePerGas` Sets the base fee of the next block. `anvil_setChainId` Sets the chain ID of the current EVM instance. `anvil_dumpState` Returns a hex string representing the complete state of the chain. Can be re-imported into a fresh/restarted instance of Anvil to reattain the same state. `anvil_loadState` When given a hex string previously returned by `anvil_dumpState`, merges the contents into the current chain state. Will overwrite any colliding accounts/storage slots. `anvil_nodeInfo` Retrieves the configuration params for the currently running Anvil node. ##### Special Methods The special methods come from Ganache. You can take a look at the documentation [here](https://github.com/trufflesuite/ganache-cli-archive/blob/master/README.md). `evm_setAutomine` Enables or disables, based on the single boolean argument, the automatic mining of new blocks with each new transaction submitted to the network. `evm_setIntervalMining` Sets the mining behavior to interval with the given interval (seconds). `evm_snapshot` Snapshot the state of the blockchain at the current block. `evm_revert` Revert the state of the blockchain to a previous snapshot. Takes a single parameter, which is the snapshot id to revert to. `evm_increaseTime` Jump forward in time by the given amount of time, in seconds. `evm_setNextBlockTimestamp` Similar to `evm_increaseTime` but takes the exact timestamp that you want in the next block. `anvil_setBlockTimestampInterval` Similar to `evm_increaseTime` but sets a block timestamp `interval`. The timestamp of the next block will be computed as `lastBlock_timestamp + interval`. `evm_setBlockGasLimit` Sets the block gas limit for the following blocks. `anvil_removeBlockTimestampInterval` Removes an `anvil_setBlockTimestampInterval` if it exists. `evm_mine` Mine a single block. `anvil_enableTraces` Turn on call traces for transactions that are returned to the user when they execute a transaction (instead of just txhash/receipt). `eth_sendUnsignedTransaction` Execute a transaction regardless of signature status. For the next three methods, make sure to read [Geth's documentation](https://geth.ethereum.org/docs/rpc/ns-txpool). `txpool_status` Returns the number of transactions currently pending for inclusion in the next block(s), as well as the ones that are being scheduled for future execution only. `txpool_inspect` Returns a summary of all the transactions currently pending for inclusion in the next block(s), as well as the ones that are being scheduled for future execution only. `txpool_content` Returns the details of all transactions currently pending for inclusion in the next block(s), as well as the ones that are being scheduled for future execution only. ##### Otterscan Methods The `ots_*` namespace implements the [Otterscan specification](https://docs.otterscan.io/api-docs/ots-api). `ots_getApiLevel` Used by Otterscan to check if it's connecting to a compatible node and display a friendly message if it is not. `ots_getInternalOperations` Returns the internal ETH transfers inside a transaction. `ots_hasCode` Check if a certain address contains a deployed code. `ots_getTransactionError` Extract the transaction raw error output. `ots_traceTransaction` Extract all variations of calls, contract creation and self-destructs and returns a call tree. `ots_getBlockDetails` Tailor-made and expanded version of eth\_getBlock\* for block details page in Otterscan. `ots_getBlockTransactions` Get paginated transactions for a certain block, And removes some verbose fields such logs. `ots_searchTransactionsBefore` Gets paginated inbound/outbound transaction calls for a certain address, and before a given target block. `ots_searchTransactionsAfter` Gets paginated inbound/outbound transaction calls for a certain address, and after a given target block. `ots_getTransactionBySenderAndNonce` Gets the transaction hash for a certain sender address, given its nonce. `ots_getContractCreator` Gets the transaction hash and the address which created a contract. #### OPTIONS ##### General Options `-a, --accounts `      Set the number of accounts. \[default: 10] `--auto-impersonate`      Enable autoImpersonate on startup. `-b, --block-time `      Block time in seconds for interval mining. `--balance `      Set the balance of the accounts. \[default: 10000] `--derivation-path `      Set the derivation path of the child key to be derived. \[default: m/44'/60'/0'/0/] `-h, --help`      Print help information. `--hardfork `      Choose the EVM hardfork to use e.g. `prague`, `cancun`, `shanghai`, `paris`, `london`, etc... \[default: latest] `--init `      Initialize the genesis block with the given `genesis.json` file. `-m, --mnemonic `      BIP39 mnemonic phrase used for generating accounts. `--no-mining`      Disable auto and interval mining, and mine on demand instead. `--order `      How transactions are sorted in the mempool. \[default: fees] `-p, --port `      Port number to listen on. \[default: 8545] `--steps-tracing`      Enable steps tracing used for debug calls returning geth-style traces. \[aliases: tracing] `--ipc []`      Starts an IPC endpoint at the given `PATH` argument or the default path: unix: `tmp/anvil.ipc`, windows: `\\.\pipe\anvil.ipc`. `--silent`      Don't print anything on startup. `--timestamp `      Set the timestamp of the genesis block. `-V, --version`      Print version information. `--disable-default-create2-deployer`      Disables deploying the default CREATE2 factory when running Anvil without forking. ##### EVM Options `-f, --fork-url `      Fetch state over a remote endpoint instead of starting from an empty state. `--fork-block-number `      Fetch state from a specific block number over a remote endpoint (Must pass `--fork-url` in the same command-line). `--fork-chain-id `      Specify chain id to skip fetching it from remote endpoint. This enables offline-start mode. You still must pass both `--fork-url` and `--fork-block-number`, and already have your required state cached on disk, anything missing locally would be fetched from the remote. `--fork-retry-backoff `      Initial retry backoff on encountering errors. `--fork-transaction-hash `      Fetch state from a specific transaction hash over a remote endpoint (Must pass `--fork-url` in the same command-line). `--retries `      Number of retry requests for spurious networks (timed out requests). \[default: 5] `--timeout `      Timeout in ms for requests sent to remote JSON-RPC server in forking mode. \[default: 45000] `--compute-units-per-second `      Sets the number of assumed available compute units per second for this provider. \[default: 330] `--no-rate-limit`      Disables rate limiting for this node's provider. Will always override `--compute-units-per-second` if present. \[default: false] `--no-storage-caching`      Disables RPC caching; all storage slots are read from the endpoint. This flag overrides the project's configuration file (Must pass --fork-url in the same command-line). ##### Executor Environment Config `--base-fee ` `--block-base-fee-per-gas `      The base fee in a block. `--chain-id `      The chain ID. \[default: 31337] `--code-size-limit `      EIP-170: Contract code size limit in bytes. Useful to increase for tests. \[default: 0x6000 (\~25kb)] `--gas-limit `      The block gas limit. `--gas-price `      The gas price. ##### Server Options `--allow-origin `      Set the CORS `allow_origin`. \[default: \*] `--no-cors`      Disable CORS. `--host `      The IP address the server will listen on. `--config-out `      Writes output of `anvil` as json to user-specified file. `--prune-history`      Don't keep full chain history. `--no-request-size-limit`      Disable the request size limit. Default is 2MB #### EXAMPLES 1. Set the number of accounts to 15 and their balance to 300 ETH ```sh anvil --accounts 15 --balance 300 ``` 2. Choose the address which will execute the tests ```sh anvil --sender 0xC8479C45EE87E0B437c09d3b8FE8ED14ccDa825E ``` 3. Change how transactions are sorted in the mempool to FIFO ```sh anvil --order fifo ``` #### Shell Completions `anvil completions` *shell* Generates a shell completions script for the given shell. Supported shells are: * bash * elvish * fish * powershell * zsh ##### EXAMPLES 1. Generate shell completions script for zsh: ```sh anvil completions zsh > $HOME/.oh-my-zsh/completions/_anvil ``` #### Usage within Docker In order to run anvil as a service in Github Actions with the [Docker container](/guides/foundry-in-docker), where passing arguments to the entrypoint command is not possible, use the `ANVIL_IP_ADDR` environment variable to set the host's IP. `ANVIL_IP_ADDR=0.0.0.0` is equivalent to providing the `--host ` option. ##### Using `genesis.json` The `genesis.json` file in Anvil serves a similar purpose as in Geth, defining the network's initial state, consensus rules, and preallocated accounts to ensure all nodes start consistently and maintain network integrity. All values, including balance, gas limit and such, are to be defined as hexadecimals. * `chainId`: Identifier for the blockchain, unique to each network. * `nonce`: A counter used in hashing algorithms to ensure data integrity. * `timestamp`: The creation time of the genesis block in Unix time. * `extraData`: Additional data that can be included by the creator of the genesis block. * `gasLimit`: The maximum amount of gas that can be used in the block. * `difficulty`: A measure of how difficult it is to mine a new block. * `mixHash`: A unique identifier proving a sufficient amount of computation for the block. * `coinbase`: The Ethereum address of the miner who mined this block. * `stateRoot`: The root of the state trie, reflecting the final state after all transactions. * `alloc`: Allows pre-allocating Ether to a set of addresses with predefined balances. * `number`: The block number, with the genesis block being 0. * `gasUsed`: The total gas used in the block. * `parentHash`: The hash of the parent block, all zeros for the genesis block since there is no parent. A sample for simulating mainnet via genesis can be found [here](https://github.com/paradigmxyz/reth/blob/8f3e4a15738d8174d41f4aede5570ecead141a77/crates/primitives/res/genesis/mainnet.json). ```json { "chainId": "0x2323", "nonce": "0x42", "timestamp": "0x0", "extraData": "0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa", "gasLimit": "0x1388", "difficulty": "0x400000000", "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "coinbase": "0x0000000000000000000000000000000000000000", "stateRoot": "0xd7f8974fb5ac78d9ac099b9ad5018bedc2ce0a72dad1827a1709da30580f0544", "alloc": { "000d836201318ec6899a67540690382780743280": { "balance": "0xad78ebc5ac6200000" } }, "number": "0x0", "gasUsed": "0x0", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000" } ``` ### Overview of Anvil-ZKsync Anvil-ZKsync is a local testnet node shipped with Foundry-ZKsync. You can use it for testing your contracts from frontends or for interacting over RPC. Anvil-ZKsync is part of the Foundry-ZKsync suite and is installed alongside `forge`, and `cast`. If you haven't installed Foundry-ZKsync yet, see [Foundry-ZKsync installation](/introduction/installation). > Note: If you have an older version of Foundry-ZKsync installed, you'll need to re-install `foundryup-zksync` in order for Anvil-ZKsync to be downloaded. #### How to use Anvil-ZKsync To use Anvil-ZKsync, simply type `anvil-zksync`. You should see a list of accounts and private keys available for use, as well as the address and port that the node is listening on. Anvil-ZKsync is highly configurable. You can run `anvil-zksync -h` to see all the configuration options. Some basic options are: ```bash # Number of dev accounts to generate and configure. [default: 10] anvil-zksync -a, --accounts # Port number to listen on. [default: 8545] anvil-zksync -p, --port ``` ### Cast Cast is a Swiss Army knife for interacting with Ethereum applications from the command line. You can make smart contract calls, send transactions, or retrieve any type of chain data - all from your command-line! The `cast` binary can be used both within and outside of a Foundry project. Cast is part of the Foundry suite and is installed alongside `forge`, `chisel`, and `anvil`. If you haven't installed Foundry yet, see [Foundry installation](/introduction/installation). #### Getting started Here are a few examples of what you can do: **Check the latest block on Ethereum Mainnet**: ```sh cast block-number --rpc-url https://reth-ethereum.ithaca.xyz/rpc ``` **Check the Ether balance of `vitalik.eth`** ```sh cast balance vitalik.eth --ether --rpc-url https://reth-ethereum.ithaca.xyz/rpc ``` **Replay and trace a transaction** ```sh cast run 0x9c32042f5e997e27e67f82583839548eb19dc78c4769ad6218657c17f2a5ed31 --rpc-url https://reth-ethereum.ithaca.xyz/rpc ``` Optionally, pass `--etherscan-api-key ` to decode transaction traces using verified source maps, providing more detailed and human-readable information. **Retrieve the total supply of the DAI token** ```sh // [!include ~/snippets/output/cast/cast-call:all] ``` **Decode calldata** ```sh // [!include ~/snippets/output/cast/cast-4byte-calldata:all] ``` **Send messages between two Anvil accounts** ```sh cast send --private-key 0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc $(cast from-utf8 "hello world") --rpc-url http://127.0.0.1:8545/ ```
:::info See the [`cast` Reference](/cast/reference/cast) for a complete overview of all the available subcommands. ::: ## Additional Cheatcodes A few new cheatcodes have been added to the existing [Cheatcodes](/reference/cheatcodes/overview) list to help within the ZKsync context, ### Cheatcodes Interface This is the extended Solidity interface for all ZKsync-specific cheatcodes present in Forge. ```solidity interface CheatCodesExt { /// Registers bytecodes for ZK-VM for transact/call and create instructions. function zkRegisterContract( string calldata name, bytes32 evmBytecodeHash, bytes calldata evmDeployedBytecode, bytes calldata evmBytecode, bytes32 zkBytecodeHash, bytes calldata zkDeployedBytecode ) external pure; /// Enables/Disables use ZK-VM for transact/call and create instructions. function zkVm(bool enable) external pure; /// When running in zkEVM context, skips the next CREATE or CALL, executing it on the EVM instead. /// All `CREATE`s executed within this skip will automatically have `CALL`s to their target addresses /// executed in the EVM and need not be marked with this cheatcode at every usage location. function zkVmSkip() external pure; /// Enables the use of a paymaster for the next transaction. function zkUsePaymaster(address paymaster, bytes calldata paymaster_input) external pure; /// Marks a given contract as a factory dependency only for the next CREATE or CALL operation function zkUseFactoryDep(string calldata name) external pure; } ``` ### Usage Refer to the [forge-zksync-std](../forge-zksync-std) section on accessing these cheatcodes in your tests. ## zkRegisterContract ```solidity function zkRegisterContract( string calldata name, bytes32 evmBytecodeHash, bytes calldata evmDeployedBytecode, bytes calldata evmBytecode, bytes32 zkBytecodeHash, bytes calldata zkDeployedBytecode ) external pure; ``` ### Description Registers bytecodes for ZK-VM for transact/call and create instructions. This cheatcode allows you to manually register the mapping between EVM and zkEVM bytecodes for contracts. ### Parameters * `name`: Contract name identifier * `evmBytecodeHash`: Hash of the EVM bytecode * `evmDeployedBytecode`: The deployed EVM bytecode * `evmBytecode`: The creation EVM bytecode * `zkBytecodeHash`: Hash of the zkEVM bytecode * `zkDeployedBytecode`: The deployed zkEVM bytecode ### Examples ```solidity import {Test} from "forge-std/Test.sol"; import {TestExt} from "forge-zksync-std/TestExt.sol"; contract ZkRegisterTest is Test, TestExt { function testRegisterContract() public { // Get bytecodes from compilation artifacts bytes memory evmBytecode = vm.getCode("MyContract.sol"); bytes memory evmDeployedBytecode = vm.getDeployedCode("MyContract.sol"); bytes memory zkDeployedBytecode = vm.getDeployedCode("MyContract.sol:zksolc"); bytes32 evmHash = keccak256(evmBytecode); bytes32 zkHash = keccak256(zkDeployedBytecode); // Register the contract bytecodes vmExt.zkRegisterContract( "MyContract", evmHash, evmDeployedBytecode, evmBytecode, zkHash, zkDeployedBytecode ); // Now the contract can be deployed and executed on zkEVM vmExt.zkVm(true); MyContract myContract = new MyContract(); } } ``` ### Use Cases * Manually managing bytecode mappings for complex deployment scenarios * Working with pre-compiled contracts * Advanced testing scenarios requiring specific bytecode configurations * Debugging bytecode translation issues ### Notes * This is typically handled automatically by foundry-zksync during compilation * Manual registration is only needed for advanced use cases * Ensure bytecode hashes are calculated correctly * Both EVM and zkEVM bytecodes must be valid for the same contract logic ## zkUseFactoryDep ```solidity function zkUseFactoryDep(string calldata name) external pure; ``` ### Description Marks a given contract as a factory dependency only for the next CREATE or CALL operation. Factory dependencies are contracts that may be deployed by other contracts during execution. ### Parameters * `name`: The name of the contract to mark as a factory dependency ### Examples ```solidity import {Test} from "forge-std/Test.sol"; import {TestExt} from "forge-zksync-std/TestExt.sol"; contract FactoryDepTest is Test, TestExt { function testFactoryDependency() public { // Mark ChildContract as a factory dependency vmExt.zkUseFactoryDep("ChildContract"); // Deploy factory contract that will create ChildContract instances Factory factory = new Factory(); // The factory can now deploy ChildContract instances address childAddress = factory.createChild(); } } contract Factory { function createChild() external returns (address) { // This will work because ChildContract was marked as factory dependency ChildContract child = new ChildContract(); return address(child); } } contract ChildContract { uint256 public value = 42; } ``` ### Use Cases * Factory pattern implementations where contracts deploy other contracts * Complex deployment scenarios with nested contract creation * Testing contracts that use CREATE2 for deterministic deployments * Working with proxy patterns and upgradeable contracts ### Notes * Only affects the immediate next CREATE or CALL operation * The specified contract must exist in the compilation artifacts * Factory dependencies are automatically handled in most cases * Manual marking is needed for complex scenarios or edge cases * In zkEVM, all factory dependencies must be known at deployment time ## zkUsePaymaster ```solidity function zkUsePaymaster(address paymaster, bytes calldata paymaster_input) external pure; ``` ### Description Enables the use of a paymaster for the next transaction. The paymaster will pay for the gas costs of the subsequent operation. ### Parameters * `paymaster`: The address of the paymaster contract * `paymaster_input`: Encoded input data for the paymaster contract ### Examples #### General Flow Paymaster ```solidity import {Test} from "forge-std/Test.sol"; import {TestExt} from "forge-zksync-std/TestExt.sol"; contract PaymasterTest is Test, TestExt { function testGeneralPaymaster() public { address paymasterAddress = 0x1234...; // Encode paymaster input for general flow bytes memory paymasterInput = abi.encodeWithSelector( bytes4(keccak256("general(bytes)")), bytes("0x") ); // Enable paymaster for next transaction vmExt.zkUsePaymaster(paymasterAddress, paymasterInput); // This deployment will be paid by the paymaster MyContract contract1 = new MyContract(); } } ``` #### Approval-Based Paymaster ```solidity function testApprovalBasedPaymaster() public { address paymasterAddress = 0x3cB2b87D10Ac01736A65688F3e0Fb1b070B3eeA3; address tokenAddress = 0x5678...; // Encode paymaster input for approval-based flow bytes memory paymasterInput = abi.encodeWithSelector( bytes4(keccak256("approvalBased(address,uint256,bytes)")), tokenAddress, // ERC20 token address uint256(1 ether), // Amount to approve bytes("0x") // Additional data ); // Enable paymaster for next transaction vmExt.zkUsePaymaster(paymasterAddress, paymasterInput); // This call will be paid by the paymaster using ERC20 tokens myContract.someFunction(); } ``` ### Notes * Only affects the immediate next transaction * The paymaster input format depends on the specific paymaster implementation * Ensure the paymaster contract has sufficient funds to cover gas costs * For approval-based paymasters, ensure the account has approved sufficient tokens ### See Also * [General Paymaster Example](/zksync-specifics/examples/general-paymaster) * [Approval-based Paymaster Example](/zksync-specifics/examples/paymaster-approval-based) ## zkVmSkip ```solidity function zkVmSkip() external pure; ``` ### Description When running in zkEVM context, skips the next CREATE or CALL, executing it on the EVM instead. All `CREATE`s executed within this skip will automatically have `CALL`s to their target addresses executed in the EVM and need not be marked with this cheatcode at every usage location. ### Examples ```solidity import {Test} from "forge-std/Test.sol"; import {TestExt} from "forge-zksync-std/TestExt.sol"; contract ZkVmSkipTest is Test, TestExt { function testZkVmSkip() public { // Enable ZK-VM mode vmExt.zkVm(true); // This will execute on zkEVM MyContract contract1 = new MyContract(); // Skip the next operation - execute on EVM instead vmExt.zkVmSkip(); // This will execute on EVM despite zkVm being enabled MyContract contract2 = new MyContract(); // This will execute on zkEVM again MyContract contract3 = new MyContract(); } } ``` ### Use Cases * Testing contracts that have different behavior on EVM vs zkEVM * Debugging by comparing execution between both virtual machines * Working with contracts that may not be compatible with zkEVM * Performance comparisons between EVM and zkEVM execution ### Notes * Only affects the immediate next CREATE or CALL operation * Nested operations from the skipped operation will also execute on EVM * Does not permanently disable zkEVM mode - subsequent operations return to zkEVM ## zkVm ```solidity function zkVm(bool enable) external pure; ``` ### Description Enables or disables the use of ZK-VM for transact/call and create instructions. When enabled (`true`), subsequent `CREATE` and `CALL` operations in the test will be executed on the zkEVM instead of the regular EVM. When disabled (`false`), operations return to standard EVM execution. ### Examples ```solidity import {Test} from "forge-std/Test.sol"; import {TestExt} from "forge-zksync-std/TestExt.sol"; contract ZkVmTest is Test, TestExt { function testZkVmEnable() public { // Enable ZK-VM mode vmExt.zkVm(true); // This contract deployment will execute on zkEVM MyContract contract1 = new MyContract(); // Disable ZK-VM mode vmExt.zkVm(false); // This contract deployment will execute on EVM MyContract contract2 = new MyContract(); } } ``` ### Notes * Using `--zksync` flag is equivalent to having `vmExt.zkVm(true)` as the first statement in a test * Only `CREATE` and `CALL` operations are affected by this cheatcode * Once dispatched to zkEVM, all nested operations remain in zkEVM context * Cheatcodes are not supported within zkEVM execution context ### Chisel Chisel is a fast, utilitarian, and verbose Solidity REPL. The `chisel` binary can be used both within and outside of a Foundry project. If the binary is executed in a Foundry project root, Chisel will inherit the project's configuration options. Chisel is part of the Foundry suite and is installed alongside `forge`, `cast`, and `anvil`. If you haven't installed Foundry yet, see [Foundry installation](/introduction/installation). #### Getting started To use Chisel, simply type `chisel`. ```sh chisel ``` From here, start writing Solidity code! Chisel will offer verbose feedback on each input. Create a variable `a` and query it: ```console ➜ uint256 a = 123; ➜ a Type: uint256 ├ Hex: 0x7b ├ Hex (full word): 0x000000000000000000000000000000000000000000000000000000000000007b └ Decimal: 123 ``` Finally, run `!source` to see `a` was applied: ```solidity // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.28; import {Vm} from "forge-std/Vm.sol"; contract REPL { Vm internal constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); /// @notice REPL contract entry point function run() public { uint256 a = 123; } } ``` To see available commands, type `!help` within the REPL.
:::info See the [`chisel` Reference](/chisel/reference) for in depth information on Chisel and its capabilities. ::: ### chisel #### NAME `chisel` - Test and receive verbose feedback on Solidity inputs within a REPL environment. #### SYNOPSIS `chisel` \[*options*] ##### Subcommands (bin) 1. `chisel list` * Displays all cached sessions stored in `~/.foundry/cache/chisel`. 2. `chisel load ` * If a cached session with `id = ` exists, launches the REPL and loads the corresponding session. 3. `chisel view ` * If a cached session with `id = ` exists, displays the source code of the session's REPL contract. 4. `chisel clear-cache` * Deletes all cache files within the `~/.foundry/cache/chisel` directory. These sessions are unrecoverable, so use this command with care. ##### Flags See `man chisel` or `chisel --help` for all available environment configuration flags. #### DESCRIPTION Chisel is a Solidity REPL (short for "read-eval-print loop") that allows developers to write and test Solidity code snippets. It provides an interactive environment for writing and executing Solidity code, as well as a set of built-in commands for working with and debugging your code. This makes it a useful tool for quickly testing and experimenting with Solidity code without having to spin up a sandbox foundry test suite. #### Usage To open chisel, simply execute the `chisel` binary. From there, input valid Solidity code. There are two kinds of inputs to the chisel prompt apart from commands: 1. Expressions * Expressions are statements that return a value or otherwise can be evaluated on their own. For example, `1 << 8` is an expression that will evaluate to a `uint256` with the value `256`. Expressions will be evaluated up front, and will not persist in the session state past their evaluation. * Examples: * `address(0).balance` * `abi.encode(256, bytes32(0), "Chisel!")` * `myViewFunc(128)` * ... 2. Statements * Statements are snippets of code that are meant to persist in the session's state. Statements include variable definitions, calls to non-state-mutating functions that return a value, and contract, function, event, error, mapping, or struct definitions. If you would like an expression to be evaluated as a statement, a semi-colon (`;`) can be appended to the end. * Examples: * `uint256 a = 0xa57b` * `myStateMutatingFunc(128)` || `myViewFunc(128);`. Notice the `;` * ```solidity function hash64( bytes32 _a, bytes32 _b ) internal pure returns (bytes32 _hash) { assembly { // Store the 64 bytes we want to hash in scratch space mstore(0x00, _a) mstore(0x20, _b) // Hash the memory in scratch space // and assign the result to `_hash` _hash := keccak256(0x00, 0x40) } } ``` * `event ItHappened(bytes32 indexed hash)` * `struct Complex256 { uint256 re; uint256 im; }` * ... ##### Available Commands ```text // [!include ~/snippets/output/chisel/help:output] ``` **General** `!help` | `!h` Display all commands. `!quit` | `!q` Quit Chisel. `!exec [args]` | `!e [args]` Execute a shell command and print the output. Example: ```sh ➜ !e ls CHANGELOG.md LICENSE README.md TESTS.md artifacts cache contracts crytic-export deploy deploy-config deployments dist echidna.yaml forge-artifacts foundry.toml hardhat.config.ts layout-lock.json node_modules package.json scripts slither.config.json slither.db.json src tasks test-case-generator tsconfig.build.json tsconfig.build.tsbuildinfo tsconfig.json ``` **Session** `!clear` | `!c` Clear current session source. Under the hood, each Chisel session has an underlying contract that is altered as you input statements. This command clears this contract and resets your session to the default state. `!source` | `!so` Display the source code of the current session. As mentioned above, each Chisel session has an underlying contract. This command will display the source code of this contract. `!save [id]` | `!s [id]` Save the current session to cache. Chisel allows for caching sessions, which can be very useful if you are testing more complex logic in Chisel or if you want to return to a session at a later time. All cached Chisel sessions are stored in `~/.foundry/cache/chisel`. If an `id` argument is not supplied, Chisel will automatically assign a numerical ID to the session you are saving. `!load ` | `!l ` Load a previous session ID from cache. This command will load a previously cached session from the cache. Along with the session's source, all environment settings will also be loaded. The `id` argument must correspond with an existing cached session in the `~/.foundry/cache/chisel` directory. `!list` | `!ls` List all cached sessions. This command will display all cached chisel sessions within the `~/.foundry/cache/chisel` directory. `!clearcache` | `!cc` Clear the chisel cache of all stored sessions. Deletes all cache files within the `~/.foundry/cache/chisel` directory. These sessions are unrecoverable, so use this command with care. `!export` | `!ex` Export the current session source to a script file. If `chisel` was executed from the root directory of a foundry project, it is possible to export your current session to a foundry script in the `scripts` dir of your project. `!fetch ` | `!fe ` Fetch the interface of a verified contract on Etherscan. This command will attempt to parse the interface of a verified contract @ `` from the Etherscan API. If successful, the interface will be inserted into the session source with the name ``. At the moment, only interfaces of verified contracts on Ethereum mainnet can be fetched. In the future, Chisel will support fetching interfaces from multiple Etherscan-supported chains. `!edit` Open the current session's `run()` function in an editor. chisel will use the editor defined in the `$EDITOR` environment variable. **Environment** `!fork ` | `!f ` Fork an RPC for the current session. Supply 0 arguments to return to a local network. Attempts to fork the state of the provided RPC. If no URL is provided, returns to using a blank, local devnet state. `!traces` | `!t` Enable / disable traces for the current session. When tracing is enabled, foundry-style call tracing and logs will be printed after each statement is inserted. **Debug** `!memdump` | `!md` Dump the raw memory of the current state. Attempts to dump the raw memory of the machine state after the last instruction of the REPL contract's `run` function has finished executing. `!stackdump` | `!sd` Dump the raw stack of the current state. Attempts to dump the raw stack of the machine state after the last instruction of the REPL contract's `run` function has finished executing. `!rawstack ` | `!rs ` Display the raw value of a variable's stack allocation. For variables that are > 32 bytes in length, this will display their memory pointer. This command is useful when you want to view the full raw stack allocation for a variable that is less than 32 bytes in length. Example: ```sh ➜ address addr ➜ assembly { addr := not(0) } ➜ addr Type: address └ Data: 0xffffffffffffffffffffffffffffffffffffffff ➜ !rs addr Type: bytes32 └ Data: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff ➜ ``` ### Continuous Integration #### GitHub Actions To test your project using GitHub Actions, here is a sample workflow: ```yml on: [push] name: test jobs: check: name: Foundry project runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: submodules: recursive - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 with: version: stable - name: Run tests run: forge test -vvv ``` #### Travis CI To test your project using Travis CI, here is a sample workflow: ```yml language: rust cache: cargo: true directories: - $HOME/.foundry install: - curl -L https://foundry.paradigm.xyz | bash - export PATH=$PATH:$HOME/.foundry/bin - foundryup -b master script: - forge test -vvv ``` ### GitLab CI To test your project using GitLab CI, here is a sample workflow: Note: check out [Policy](https://docs.gitlab.com/runner/executors/docker.html#how-pull-policies-work) to fetch the remote image ```yml variables: GIT_SUBMODULE_STRATEGY: recursive jobs: image: ghcr.io/foundry-rs/foundry script: - forge install - forge test -vvv ``` ### Dynamic Test Linking [v1.1 release](https://github.com/foundry-rs/foundry/releases/tag/v1.1.0) comes with dynamic test linking feature, built on top of [Solar](https://github.com/paradigmxyz/solar), which eliminates redundant test compilation when changing the contract logic, Foundry skips recompiling large test suites, resulting in massive time savings. How it works: On the initial build, Foundry preprocesses test contracts by extracting constructor parameters of the contracts under test and replacing direct instantiations with [`deployCode` cheatcodes](/reference/cheatcodes/get-deployed-code). Subsequent compilations reuse pre-built artifacts for deployed contracts instead of recompiling both the source and all associated test contracts. Dynamic test linking capability is built on top of [Solar](https://github.com/paradigmxyz/solar), the blazingly fast and modular Solidity compiler. You can enable this feature by setting the `dynamic_test_linking` configuration option to `true` in your `foundry.toml` file: ```toml [profile.default] ... dynamic_test_linking = true ``` OR by passing the `--dynamic-test-linking` flag to the `forge build` command: ```bash forge build --dynamic-test-linking ``` We are looking into enabling this by default in the future. Benchmarks from the [PR](https://github.com/foundry-rs/foundry/pull/10010) show greater than 10x speedup in compilation time for large projects: | Project | Change | Files compiled (with / without, after initial compile) | Time to compile (with / without, after initial compile) | | ------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ | ------------------------------------------------------- | | [uniswap v4-core](https://github.com/Uniswap/v4-core/tree/80311e34080fee64b6fc6c916e9a51a437d0e482) | add `Lock.lock();` at [PoolManager.sol#L107](https://github.com/Uniswap/v4-core/blob/80311e34080fee64b6fc6c916e9a51a437d0e482/src/PoolManager.sol#L107) | 1 / 19 | 2.25s / 165.13s | | [spark-psm](https://github.com/sparkdotfi/spark-psm/tree/9d0bcc045e81407408368c9a4bb6e3f13db77e32) | change `amountOut < minAmountOut` at [PSM3.sol#L125](https://github.com/sparkdotfi/spark-psm/blob/9d0bcc045e81407408368c9a4bb6e3f13db77e32/src/PSM3.sol#L125) | 3 / 28 | 2.14s / 16.15s | | [morpho-blue-bundlers](https://github.com/morpho-org/morpho-blue-bundlers/tree/1fa17256abb86c4de48fd5e251ebd46aae70ca1a) | change `if (assets < 0)` at [MorphoBundler.sol#L106](https://github.com/morpho-org/morpho-blue-bundlers/blob/1fa17256abb86c4de48fd5e251ebd46aae70ca1a/src/MorphoBundler.sol#L106) | 11 / 36 | 16.39s / 251.05s | | [morpho-blue](https://github.com/morpho-org/morpho-blue/commit/9e2b0755b47bbe5b09bf1be8f00e060d4eab6f1c) | add `require(assets != 0, ErrorsLib.ZERO_ASSETS)` at [Morpho.sol#L424](https://github.com/morpho-org/morpho-blue/blob/9e2b0755b47bbe5b09bf1be8f00e060d4eab6f1c/src/Morpho.sol#L424) | 1 / 23 | 1.01s / 133.73s | | [sablier lockup](https://github.com/sablier-labs/lockup/tree/b2f33926fcac72a1a855c6b8ccaa75166895f13c) | change `if (cliffTime < 0)` at [SablierLockup.sol#L480](https://github.com/sablier-labs/lockup/blob/b2f33926fcac72a1a855c6b8ccaa75166895f13c/src/SablierLockup.sol#L480) | 1 / 104 | 781ms / 71.29s | | [solady](https://github.com/Vectorized/solady/commit/724c39bdfebb593157c2dfa6797c07a25dfb564c) | add additional `_setOwner(newOwner)` at [Ownable.sol#L182](https://github.com/Vectorized/solady/blob/724c39bdfebb593157c2dfa6797c07a25dfb564c/src/auth/Ownable.sol#L182) | 9 / 14 | 6.17s / 6.34s | | [euler evc](https://github.com/euler-xyz/ethereum-vault-connector/commit/64f6d2171a57e02a0f95bcbdecf1d92e9d253d40) | change `SET_MAX_ELEMENTS` to `11` at [Set.sol#L7](https://github.com/euler-xyz/ethereum-vault-connector/blob/64f6d2171a57e02a0f95bcbdecf1d92e9d253d40/src/Set.sol#L7) | 28 / 30 | 9.17s / 9.40s | ### Integrating with Hardhat It's possible to have your Foundry project work alongside [Hardhat](https://hardhat.org/). This article assumes that you have Foundry and node installed in your system. This article also assumes familiarity with both Foundry and Hardhat. #### Why does this not work out of the box? Hardhat by default expects libraries to be installed in `node_modules`, the default folder for all NodeJS dependencies. Foundry expects them to be in `lib`. Of course [we can configure Foundry](/config/overview) but not easily to the directory structure of `node_modules`. For this reason, the recommended setup is to use [hardhat-foundry](https://www.npmjs.com/package/@nomicfoundation/hardhat-foundry). When hardhat-foundry is installed and used correctly, Hardhat will use the same contracts directory that is used by Foundry, and it will be able to use dependencies installed with forge install. In this article we will cover both scenarios: 1. Adding Hardhat to a Foundry project, and, 2. Adding Foundry to a Hardhat project. #### Just show me the example repo! [Enjoy!](https://github.com/foundry-rs/HardhatInFoundry) If you want to adapt this to a Foundry project you already have or learn how it works, read below: #### Adding Hardhat to a Foundry project Inside your Foundry project working directory: 1. `npm init -y` - This will set up a `package.json` file. 2. `npm i --save-dev hardhat@2` - Install Hardhat as a dev dependency into your project. 3. `npx hardhat init` - Initialize your Hardhat project inside the same directory and choose the "**Create an empty hardhat.config.js**" option. This will create a basic `hardhat.config.js` file. 4. `npm i --save-dev @nomicfoundation/hardhat-foundry @nomicfoundation/hardhat-toolbox` - This will install the hardhat-foundry plugin and the Hardhat toolbox plugin which is a combination of all the basic dependencies you need to run Hardhat tests. Your hardhat.config.js file should look like this to make the plugins work: ```javascript require("@nomicfoundation/hardhat-toolbox"); require("@nomicfoundation/hardhat-foundry"); /** @type import('hardhat/config').HardhatUserConfig */ module.exports = { solidity: "0.8.19", }; ``` 5. By default, a Foundry project ships with a simple `Counter.sol` contract and a couple of tests. Create a file named `Counter.t.js` inside the `test` directory parallel to the default `Counter.t.sol` file. 6. Add the following code to the `Counter.t.js` file: ```javascript const { expect } = require("chai"); const hre = require("hardhat"); const { loadFixture } = require("@nomicfoundation/hardhat-toolbox/network-helpers"); describe("Counter contract", function () { async function CounterLockFixture() { const counter = await ethers.deployContract("Counter"); await counter.setNumber(0); return { counter }; } it("Should increment the number correctly", async function () { const { counter } = await loadFixture(CounterLockFixture); await counter.increment(); expect(await counter.number()).to.equal(1); }); // This is not a fuzz test because Hardhat does not support fuzzing yet. it("Should set the number correctly", async function () { const { counter } = await loadFixture(CounterLockFixture); await counter.setNumber(100); expect(await counter.number()).to.equal(100); }); }); ``` This piece of code will execute the same tests as the default `Counter.t.sol` file. And this is it! You can create Hardhat and Foundry tests in the same `test` directory and run them with `npx hardhat test` and `forge test` respectively. Check out [Hardhat's documentation](https://hardhat.org/docs) to learn more. #### Adding Foundry to a Hardhat project Inside your Hardhat project working directory: 1. `npm i --save-dev @nomicfoundation/hardhat-foundry`- Install the hardhat-foundry plugin. 2. Add `require("@nomicfoundation/hardhat-foundry");` to the top of your `hardhat.config.js` file. > ℹ️ **Note** > Step number 3 will only work if your directory is an initialized git repository. Run `git init` if you haven't already. 3. Run `npx hardhat init-foundry` in your terminal. This will generate a `foundry.toml` file based on your Hardhat project's existing configuration, and will install the `forge-std` library. Hardhat will now set up a basic Foundry project inside the same directory with a few configurations inside the `foundry.toml` file to make sure that Foundry knows where to look for your contracts, tests and dependencies. You can always change these configurations later by editing the `foundry.toml` file. ### Configuring your `foundry.toml` Forge can be configured using a configuration file called `foundry.toml`, which is placed in the root of your project. Configuration can be namespaced by profiles. The default profile is named `default`, from which all other profiles inherit. You are free to customize the `default` profile, and add as many new profiles as you need. Additionally, you can create a global `foundry.toml` in your home directory. Let's take a look at a configuration file that contains two profiles: the default profile, which always enables the optimizer, as well as a CI profile, that always displays traces: ```toml [profile.default] optimizer = true optimizer_runs = 20_000 [profile.ci] verbosity = 4 ``` When running `forge`, you can specify the profile to use using the `FOUNDRY_PROFILE` environment variable. #### Standalone sections Besides the profile sections, the configuration file can also contain standalone sections (`[fmt]`, `[fuzz]`, `[invariant]` etc). By default, each standalone section belongs to the `default` profile. i.e. `[fmt]` is equivalent to `[profile.default.fmt]`. To configure the standalone section for different profiles other than `default`, use syntax `[profile..]`. i.e. `[profile.ci.fuzz]`.
:::info See the [`foundry.toml` Reference](/config/reference/default-config) for a complete overview of what you can configure. ::: ### Shell Autocompletion You can generate autocompletion shell scripts for `bash`, `elvish`, `fish`, `powershell`, and `zsh`. #### zsh First, ensure that the following is present somewhere in your `~/.zshrc` file (if not, add it): ```sh autoload -U compinit compinit -i ``` Then run: ```sh forge completions zsh | sudo tee /usr/local/share/zsh/site-functions/_forge cast completions zsh | sudo tee /usr/local/share/zsh/site-functions/_cast anvil completions zsh | sudo tee /usr/local/share/zsh/site-functions/_anvil ``` For macOS: ```sh forge completions zsh > /opt/homebrew/completions/zsh/_forge cast completions zsh > /opt/homebrew/completions/zsh/_cast anvil completions zsh > /opt/homebrew/completions/zsh/_anvil ``` #### fish ```sh mkdir -p $HOME/.config/fish/completions forge completions fish > $HOME/.config/fish/completions/forge.fish cast completions fish > $HOME/.config/fish/completions/cast.fish anvil completions fish > $HOME/.config/fish/completions/anvil.fish source $HOME/.config/fish/config.fish ``` #### bash ```sh mkdir -p $HOME/.local/share/bash-completion/completions forge completions bash > $HOME/.local/share/bash-completion/completions/forge cast completions bash > $HOME/.local/share/bash-completion/completions/cast anvil completions bash > $HOME/.local/share/bash-completion/completions/anvil exec bash ``` ### Static Analyzers #### Slither To test your project using [slither](https://github.com/crytic/slither), here is a sample `slither.config.json`: ```json { "filter_paths": "lib" } ``` To run Slither on the entire project, use this command in the root of the project: ```sh slither . ``` By default (as of version 0.10.0), this will skip tests and scripts. To force inclusion of the tests and scripts, add the `--foundry-compile-all` flag. To run Slither on a single file, use this command: ```sh slither src/Contract.sol ``` Note, this requires configuring the [solc version in the foundry config file](https://book.getfoundry.sh/config/reference/solidity-compiler#solc_version). You do not need to provide remappings via the `solc_remaps` option as Slither will automatically detect remappings in a Foundry project. Slither will invoke `forge` to perform the build. See the [Slither wiki](https://github.com/crytic/slither/wiki/Usage) for more information. In order to use a custom configuration, such as the sample `slither.config.json` mentioned above, the following command is used as mentioned in the [slither-wiki](https://github.com/crytic/slither/wiki/Usage#configuration-file). By default slither looks for the `slither.config.json` but you can define the path and any other `json` file of your choice: ```sh slither --config-file /file.config.json . ``` Example output (Raw): ```bash Pragma version^0.8.13 (Counter.sol#2) necessitates a version too recent to be trusted. Consider deploying with 0.6.12/0.7.6/0.8.7 solc-0.8.13 is not recommended for deployment Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-versions-of-solidity setNumber(uint256) should be declared external: - Counter.setNumber(uint256) (Counter.sol#7-9) increment() should be declared external: - Counter.increment() (Counter.sol#11-13) Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#public-function-that-could-be-declared-external Counter.sol analyzed (1 contracts with 78 detectors), 4 result(s) found ``` Slither also has a [GitHub Action](https://github.com/marketplace/actions/slither-action) for CI/CD. #### Aderyn To test your project using [aderyn](https://github.com/cyfrin/aderyn), install the [VS Code extension](https://marketplace.visualstudio.com/items?itemName=Cyfrin.aderyn\&ssr=false#overview) supported by Cyfrin. To run the tool manually, follow the [Quick Start](https://cyfrin.gitbook.io/cyfrin-docs/aderyn-cli/quickstart) example with video guide. ```bash cd path/to/solidity/project/root aderyn ``` Explore more CLI options [here](https://cyfrin.gitbook.io/cyfrin-docs/cli-options). #### Mythril To test your project using [mythril](https://github.com/ConsenSys/mythril), here is a sample `mythril.config.json`: ```json { "remappings": ["ds-test/=lib/ds-test/src/", "forge-std/=lib/forge-std/src/"], "optimizer": { "enabled": true, "runs": 200 } } ``` Note, you need switch `rustc` to nightly to install `mythril`: ``` rustup default nightly pip3 install mythril myth analyze src/Contract.sol --solc-json mythril.config.json ``` See the [mythril docs](https://mythril-classic.readthedocs.io/en/develop/) for more information. You can pass custom Solc compiler output to Mythril using the `--solc-json` flag. For example: ```bash myth analyze src/Counter.sol --solc-json mythril.config.json . . mythril.laser.plugin.loader [INFO]: Loading laser plugin: coverage mythril.laser.plugin.loader [INFO]: Loading laser plugin: mutation-pruner . . Achieved 11.56% coverage for code: 608060405234801561001057600080fd5b5060f78061001f6000396000f3fe6080604052348015600f57600080fd5b5060043610603c5760003560e01c80633fb5c1cb1460415780638381f58a146053578063d09de08a14606d575b600080fd5b6051604c3660046083565b600055565b005b605b60005481565b60405190815260200160405180910390f35b6051600080549080607c83609b565b9190505550565b600060208284031215609457600080fd5b5035919050565b60006001820160ba57634e487b7160e01b600052601160045260246000fd5b506001019056fea2646970667358221220659fce8aadca285da9206b61f95de294d3958c409cc3011ded856f421885867464736f6c63430008100033 mythril.laser.plugin.plugins.coverage.coverage_plugin [INFO]: Achieved 90.13% coverage for code: 6080604052348015600f57600080fd5b5060043610603c5760003560e01c80633fb5c1cb1460415780638381f58a146053578063d09de08a14606d575b600080fd5b6051604c3660046083565b600055565b005b605b60005481565b60405190815260200160405180910390f35b6051600080549080607c83609b565b9190505550565b600060208284031215609457600080fd5b5035919050565b60006001820160ba57634e487b7160e01b600052601160045260246000fd5b506001019056fea2646970667358221220659fce8aadca285da9206b61f95de294d3958c409cc3011ded856f421885867464736f6c63430008100033 mythril.laser.plugin.plugins.instruction_profiler [INFO]: Total: 1.0892839431762695 s [ADD ] 0.9974 %, nr 9, total 0.0109 s, avg 0.0012 s, min 0.0011 s, max 0.0013 s . . [SWAP1 ] 1.8446 %, nr 18, total 0.0201 s, avg 0.0011 s, min 0.0010 s, max 0.0013 s [SWAP2 ] 0.8858 %, nr 9, total 0.0096 s, avg 0.0011 s, min 0.0010 s, max 0.0011 s mythril.analysis.security [INFO]: Starting analysis mythril.mythril.mythril_analyzer [INFO]: Solver statistics: Query count: 61 Solver time: 3.6820807456970215 The analysis was completed successfully. No issues were detected. ``` The findings will be listed at the end of this output if any. Since the default `Counter.sol` doesn't have any logic, `mythx` reports that no issues were found. ### Integrating with VSCode You can get Solidity support for Visual Studio Code by installing the [VSCode Solidity extension](https://github.com/juanfranblanco/vscode-solidity). To make the extension play nicely with Foundry, you may have to tweak a couple of things. #### 1. Remappings You may want to place your remappings in `remappings.txt`. If they are already in `foundry.toml`, copy them over and use `remappings.txt` instead. If you just use the autogenerated remappings that Foundry provides, run `forge remappings > remappings.txt`. #### 2. Dependencies You may have to add the following to your `.vscode/settings.json` for the extension to find your dependencies: ```json { "solidity.packageDefaultDependenciesContractsDirectory": "src", "solidity.packageDefaultDependenciesDirectory": "lib" } ``` Where `src` is the source code directory and `lib` is your dependency directory. #### 3. Formatter To enable the built-in formatter that comes with Foundry to automatically format your code on save, you can add the following settings to your `.vscode/settings.json`: ```json { "editor.formatOnSave": true, "[solidity]": { "editor.defaultFormatter": "JuanBlanco.solidity" }, "solidity.formatter": "forge" } ``` To configure the formatter settings, refer to the [Formatter](/config/reference/formatter) reference. #### 4. Solc Version Finally, it is recommended to specify a Solidity compiler version: ```json "solidity.compileUsingRemoteVersion": "v0.8.17" ``` To get Foundry in line with the chosen version, add the following to your `default` profile in `foundry.toml`. ```toml solc = "0.8.17" ``` #### Example of using OpenZeppelin contracts and non-standard project layout. ```bash . └── project └── contracts ├── lib │ ├── forge-std │ └── openzeppelin-contracts ├── script ├── src └── test ``` Add line to `remappings.txt` file ([`forge remapping`](/guides/project-setup/dependencies#remapping-dependencies)): ```solidity @openzeppelin/=lib/openzeppelin-contracts/ ``` Add line to `.vscode/settings.json` file (solidity extension settings): ```json { "solidity.remappings": [ "@openzeppelin/=project/contracts/lib/openzeppelin-contracts/" ] } ``` Now all contracts from the OpenZeppelin documentation can be used. ```javascript import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; ``` ### Vyper support Foundry supports compiling and testing Vyper contracts. #### 1. Compilation You can install Vyper by following the instructions [here](https://vyper.readthedocs.io/en/stable/installing-vyper.html). If you have `vyper` available in your PATH, foundry will automatically use it. Otherwise, you can set the path to `vyper` in your `foundry.toml` by adding the following: ```toml [vyper] path = "/path/to/vyper" ``` ##### Vyper libraries via `forge install` If you want an import like the following to work in your Vyper contract: ```vyper from snekmate.utils import eip712_domain_separator ``` You can install Vyper the desired library via `forge install` e.g. `forge install pcaversaccio/snekmate`. You then need to adjust your `foundry.toml` as follows (replacing "snekmate" with the name of your desired package): ```toml skip = ["**/lib/snekmate/**"] libs = ["lib", "lib/snekmate/src"] ``` ##### Vyper libraries via `pip` Alternatively if you want to install the package via `pip` into your system's python configuration or a virtual environment you can point foundry to it by modifying your `foundry.toml` as follows: ```toml # Assuming you have a virtual environment in `.venv` and are using Python 3.12 libs = ["lib", ".venv/lib/python3.12/site-packages/"] ``` Note that compatible alternative python package managers like `uv` will work too. #### 2. Solidity tests Let's write a test for this simple Counter contract: ```vyper number: public(uint256) @deploy @payable def __init__(initial_number: uint256): self.number = initial_number @external def set_number(new_number: uint256): self.number = new_number @external def increment(): self.number += 1 ``` We can deploy it by using the `deployCode` cheatcode from `forge-std` and test it with the following Solidity test: ```solidity import {Test} from "forge-std/Test.sol"; interface ICounter { function increment() external; function number() external view returns (uint256); function set_number(uint256 newNumber) external; } contract CounterTest is Test { ICounter public counter; uint256 initialNumber = 5; function setUp() public { counter = ICounter(deployCode("Counter", abi.encode(initialNumber))); assertEq(counter.number(), initialNumber); } function test_Increment() public { counter.increment(); assertEq(counter.number(), initialNumber + 1); } function testFuzz_SetNumber(uint256 x) public { counter.set_number(x); assertEq(counter.number(), x); } } ``` #### 3. Deploying You can deploy Vyper contracts via `forge create` command: ```bash forge create Counter --constructor-args '1' --rpc-url $RPC_URL --private-key $PRIVATE_KEY ``` And with `deployCode` you can deploy Vyper contracts in your scripts as well: ```solidity import {Script} from "forge-std/Script.sol"; contract CounterScript is Script { function run() public { vm.broadcast(); deployCode("src/Counter.vy", abi.encode(1)); } } ``` #### 4. Vyper scripts You can write Vyper scripts in the same way as Solidity scripts: ```vyper interface Vm: def startBroadcast(): nonpayable interface ICounter: def increment(): nonpayable def number() -> uint256: view vm: constant(Vm) = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D) @external def run(counter: address): number_before: uint256 = staticcall ICounter(counter).number() extcall vm.startBroadcast() extcall ICounter(counter).increment() number_after: uint256 = staticcall ICounter(counter).number() assert number_after == number_before + 1 ``` Such script can be run with the following command: ```bash forge script script/Increment.s.vy --sig 'run' '' --rpc-url $RPC_URL --broadcast --private-key $PRIVATE_KEY ``` #### 5. Limitations * While you can write and run tests and scripts in Vyper, there is no `new` keyword in Vyper allowing you to deploy contracts. This will be addressed in the future with new cheatcodes. * Vyper does not allow overloads with the same names but different parameter types. Thus some cheatcode combinations might require workarounds to be used. (e.g. `startBroadcast(address sender))` and `startBroadcast(uint256 pk)`) * `forge coverage` currently does not support Vyper contracts. ## Deploy-time linking with zksolc Contrary to `solc`, unlinked bytecode in `zksolc` contains their link references using the ELF format, thus a simple text search & replace is not sufficient. This document doesn't go into detail on how to perform linking, for that please refer to the official compiler documentation, instead it focuses on *where* this happens within `foundry-zksync`. ### General flow The process of linking happens in 2 separate sections of `foundry` code, during the build of the `MultiContractRunner` for `forge test` and during the build of `LinkedBuildData` as part of `forge script`. The main difference between the two is that tests collect all missing library references across all contracts and collate them together to then deploy them for each test contract, effectively deploying all libraries for all tests; contrary to scripts where `foundry` will collect references and link only libraries in the target script's dependency tree. Therefore, the general flow is: 1. collect all dependencies of the target(s) 2. perform linking by precalculating the address of each library a. using `CREATE` with a single pass (tests only allow this methodology) b. using `CREATE2` with multiple passes until the list exhaustion 3. collect the linked contracts as well as the libraries to deploy a. specifically for zksync: register linked contracts in `DualCompiledContracts` 4. perform deployment according to the selected opcode 5. deploy test/script & invoke We will now go into the details of each step, explaining the differences between the EVM flow and EraVM's. #### Dependency resolution The main difference in dependency resolution is the introduction of factory dependencies. This means that factory dependencies may remain unlinked during compilation due to their reliance on a (missing) library. Unlinked contracts don't have a bytecode hash, thus they are not present in the compiler output's `factoryDependencies`, but instead are present in the new `factoryDependenciesUnlinked` encoded as a set of `:` describing the referenced contracts. We thus also traverse the unlinked factory dependencies to collect the library references in use, to be able to fully link all the dependencies of the target contract. It's important to match the references for factory deps with the link input contracts so the compiler can resolve them. #### Address precalculation Here the real difference is the computation of the address, because zksync uses a different prefix (and also different parameters for `CREATE2`). No other major changes, other than ignoring the factory deps's address calculation as that doesn't apply here. #### Linked contracts This is the main difference, where for `solc` only a text & replace is necessary, for `zksolc` we need to invoke the compiler with the `--link` argument and pass the list of bytecodes to link as well as the list of libraries to link against. The output will contain both the ignored and remaining unlinked contracts (which should be empty due to foundry linking everything) as well as the newly fully linked bytecodes. After this process we consume the linked bytecodes to register them in the context's `DualCompiledContracts`, to be able to translate between EVM and EraVM. The list of libraries to deploy remains unused, as instead the `DualCompiledContracts` collection contains the bytecode necessary to perform the EraVM deployment #### Library deployment This step happens twice, as we now need to deploy the library both in EVM and EraVM, due to the difference in address calculation between the two VMs. We relegate most of this to the strategy's `deploy_library` method which takes care of doing the deployment in the backend as well as returning the equivalent broadcastable transaction that is useful in the context of `forge script`. As far as EraVM goes, we reproduce the environment before the EVM deployment and then execute the deployment according to the scheme in use, and if it's `CREATE2` we make use of the `CREATE2` factory. #### Test/script deployment and invocation No changes here, as now any contract deployed on EraVM that depended on a library will contain the appropriate address, which contains the appropriate EraVM bytecode ## Nonces Nonces in EVM are generally 8-bytes long and are incremented for both external transactions and `CREATE` opcodes. ZKsync deviates from EVM nonces in the form that the nonces are split into `transaction nonce` and `deployment nonce`, which are incremented on external transactions and deployments, respectively. The nonces have a theoretical maximum size of 16-bytes but the rpc as of now only works with 4-bytes. ### `forge test`: EVM In the following forge test, the nonce for `NonceTest` contract increases as follows (assuming starting nonce of `0`): ```sol contract NonceTest is Test { function testNonces() public { // nonce(0): start Counter c = new Counter(); // nonce(1): CREATE c.setNumber(10); // nonce(1): CALL } } ``` In case of broadcast, we have: ```sol contract NonceTest is Test { function testNonces() public { // nonce(0): start vm.startBroadcast(); Counter c = new Counter(); // nonce(1): CREATE, broadcasted c.setNumber(10); // nonce(2): CALL, broadcasted vm.stopBroadcast(); } } ``` The hooks in the `Cheatcodes` inspector are responsible for manually incrementing the nonce when broadcast is enabled, as each recorded transaction is no externally broadcasted to an rpc. ### `forge test`: ZKsync For ZKSync, we instead get the following behavior: ```sol contract NonceTest is Test { function testNonces() public { // txNonce(0) deployNonce(0): start Counter c = new Counter(); // txNonce(0) deployNonce(1): CREATE c.setNumber(10); // txNonce(0) deployNonce(1): CALL } } ``` In case of broadcast, we have: ```sol contract NonceTest is Test { function testNonces() public { // txNonce(0) deployNonce(0): start vm.startBroadcast(); Counter c = new Counter(); // txNonce(1) deployNonce(1): CREATE, broadcasted c.setNumber(10); // txNonce(2) deployNonce(1): CALL, broadcasted vm.stopBroadcast(); } } ``` ### Nonce Correction As explained in [Execution Overview](../execution-overview.md#zksync-mode), we intercept each `CALL` or `CREATE` opcode in EVM context and execute it in zkEVM context. This, unfortunately, leads to the following inconsistencies: 1. The `CALL` or `CREATE` must be dispatched as an external L2 transaction. 2. The initiator of the transaction **must** be an EOA. Therefore the `msg.sender`/`NonceTest` can not be used. We set the `tx.origin`/`initiator_address` to be the default foundry caller used for the test execution. We then need to correctly set the `msg.sender` to be the `NonceTest` contract during the zkEVM execution. This is achieved by overriding zkEVM's call stack within the `DynTracer` implementation when we detect `executeTransaction` call. This step makes it so that the deployment nonce and the balance of the actual `msg.sender` is updated instead of the `tx.origin`. We cannot do the same for the earlier `validateTransaction` which is responsible for incrementing the transaction nonce due to additional safeguards in the bootloader for the validating step, that ensure the nonces are updated for the `initiator_address` and are updated correctly. This leads to the deployment nonce and balance being correctly incremented for `msg.sender` but the transaction nonce being updated for the `tx.origin`, **which must be reverted** as it's actually the wrapping of the original transaction in an L2 transaction that causes the increase. Without this adjustment, the transaction nonce for `tx.origin` will keep on increasing: ```sol // tx.origin is the signer address, and would have its nonce always increase without the fix. contract NonceTest is Test { function testNonces() public { // NonceTest{txNonce(0) deployNonce(0)} TxOrigin{txNonce(0) deployNonce(0)}: start Counter c = new Counter(); // NonceTest{txNonce(0) deployNonce(1)} TxOrigin{txNonce(1) deployNonce(0)}: CREATE c.setNumber(10); // NonceTest{txNonce(0) deployNonce(1)} TxOrigin{txNonce(2) deployNonce(0)}: CALL } } ``` Hence, we must **always** revert the nonce for the `initiator_address` specified in the L2 transaction that we dispatch to zkEVM. ### Other Nonce Adjustments #### Broadcast During a broadcast in EVM, the nonce is increased during a `CREATE`, no action is needed when broadcasting, however foundry increments the nonces manually for a `CALL`. However, during a broadcast In zkEVM, the nonce that will be increased during `CREATE` is the deployment nonce so we must still increase the transaction nonce manually so the transaction is broadcastable. This step isn't required in EVM. For `CALL` we increment the nonce manually just like for EVM. #### Batching Large Dependencies If a zkEVM transaction is too large to fit in a block, we must split the factory dependencies into smaller transactions. Here, we then execute empty transactions with just a subset of factory dependencies to mark them as known, and finally, the last transaction contains the actual transaction data. Therefore we need to manually increment the transaction nonce to make the next transaction of the batch to be executed correctly, as we are artificially splitting a single transaction into multiple ones. During the zkEVM execution, the transaction nonce will still be incremented once and then reverted, so the nonce must only increase for the "extra transactions" and not for the last one, as we started with a "good" nonce value. ### Storage Migration During a forge test/script execution we may switch between EVM and zkEVM; this causes issues for nonces in particular as while EVM uses a single nonce for both transactions and deployments, ZKsync splits them into two distinct nonces. When a EVM account is translated to ZKsync, we persist its existing deployment nonce and merely overwrite the transaction nonce with that of the EVM's. However, the EVM nonce value consists of deployments as well. Meanwhile when a ZKsync account is translated to EVM account we discard the deployment nonce and only work with transaction nonce in the EVM context. While these differences are generally unimportant as long as the nonce values are monotonically increasing, they may cause inconsistencies in derived addresses. ## ZKsync Developer Guide ZKsync specific caveats for developers. ## Using the zkUsePaymaster Cheatcode in General Flow Paymaster Contracts This example covers the use of a general flow paymaster contract. For this example, we will use the `GaslessPaymaster` contract from the paymaster example repository [here](https://github.com/matter-labs/paymaster-examples). ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@matterlabs/era-contracts/interfaces/IPaymaster.sol"; import "@matterlabs/era-contracts/interfaces/IPaymasterFlow.sol"; import "@matterlabs/era-contracts/Constants.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; /// @author Matter Labs /// @notice This contract does not include any validations other than using the paymaster general flow. contract GaslessPaymaster is IPaymaster, Ownable { constructor() Ownable(msg.sender) {} modifier onlyBootloader() { require( msg.sender == BOOTLOADER_FORMAL_ADDRESS, "Only bootloader can call this method" ); // Continue execution if called from the bootloader. _; } function validateAndPayForPaymasterTransaction( bytes32, bytes32, Transaction calldata _transaction ) external payable onlyBootloader returns (bytes4 magic, bytes memory context) { // By default we consider the transaction as accepted. magic = PAYMASTER_VALIDATION_SUCCESS_MAGIC; require( _transaction.paymasterInput.length >= 4, "The standard paymaster input must be at least 4 bytes long" ); bytes4 paymasterInputSelector = bytes4( _transaction.paymasterInput[0:4] ); if (paymasterInputSelector == IPaymasterFlow.general.selector) { // Note that while the minimal amount of ETH needed is tx.gasPrice * tx.gasLimit, // neither paymaster nor account are allowed to access this context variable. uint256 requiredETH = _transaction.gasLimit * _transaction.maxFeePerGas; // The bootloader never returns any data, so it can safely be ignored here. (bool success, ) = payable(BOOTLOADER_FORMAL_ADDRESS).call{ value: requiredETH }(""); require( success, "Failed to transfer tx fee to the Bootloader. Paymaster balance might not be enough." ); } else { revert("Unsupported paymaster flow in paymasterParams."); } } function postTransaction( bytes calldata _context, Transaction calldata _transaction, bytes32, bytes32, ExecutionResult _txResult, uint256 _maxRefundedGas ) external payable override onlyBootloader { // Refunds are not supported yet. } function withdraw(address payable _to) external onlyOwner { // Send paymaster funds to the owner uint256 balance = address(this).balance; (bool success, ) = _to.call{value: balance}(""); require(success, "Failed to withdraw funds from paymaster."); } receive() external payable {} } ``` This contract is a general-flow paymaster, meaning it can pay for any account. To use it, we must first deploy it in the intended network. We will deploy this example in the era-test-node and then use the `zkUsePaymaster` cheatcode to pay for a transaction using a script. ```solidity pragma solidity ^0.8.0; import {Script} from "forge-std/Script.sol"; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "../src/GeneralPaymaster.sol"; // We need to import the TestExt to use the zkUsePaymaster cheatcode // as this is a ZKsync-specific cheatcode import "../src/Counter.sol"; import {TestExt} from "forge-zksync-std/TestExt.sol"; contract PaymasterUsageScript is Script, TestExt { Counter public counter; function run() public { vm.startBroadcast(); GaslessPaymaster paymaster = new GaslessPaymaster(); // Fund the paymaster address(paymaster).call{value: 0.05 ether}(""); bytes memory paymaster_encoded_input = abi.encodeWithSelector( bytes4(keccak256("general(bytes)")), bytes("0x") ); vmExt.zkUsePaymaster( address(paymaster), paymaster_encoded_input ); counter = new Counter(); vm.stopBroadcast(); } } ``` The key part of this script is encoding the paymaster call with the `general(bytes)` selector and then using the `zkUsePaymaster` cheatcode to pay for the transaction. This will vary depending on the paymaster contract that you are using. ```solidity // This is the encoding for the GaslessPaymaster bytes memory paymaster_encoded_input = abi.encodeWithSelector( bytes4(keccak256("general(bytes)")), bytes("0x") ); // Using the encoded parameters to call the zkUsePaymaster cheatcode vmExt.zkUsePaymaster( address(paymaster), paymaster_encoded_input ); ``` After calling the `zkUsePaymaster` cheatcode, the paymaster will pay for the following transaction. ## Ledger Integration Example This example demonstrates how to use a Ledger hardware wallet to interact with the ZKsync network through foundry-zksync. ### Prerequisites * Ledger hardware wallet * Ledger Live application installed * Ethereum app installed on Ledger device * USB connection to computer ### Setup 1. Connect your Ledger device 2. Open the Ethereum app on the Ledger 3. Ensure the device is unlocked ### Script Configuration ```solidity import {Script} from "forge-std/Script.sol"; import {TestExt} from "forge-zksync-std/TestExt.sol"; contract LedgerScript is Script, TestExt { function run() external { // Use Ledger for transaction signing vm.startBroadcast(); // Deploy a simple contract Counter counter = new Counter(); // Interact with the contract counter.increment(); vm.stopBroadcast(); } } ``` ### Running with Ledger To run a script using your Ledger device: ```bash forge script LedgerScript --rpc-url $ZKSYNC_RPC_URL --ledger --zksync ``` ### Important Notes * Ensure your Ledger device is connected and unlocked * The Ethereum app must be open on the device * Transactions will require manual confirmation on the Ledger * ZKsync transactions are supported through the Ethereum app ### Troubleshooting * If the device is not detected, check USB connection * Ensure Ledger Live is closed when using with foundry * Verify the Ethereum app is the latest version ## ZKsync Specific Examples Here, you can see a few short examples of the tool running so you could experience the magic right away. * [Paymaster Approval Based](paymaster-approval-based): use of an approval-based paymaster contract * [General Flow Paymaster](general-paymaster): use of a general flow paymaster contract. * [Ledger](ledger): use a Ledger device to interact with ZKsync network. * [Smart Account](smart-account): configuration and deployment of a multisig smart account. Enjoy! ## Using the zkUsePaymaster Cheatcode in Approval-Based Paymaster Contracts This example covers the use of an approval-based paymaster contract. The paymaster contract used is the testnet paymaster of ZKsync documented [here](https://docs.zksync.io/build/start-coding/quick-start/paymasters-introduction). ### Steps Overview 1. Setup and Initialization * Create a custom ERC20 token contract. * Deploy the ERC20 contract. * Mint tokens to the address using the paymaster. 2. Approval and Paymaster Preparation * Create a paymaster contract. * Encode the paymaster call with the required parameters. * Use the zkUsePaymaster cheatcode. ### Step-by-Step Let's start by deploying the ERC20 contract and minting tokens for the account using the paymaster. The approval-based paymaster allows users to transfer ERC20 tokens to the paymaster, which pays for the transaction. This is the code for the ERC20 contract: ```solidity import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract MyERC20 is ERC20 { constructor() ERC20("SPITTE", "SPT") {} function mint(address to, uint256 amount) public { _mint(to, amount); } } ``` Now, in the script, we are going to run, we deploy the contract and mint some tokens to the account that is using the paymaster: ```solidity import {Script} from "forge-std/Script.sol"; import {console2} from "../lib/forge-std/src/console2.sol"; // We need to import the TestExt to use the zkUsePaymaster cheatcode // as this is a ZKsync specific cheatcode import {TestExt} from "forge-zksync-std/TestExt.sol"; contract PaymasterApprovalScript is Script, TestExt { function run() external { vm.startBroadcast(); MyERC20 erc20 = new MyERC20(); erc20.mint(address(tx.origin), 10); vm.stopBroadcast(); } } ``` Next, we prepare the encoded input for the paymaster: ```solidity // Encode the paymaster input bytes memory paymaster_encoded_input = abi.encodeWithSelector( bytes4(keccak256("approvalBased(address,uint256,bytes)")), address(erc20), // ERC20 token address uint256(1 ether), // Approval amount bytes("0x") // Additional data (empty in this case) ); ``` Here, we are encoding the paymaster input with the approvalBased method signature and the required parameters. The second parameter is the address of the recently deployed ERC20 contract, the third parameter is the amount of tokens the paymaster consumes from the user to pay for the transaction, and the last one is empty bytes. With the encoded input prepared, we can now use the zkUsePaymaster cheatcode to prepare the next call to be executed using the paymaster: ```solidity // Using zkUsePaymaster with the encoded input vmExt.zkUsePaymaster(address(0x3cB2b87D10Ac01736A65688F3e0Fb1b070B3eeA3), paymaster_encoded_input); Counter counter = new Counter(); counter.increment(); ``` The `counter.increment()` call will be executed using the paymaster we set up in the encoded input. ### Complete code Below is the complete code for the PaymasterTestScript demonstrating all the steps: ```solidity pragma solidity ^0.8.0; import {Script} from "forge-std/Script.sol"; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; // We need to import the TestExt to use the zkUsePaymaster cheatcode // as this is a ZKsync specific cheatcode import {TestExt} from "forge-zksync-std/TestExt.sol"; contract PaymasterTestScript is Script, TestExt { function run() external { vm.startBroadcast(); // Deploy the ERC20 contract MyERC20 erc20 = new MyERC20(); // Mint some tokens erc20.mint(address(tx.origin), 10); // Encode the paymaster input bytes memory paymaster_encoded_input = abi.encodeWithSelector( bytes4(keccak256("approvalBased(address,uint256,bytes)")), // Function selector address(erc20), // ERC20 address uint256(1 ether), // The uint256 value bytes("0x") // Empty bytes "0x" ); // Create a new Counter contract Counter counter = new Counter(); // Use the zkUsePaymaster cheatcode to prepare the next call to be executed using the paymaster vm.zkUsePaymaster(address(0x3cB2b87D10Ac01736A65688F3e0Fb1b070B3eeA3), paymaster_encoded_input); // Increment the counter counter.increment(); vm.stopBroadcast(); } } contract Counter { uint256 public count = 0; function increment() public { count++; } function getCount() public view returns (uint256) { return count; } } contract MyERC20 is ERC20 { constructor() ERC20("SPITTE", "SPT") {} function mint(address to, uint256 amount) public { _mint(to, amount); } } ``` ## Smart Account Example This example demonstrates the configuration and deployment of a multisig smart account on ZKsync. ### Overview Smart accounts on ZKsync provide enhanced functionality beyond traditional externally owned accounts (EOAs). This example covers: * Setting up a multisig smart account * Configuring multiple signers * Deploying the account contract * Performing transactions through the smart account ### Implementation ```solidity // Example smart account implementation contract MultiSigAccount { mapping(address => bool) public isOwner; uint256 public required; uint256 public ownerCount; constructor(address[] memory _owners, uint256 _required) { require(_required > 0 && _required <= _owners.length, "Invalid required number"); for (uint256 i = 0; i < _owners.length; i++) { require(_owners[i] != address(0), "Invalid owner"); isOwner[_owners[i]] = true; } ownerCount = _owners.length; required = _required; } // Additional smart account logic... } ``` ### Deployment Script ```solidity import {Script} from "forge-std/Script.sol"; import {TestExt} from "forge-zksync-std/TestExt.sol"; contract DeploySmartAccount is Script, TestExt { function run() external { vm.startBroadcast(); address[] memory owners = new address[](3); owners[0] = address(0x1234...); owners[1] = address(0x5678...); owners[2] = address(0x9abc...); MultiSigAccount account = new MultiSigAccount(owners, 2); vm.stopBroadcast(); } } ``` This example shows how to deploy and configure a multisig smart account that requires 2 out of 3 signatures for transactions. ### Debugger > 🚨 **Important** > > Debugger in not officially supported for ZKsync. Forge ships with an interactive debugger. The debugger is accessible on [`forge test`](/forge/reference/test), on [`forge script`](/forge/reference/script) and on [`cast run`](/cast/reference/run). You can only select a single function or a single transaction to debug at the time. Using `forge test` (or `forge script`): ```sh forge test --debug --match-test "" ``` Where `` is the function signature of the file you want to debug. For example: ```sh forge test --debug --match-test "test_Increment" ``` If the matching test is a fuzz test, the debugger will open the first failing fuzz scenario, or the last successful one, whichever comes first. For example: ```sh forge test --debug --match-test "testFuzz_SetNumber" ``` Using `cast run`: ```sh cast run --debug \ 0xd15e0237413d7b824b784e1bbc3926e52f4726e5e5af30418803b8b327b4f8ca ``` #### Debugger layout ![An image of the debugger UI](/debugger.png) When the debugger is run, you are presented with a terminal divided into four quadrants: * **Quadrant 1**: The opcodes in the debugging session, with the current opcode highlighted. Additionally, the address of the current account, the program counter and the accumulated gas usage is also displayed * **Quadrant 2**: The current stack, as well as the size of the stack * **Quadrant 3**: The source view * **Quadrant 4**: The current memory of the EVM As you step through your code, you will notice that the words in the stack and memory sometimes change color. For the memory: * **Red words** are about to be written to by the current opcode * **Green words** were written to by the previous opcode * **Cyan words** are being read by the current opcode For the stack, **cyan words** are either being read or popped by the current opcode. > ⚠️ **Note** > > In most test frameworks, the first test assertion to fail is the one reported. > In foundry, the last test assertion to fail (that comes from DSTest or cheatcodes) is the one to be reported. #### Navigating #### General * q: Quit the debugger * h: Show help #### Navigating calls * 0-9 + k: Step a number of times backwards (alternatively scroll up with your mouse) * 0-9 + j: Step a number of times forwards (alternatively scroll down with your mouse) * g: Move to the beginning of the transaction * G: Move to the end of the transaction * c: Move to the previous call-type instruction (i.e. [`CALL`][op-call], [`STATICCALL`][op-staticcall], [`DELEGATECALL`][op-delegatecall], and [`CALLCODE`][op-callcode]). * C: Move to the next call-type instruction * a: Move to the previous [`JUMP`][op-jump] or [`JUMPI`][op-jumpi] instruction * s: Move to the next [`JUMPDEST`][op-jumpdest] instruction * ' + a-z: Move to `` breakpoint set by a [`vm.breakpoint`][cheat-breakpoint] cheatcode #### Navigating memory * Ctrl + j: Scroll the memory view down * Ctrl + k: Scroll the memory view up * m: Show memory as UTF8 #### Navigating the stack * J: Scroll the stack view down * K: Scroll the stack view up * t: Show labels on the stack to see what items the current op will consume [op-call]: https://www.evm.codes/#f1 [op-staticcall]: https://www.evm.codes/#fa [op-delegatecall]: https://www.evm.codes/#f4 [op-callcode]: https://www.evm.codes/#f2 [op-jumpdest]: https://www.evm.codes/#5b [op-jump]: https://www.evm.codes/#f1 [op-jumpi]: https://www.evm.codes/#57 [cheat-breakpoint]: /reference/cheatcodes/breakpoint ### Deploying Forge can deploy smart contracts to a given network with the [`forge create`](/forge/reference/create) command. Forge CLI can deploy only one contract at a time. For deploying and verifying multiple smart contracts on multiple chains in one go, Forge's [Solidity scripting](/guides/scripting-with-solidity) would be the more efficient approach. To deploy a contract, you must provide a RPC URL (env: `ETH_RPC_URL`) and the private key of the account that will deploy the contract. Additionally the `--broadcast` flag is responsible for publishing your transaction to the network as a safety precaution and mirrors the `--broadcast` flag of `forge script`. If you do not pass the `--broadcast` flag your transaction is a dry-run. To deploy `MyContract` to a network: ```sh $ forge create --zksync --rpc-url --private-key --broadcast src/MyContract.sol:MyContract compiling... success. Deployer: 0xa735b3c25f... Deployed to: 0x4054415432... Transaction hash: 0x6b4e0ff93a... ``` Solidity files may contain multiple contracts. `:MyContract` above specifies which contract to deploy from the `src/MyContract.sol` file. Use the `--constructor-args` flag to pass arguments to the constructor: ```solidity // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; import {ERC20} from "solmate/tokens/ERC20.sol"; contract MyToken is ERC20 { constructor( string memory name, string memory symbol, uint8 decimals, uint256 initialSupply ) ERC20(name, symbol, decimals) { _mint(msg.sender, initialSupply); } } ``` Additionally, we can tell Forge to verify our contract on ZKsync Block Explorer, Etherscan, Sourcify or Blockscout, if the network is supported, by passing `--verify`. It is recommended to make use of ZKsync Block Explorer by specifying `--verifier zksync` and using the verification URL (e.g. ZKsync Sepolia `https://explorer.sepolia.era.zksync.dev/contract_verification`). ```sh $ forge create --zksync \ --rpc-url \ --constructor-args "ForgeUSD" "FUSD" 18 1000000000000000000000 \ --private-key \ --verifier zksync \ --verifier-url https://explorer.sepolia.era.zksync.dev/contract_verification \ --verify \ --broadcast ``` ### Multi-chain deployments Deploying and verifying multiple smart contracts on multiple chains in one go is possible by using forking cheatcodes. For example, if you want to deploy a `Counter` contract on Sepolia Mainnet and Base Sepolia using a single command, you can configure RPC endpoints and verifiers as: ```toml [rpc_endpoints] sepolia = "${SEPOLIA_URL}" base-sepolia = "${BASE_SEPOLIA_URL}" [etherscan] sepolia = { key = "${ETHERSCAN_API_KEY}" } base-sepolia = { key = "${ETHERSCAN_API_KEY}" } ``` and create a `CounterScript` script as: ```solidity contract CounterScript is Script { function run() public { vm.createSelectFork("sepolia"); vm.startBroadcast(); new Counter(); vm.stopBroadcast(); vm.createSelectFork("base-sepolia"); vm.startBroadcast(); new Counter(); vm.stopBroadcast(); } } ``` When running: ```sh forge script script/CounterScript.s.sol --slow --multi --broadcast --private-key --verify ``` The script will create the Sepolia Mainnet fork (`vm.createSelectFork("sepolia")`), deploy and verify the `Counter` contract, and then move to Base Sepolia chain deployment (`vm.createSelectFork("base-sepolia")`). For a list of all available forking cheatcodes see [`forking`](/reference/cheatcodes/forking) docs. ### Verifying a pre-existing contract It is recommended to use the `--verify` flag with `forge create` to automatically verify the contract on explorer after a deployment using `--verifier zksync` to target ZKsync Block Explorer instance. If you are verifying an already deployed contract, read on. You can verify a contract on ZKsync Block Explorer, Etherscan, Sourcify, oklink or Blockscout with the [`forge verify-contract`](/forge/reference/verify-contract) command. You must provide: * the contract address * the contract name or the path to the contract `:` * your Etherscan API key (env: `ETHERSCAN_API_KEY`) (if verifying on Etherscan or similar explorers e.g. BscScan / BaseScan / Polygonscan). Moreover, you may need to provide: * the constructor arguments in the ABI-encoded format, if there are any * external linked libraries in `src_file_path:library_name:library_address` format, if there are any * [compiler version](https://etherscan.io/solcversions) used for build, with 8 hex digits from the commit version prefix (the commit will usually not be a nightly build). It is auto-detected if not specified. * the number of optimizations, if the Solidity optimizer was activated. It is auto-detected if not specified. * the [chain ID](https://evm-chainlist.netlify.app/), if the contract is not on Ethereum Mainnet Let's say you want to verify `MyToken` (see above). You set the [number of optimizations](/config/reference/solidity-compiler#optimizer_runs) to 1 million, compiled it with v0.8.10, and deployed it, as shown above, to the Sepolia testnet (chain ID: 11155111). Note that `--num-of-optimizations` will default to 0 if not set on verification, while it defaults to 200 if not set on deployment, so make sure you pass `--num-of-optimizations 200` if you left the default compilation settings. Here's how to verify it: ```bash forge verify-contract \ --zksync \ --chain zksync-testnet \ --num-of-optimizations 1000000 \ --watch \ --verifier zksync \ --verifier-url https://explorer.sepolia.era.zksync.dev/contract_verification \ --constructor-args $(cast abi-encode "constructor(string,string,uint256,uint256)" "ForgeUSD" "FUSD" 18 1000000000000000000000) \ \ src/MyToken.sol:MyToken Submitting verification for [src/MyToken.sol:MyToken] at address 0x21d6dffe4B406c59E80CD62b4cB1763363c8a040. Verification submitted successfully. Verification ID: 27574 Checking verification status for ID: 27574 using verifier: ZKsync at URL: https://explorer.sepolia.era.zksync.dev/contract_verification Verification was successful. ``` > ℹ️ **Note:** > > External libraries can be specified with `--libraries` argument, one for each linked library. For example, to verify a contract with two linked libraries (`Maths` and `Utils`) the `forge verify-command` should be run with > > ```bash > --libraries src/lib/Maths.sol:Maths: \ > --libraries src/lib/Utils.sol:Utils: > ``` > > arguments. It is recommended to use the [`--watch`](/forge/reference/verify-contract#verify-contract-options) flag along with `verify-contract` command in order to poll for the verification result. If the `--watch` flag was not supplied, you can check the verification status with the [`forge verify-check`](/forge/reference/verify-check) command: ```bash $ forge verify-check --zksync --chain zksync-testnet --verifier zksync Contract successfully verified. ```
> 💡 **Tip** > > Use Cast's [`abi-encode`](/cast/reference/abi-encode) to ABI-encode arguments. > > In this example, we ran `cast abi-encode "constructor(string,string,uint8,uint256)" "ForgeUSD" "FUSD" 18 1000000000000000000000` to ABI-encode the arguments.
#### Troubleshooting ##### `missing hex prefix ("0x") for hex string` Make sure the private key string begins with `0x`. ##### `EIP-1559 not activated` EIP-1559 is not supported or not activated on the RPC server. Pass the `--legacy` flag to use legacy transactions instead of the EIP-1559 ones. If you do development in a local environment, you can use Hardhat instead of Ganache. ##### `Failed to parse tokens` Make sure the passed arguments are of correct type. ##### `Signature error` Make sure the private key is correct. ##### `Compiler version commit for verify` If you want to check the exact commit you are running locally, try: ` ~/.svm/0.x.y/solc-0.x.y --version` where `x` and `y` are major and minor version numbers respectively. The output of this will be something like: ```bash solc, the solidity compiler commandline interface Version: 0.8.12+commit.f00d7308.Darwin.appleclang ``` Note: You cannot just paste the entire string "0.8.12+commit.f00d7308.Darwin.appleclang" as the argument for the compiler-version. But you can use the 8 hex digits of the commit to look up exactly what you should copy and paste from [compiler version](https://etherscan.io/solcversions). ##### `Invalid API Key` With [Etherscan API V2](https://docs.etherscan.io/etherscan-v2), only Etherscan keys are valid, this can be used for similar explorers eg BscScan/BaseScan/Polygonscan. Legacy keys from other explorers have been deprecated. #### Known Issues ##### Verifying Contracts With Ambiguous Import Paths Forge passes source directories (`src`, `lib`, `test` etc) as `--include-path` arguments to the compiler. This means that given the following project tree ```text |- src |-- folder |--- Contract.sol |--- IContract.sol ``` it is possible to import `IContract` inside the `Contract.sol` using `folder/IContract.sol` import path. Etherscan is not able to recompile such sources. Consider changing the imports to use relative import path. ## Linting `forge lint` is a command that analyzes Solidity source files in your project to identify potential issues, and enforce coding standards It helps maintain code quality and consistency across your codebase. ### Examples 1. Lint all Solidity files in the project: ```sh forge lint ``` 2. Lint only files in a specific directory: ```sh forge lint src/contracts/ ``` 3. Lint with only `high` and `gas` severity lints: ```sh forge lint --severity high --severity gas ``` 4. Lint with specific lint ID and output as JSON: ```sh forge lint --only-lint incorrect-shift --json ``` ### Supported Lints This section details the lints supported by `forge lint`. Each lint includes an ID, a description of the issue it checks for, its severity, and examples of incorrect and correct code. #### High Severity ##### `incorrect-shift` Warns against shift operations where the operands might be in an unconventional or potentially erroneous order, specifically when a literal is shifted by a non-literal. In Solidity, bitwise shift operations (`<<` for left shift, `>>` for right shift) take the value to be shifted as the left operand and the number of bits to shift as the right operand. This lint rule uses a heuristic to flag potentially incorrect shift oferations. To do so, it identifies expressions where literal is shifted by a variable, which can often be an indication of a logical error where the operands were intended to be reversed. If that was indeed intended, it is recommended to replace literals by constants. Alternatively, the lint rule can be disabled. ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract IncorrectShift { uint64 const LARGE_NUM = 1 ether; uint256 foo = 100; function correct() public view returns (uint256) { // Shifting 'foo' by a literal '2'. return foo << 2; // Shifting a const 'LARGE_NUM' by a variable 'foo'. return LARGE_NUM << foo; } function incorrect() public view returns (uint256) { // Shifting a literal '2' by a variable 'foo'. This is likely an error. return 2 << foo; } } ``` To disable this lint for your project, you can add its ID to the `exclude_lints` array within the `[lint]` section of the `foundry.toml` configuration file: ```toml [lint] # ... rest of lint config ... exclude_lints = ["incorrect-shift"] ``` Alternatively, you can also disable this individual occurrence using [inline configuration](/config/reference/linter#inline-configuration). ##### `unchecked-call` Warns when low-level calls (`.call()`, `.delegatecall()`, `.staticcall()`) do not check the success return value. Low-level calls in Solidity return a tuple `(bool success, bytes memory data)`. Not checking the `success` value can lead to silent failures where the called function reverts but execution continues, potentially resulting in unexpected behavior or security vulnerabilities. ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract UncheckedCall { function correct() public { (bool success, ) = address(target).call(""); require(success, "Call failed"); // Or using if statement (bool ok, bytes memory data) = address(target).call(abi.encodeWithSignature("foo()")); if (!ok) revert("Call failed"); } function incorrect() public { // Unchecked call - success value is ignored address(target).call(""); // Unchecked call - only data is used (, bytes memory data) = address(target).call(""); } } ``` To disable this lint for your project, you can add its ID to the `exclude_lints` array within the `[lint]` section of the `foundry.toml` configuration file: ```toml [lint] # ... rest of lint config ... exclude_lints = ["unchecked-call"] ``` Alternatively, you can also disable this individual occurrence using [inline configuration](/config/reference/linter#inline-configuration). ##### `erc20-unchecked-transfer` Warns when ERC20 `transfer` or `transferFrom` calls do not check the return value. While the ERC20 standard specifies that these functions should return a boolean indicating success, not all implementations follow this pattern correctly. Some tokens revert on failure, while others return false. Not checking the return value can lead to situations where a transfer fails silently, causing loss of funds or incorrect contract state. ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract ERC20UncheckedTransfer { IERC20 public token; function correct() public { // Check return value with require require(token.transfer(recipient, amount), "Transfer failed"); // Or capture and check explicitly bool success = token.transferFrom(sender, recipient, amount); if (!success) revert("Transfer failed"); } function incorrect() public { // Unchecked transfer - return value ignored token.transfer(recipient, amount); // Unchecked transferFrom - return value ignored token.transferFrom(sender, recipient, amount); } } ``` To disable this lint for your project, you can add its ID to the `exclude_lints` array within the `[lint]` section of the `foundry.toml` configuration file: ```toml [lint] # ... rest of lint config ... exclude_lints = ["erc20-unchecked-transfer"] ``` Note that this lint could fire false positives, as it only checks the function name, but it doesn't ensure that the called address is an ERC20 contract. Because of that, you may have to disable individual occurrences using [inline configuration](/config/reference/linter#inline-configuration). #### Medium Severity ##### divide-before-multiply Warns against performing division before multiplication within the same expression, especially with integer arithmetic. In Solidity, integer division truncates (rounds down towards zero). Performing division before multiplication can lead to a loss of precision that might be unintended and could have been avoided by reordering operations. For example, `(a / b) * c` might result in `0` if `a < b`, even if `(a * c) / b` would have yielded a non-zero result. ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract DivideBeforeMultiply { function correct() public pure returns (uint256) { return (1 * 3) / 2; // Results in 1. } function incorrect() public pure returns (uint256) { return (1 / 2) * 3; // Results in 0. } } ``` To disable this lint for your project, you can add its ID to the `exclude_lints` array within the `[lint]` section of the `foundry.toml` configuration file: ```toml [lint] # ... rest of lint config ... exclude_lints = ["divide-before-multiply"] ``` Alternatively, you can also disable this individual occurrence using [inline configuration](/config/reference/linter#inline-configuration). ##### `unsafe-typecast` Warns against unsafe type conversions that may result in data loss or unexpected behavior. In Solidity, typecasts are unchecked and can introduce unexpected behavior when converting between types of different sizes. For example, casting from a larger type to a smaller one (such as `uint256` to `uint8`) will silently truncate the higher-order bits. This may result in data loss and subtle, hard-to-detect bugs. ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract UnsafeTypecast { function safe(uint256 largeValue) public pure { if (largeValue > type(uint8).max) revert(); // casting to 'uint8' is safe because we ensure 'largeValue' can fit above. // forge-lint: disable-next-line(unsafe-typecast) uint8 smallValue = uint8(largeValue); } function unsafe(uint256 largeValue) public pure { // This cast is unsafe: it truncates `largeValue` to fit into 8 bits, // discarding all but the lowest 8 bits (i.e., `largeValue % 256`). // Any value greater than 255 will lose data, which can lead to bugs. uint8 truncated = uint8(largeValue); } } ``` To disable this lint for your project, you can add its ID to the `exclude_lints` array within the `[lint]` section of the `foundry.toml` configuration file: ```toml [lint] # ... rest of lint config ... exclude_lints = ["unsafe-typecast"] ``` Alternatively, you can also disable this individual occurrence using [inline configuration](/config/reference/linter#inline-configuration). #### Informational / Style Guide ##### `pascal-case-struct` Ensures that struct names adhere to `PascalCase` (e.g., `MyStruct`) convention. This is a common styling guideline in Solidity to improve code readability and maintain consistency. Useful resources: [Solidity Style Guide - Struct Names](https://docs.soliditylang.org/en/latest/style-guide.html#struct-names) ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract PascalCaseStruct { // Correct struct MyStruct { uint256 data; } // Incorrect struct my_struct { uint256 data; } struct myStruct { uint256 data; } } ``` To disable this lint for your project, you can add its ID to the `exclude_lints` array within the `[lint]` section of the `foundry.toml` configuration file: ```toml [lint] # ... rest of lint config ... exclude_lints = ["pascal-case-struct"] ``` Alternatively, you can also disable this individual occurrence using [inline configuration](/config/reference/linter#inline-configuration). ##### `mixed-case-function` Ensures that function names adhere to `mixedCase` (e.g., `myFunction`) convention. This helps in differentiate functions from other identifiers like structs or events and is a standard practice. Useful resources: [Solidity Style Guide - Function Names](https://docs.soliditylang.org/en/latest/style-guide.html#function-names) ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract MixedCaseFunction { // Correct function myFunction() public pure returns (uint256) { return 1; } // Incorrect function MyFunction() public pure returns (uint256) { return 1; } function my_function() public pure returns (uint256) { return 1; } } ``` To disable this lint for your project, you can add its ID to the `exclude_lints` array within the `[lint]` section of the `foundry.toml` configuration file: ```toml [lint] # ... rest of lint config ... exclude_lints = ["mixed-case-function"] ``` Alternatively, you can also disable this individual occurrence using [inline configuration](/config/reference/linter#inline-configuration). ##### `mixed-case-variable` Ensures that mutable variable names (local variables and state variables that are not `constant` or `immutable`) adhere to `mixedCase` (e.g., `myVariable`) convention. This aligns with the general Solidity style for variable naming. ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract MixedCaseVariable { // Correct uint256 stateVariable; function correct() public { uint256 localVariable = 10; } // Incorrect uint256 state_variable; function incorrect() public { uint256 local_variable = 20; } } ``` To disable this lint for your project, you can add its ID to the `exclude_lints` array within the `[lint]` section of the `foundry.toml` configuration file: ```toml [lint] # ... rest of lint config ... exclude_lints = ["mixed-case-variable"] ``` Alternatively, you can also disable this individual occurrence using [inline configuration](/config/reference/linter#inline-configuration). ##### `screaming-snake-case-const` Ensures that `constant` variable names adhere to `SCREAMING_SNAKE_CASE` (e.g., `MY_CONSTANT`). This is the standard convention for constants in Solidity, making them easily identifiable. Useful resources: [Solidity Style Guide - Constants](https://docs.soliditylang.org/en/latest/style-guide.html#constants) ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract ScreamingSnakeCaseConstant { // Correct uint256 constant MY_CONSTANT = 1; // Incorrect uint256 constant myConstant = 2; uint256 constant my_constant = 3; } ``` To disable this lint for your project, you can add its ID to the `exclude_lints` array within the `[lint]` section of the `foundry.toml` configuration file: ```toml [lint] # ... rest of lint config ... exclude_lints = ["screaming-snake-case-constant"] ``` Alternatively, you can also disable this individual occurrence using [inline configuration](/config/reference/linter#inline-configuration). ##### `screaming-snake-case-immutable` Ensures that `immutable` variable names adhere to `SCREAMING_SNAKE_CASE` (e.g., `MY_IMMUTABLE_VAR`). Similar to constants, this convention helps in distinguish immutable variables and maintaining consistency. ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract ScreamingSnakeCaseImmutable { // Correct uint256 immutable MY_IMMUTABLE_VAR; // Incorrect uint256 immutable myImmutableVar; address immutable my_immutable_var; } ``` To disable this lint for your project, you can add its ID to the `exclude_lints` array within the `[lint]` section of the `foundry.toml` configuration file: ```toml [lint] # ... rest of lint config ... exclude_lints = ["screaming-snake-case-immutable"] ``` Alternatively, you can also disable this individual occurrence using [inline configuration](/config/reference/linter#inline-configuration). ##### `unused-import` Warns when imported symbols are not used anywhere in the source file. Unused imports increase deployment costs unnecessarily and reduce code clarity. This lint checks for: * Unused named imports: `import {Symbol} from "file.sol";` * Unused aliased imports: `import "file.sol" as Alias;` * Unused aliased named imports: `import {Symbol as Alias} from "file.sol";` > Note: Plain imports without aliases (`import "file.sol";`) are not checked by this lint as they might be used for their side effects. To disable this lint for your project, you can add its ID to the `exclude_lints` array within the `[lint]` section of the `foundry.toml` configuration file: ```toml [lint] # ... rest of lint config ... exclude_lints = ["unused-import"] ``` Alternatively, you can also disable this individual occurrence using [inline configuration](/config/reference/linter#inline-configuration). ##### `unaliased-plain-import` Warns when using plain imports without an alias. Plain imports like `import "file.sol";` can make it unclear where symbols are coming from and can lead to naming conflicts. Best practice is to either: * Use named imports: `import {Symbol1, Symbol2} from "file.sol";` * Use aliased imports: `import "file.sol" as FileAlias;` ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; // Correct - using named imports import {SafeMath, Math} from "./Math.sol"; // Correct - using aliased import import "./Utils.sol" as Utils; // Incorrect - plain import without alias import "./Helpers.sol"; ``` To disable this lint for your project, you can add its ID to the `exclude_lints` array within the `[lint]` section of the `foundry.toml` configuration file: ```toml [lint] # ... rest of lint config ... exclude_lints = ["unaliased-plain-import"] ``` Alternatively, you can also disable this individual occurrence using [inline configuration](/config/reference/linter#inline-configuration). #### Gas Optimizations ##### `asm-keccak256` Recommends using inline assembly for `keccak256` hashing when possible. The Solidity global function `keccak256()` involves memory allocation and copying which can be less gas-efficient than a direct inline assembly implementation that operates on memory directly, especially for hashing small, fixed-size data. For production use, consider using [Vectorized's EfficientHashLib](https://github.com/Vectorized/solady/blob/main/src/utils/EfficientHashLib.sol) from the Solady library, which provides highly optimized implementations for common hashing patterns. ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract HashOptimization { // Correct - inline assembly function correct(uint256 a, uint256 b) public pure returns (bytes32 hashedVal) { assembly { mstore(0x00, a) mstore(0x20, b) let hashedVal := keccak256(0x00, 0x40) } } // Alternative - using EfficientHashLib from Solady // import {EfficientHashLib} from "solady/utils/EfficientHashLib.sol"; // function efficient(uint256 a, uint256 b) public pure returns (bytes32) { // return EfficientHashLib.hash(a, b); // } // Incorrect function incorrect(uint256 a, uint256 b) public pure returns (bytes32) { return keccak256(abi.encodePacked(a, b)); } } ``` To disable this lint for your project, you can add its ID to the `exclude_lints` array within the `[lint]` section of the `foundry.toml` configuration file: ```toml [lint] # ... rest of lint config ... exclude_lints = ["asm-keccak256"] ``` Alternatively, you can also disable this individual occurrence using [inline configuration](/config/reference/linter#inline-configuration). #### Codesize Optimizations ##### `unwrapped-modifier-logic` Warns when modifiers contain logic that isn't wrapped between the `_` placeholder. In Solidity, code in modifiers that appears before or after the `_` is inlined at every function using that modifier, which can significantly increase contract size when the modifier is used multiple times. Moving complex logic into internal functions that are called from the modifier can reduce bytecode size by avoiding code duplication, especially for modifiers used across many functions. ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract UnwrappedModifierLogic { address owner; // Correct - Complex logic is wrapped in an internal function modifier onlyOwner() { _checkOwner(); _; } function _checkOwner(address who) internal view { require(who == owner, "Not owner"); // Additional complex checks... } // Incorrect - Logic directly in modifier gets duplicated modifier onlyOwnerIncorrect() { require(msg.sender == owner, "Not owner"); // This code is inlined at every function using this modifier _; } } ``` To disable this lint for your project, you can add its ID to the `exclude_lints` array within the `[lint]` section of the `foundry.toml` configuration file: ```toml [lint] # ... rest of lint config ... exclude_lints = ["unwrapped-modifier-logic"] ``` Alternatively, you can also disable this individual occurrence using [inline configuration](/config/reference/linter#inline-configuration). #### See Also [Lint config](/config/reference/linter) ### Forge Forge is a command-line tool that ships with Foundry. Forge tests, builds, and deploys your smart contracts. Forge is part of the Foundry suite and is installed alongside `cast`, `chisel`, and `anvil`. If you haven't installed Foundry yet, see [Foundry installation](/introduction/installation). #### Getting started The best way to understand Forge is to simply try it (in less than 30 seconds!). First, let's initialize a new `counter` example repository: ```sh forge init counter ``` Next `cd` into `counter` and build : ```sh forge build ``` ```console [⠊] Compiling... [⠔] Compiling 27 files with Solc 0.8.28 [⠒] Solc 0.8.28 finished in 452.13ms Compiler run successful! ``` Let's [test](https://book.getfoundry.sh/forge/tests#tests) our contracts: ```sh forge test ``` ```console [⠊] Compiling... No files changed, compilation skipped Ran 2 tests for test/Counter.t.sol:CounterTest [PASS] testFuzz_SetNumber(uint256) (runs: 256, μ: 31121, ~: 31277) [PASS] test_Increment() (gas: 31293) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 5.35ms (4.86ms CPU time) Ran 1 test suite in 5.91ms (5.35ms CPU time): 2 tests passed, 0 failed, 0 skipped (2 total tests) ``` Finally, let's run our deployment script: ```sh forge script script/Counter.s.sol ``` ``` [⠊] Compiling... No files changed, compilation skipped Script ran successfully. Gas used: 109037 If you wish to simulate on-chain transactions pass a RPC URL. ``` :::info See the [`forge` Reference](/forge/reference/forge) for a complete overview of all the available subcommands. ::: ### Creating an NFT with Solmate This tutorial will walk you through creating an OpenSea compatible NFT with Foundry ZKsync and [Solmate](https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC721.sol). A full implementation of this tutorial can be found [here](https://github.com/dutterbutter/foundry-zksync-nft-tutorial). ##### This tutorial is for illustrative purposes only and provided on an as-is basis. The tutorial is not audited nor fully tested. No code in this tutorial should be used in a production environment. #### Create project and install dependencies Start by setting up a Foundry project following the steps outlined in the [Getting started section](/introduction/installation). We will also install Solmate for their ERC721 implementation, as well as some OpenZeppelin utility libraries. Install the dependencies by running the following commands from the root of your project: ```bash forge install transmissions11/solmate Openzeppelin/openzeppelin-contracts ``` These dependencies will be added as git submodules to your project. #### Implement a basic NFT Next, we will remove the boilerplate contracts found in `src/Counter.sol`, `test/Counter.t.sol`, and `script/Counter.s.sol`. After that, create a new file in the `src/` directory named `NFT.sol` and replace its content with the following code: ```solidity // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.10; import {ERC721} from "solmate/tokens/ERC721.sol"; import {Strings} from "openzeppelin-contracts/contracts/utils/Strings.sol"; contract NFT is ERC721 { uint256 public currentTokenId; constructor( string memory _name, string memory _symbol ) ERC721(_name, _symbol) {} function mintTo(address recipient) public payable returns (uint256) { uint256 newItemId = ++currentTokenId; _safeMint(recipient, newItemId); return newItemId; } function tokenURI(uint256 id) public view virtual override returns (string memory) { return Strings.toString(id); } } ``` Let's take a look at this very basic implementation of an NFT. We start by importing two contracts from our git submodules. We import solmate's gas optimized implementation of the ERC721 standard which our NFT contract will inherit from. Our constructor takes the `_name` and `_symbol` arguments for our NFT and passes them on to the constructor of the parent ERC721 implementation. Lastly we implement the `mintTo` function which allows anyone to mint an NFT. This function increments the `currentTokenId` and makes use of the `_safeMint` function of our parent contract. #### Compile & deploy with forge To compile the NFT contract run `forge build --zksync`. You might experience a build failure due to wrong mapping: ```text Error: Compiler run failed error[6275]: ParserError: Source "lib/openzeppelin-contracts/contracts/contracts/utils/Strings.sol" not found: File not found. Searched the following locations: "/PATH/TO/REPO". --> src/NFT.sol:5:1: | 5 | import {Strings} from "openzeppelin-contracts/contracts/utils/Strings.sol"; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ``` this can be fixed by setting up the correct remapping. Create a file `remappings.txt` in your project and add the line ```text openzeppelin-contracts/=lib/openzeppelin-contracts/ ``` (You can find out more on remappings in [the dependencies documentation](/guides/project-setup/dependencies). By default the compiler output will be in the `zkout` directory. To deploy our compiled contract with Forge we have to set environment variables for the RPC endpoint and the private key we want to use to deploy. Set your environment variables by running: ```bash export RPC_URL= export PRIVATE_KEY= ``` Once set, you can deploy your NFT with Forge by running the below command while adding the relevant constructor arguments to the NFT contract: ```bash forge create NFT --rpc-url=$RPC_URL --private-key=$PRIVATE_KEY --constructor-args --zksync ``` If successfully deployed, you will see the deploying wallet's address, the contract's address as well as the transaction hash printed to your terminal. #### Minting NFTs from your contract Calling functions on your NFT contract is made simple with Cast, Foundry's command-line tool for interacting with smart contracts, sending transactions, and getting chain data. Let's have a look at how we can use it to mint NFTs from our NFT contract. Given that you already set your RPC and private key env variables during deployment, mint an NFT from your contract by running: ```bash cast send --rpc-url=$RPC_URL "mintTo(address)" --private-key=$PRIVATE_KEY ``` Well done! You just minted your first NFT from your contract. You can sanity check the owner of the NFT with `currentTokenId` equal to **1** by running the below `cast call` command. The address you provided above should be returned as the owner. ```bash cast call --rpc-url=$RPC_URL --private-key=$PRIVATE_KEY "ownerOf(uint256)" 1 ``` #### Extending our NFT contract functionality and testing Let's extend our NFT by adding metadata to represent the content of our NFTs, as well as set a minting price, a maximum supply and the possibility to withdraw the collected proceeds from minting. To follow along you can replace your current NFT contract with the code snippet below: ```solidity // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.10; import {ERC721} from "solmate/tokens/ERC721.sol"; import {Ownable} from "openzeppelin-contracts/contracts/access/Ownable.sol"; import {Strings} from "openzeppelin-contracts/contracts/utils/Strings.sol"; error MintPriceNotPaid(); error MaxSupply(); error NonExistentTokenURI(); error WithdrawTransfer(); contract NFT is ERC721, Ownable { using Strings for uint256; string public baseURI; uint256 public currentTokenId; uint256 public constant TOTAL_SUPPLY = 10_000; uint256 public constant MINT_PRICE = 0.08 ether; constructor( string memory _name, string memory _symbol, string memory _baseURI ) ERC721(_name, _symbol) Ownable(msg.sender) { baseURI = _baseURI; } function mintTo(address recipient) public payable returns (uint256) { if (msg.value != MINT_PRICE) { revert MintPriceNotPaid(); } uint256 newTokenId = currentTokenId + 1; if (newTokenId > TOTAL_SUPPLY) { revert MaxSupply(); } currentTokenId = newTokenId; _safeMint(recipient, newTokenId); return newTokenId; } function tokenURI(uint256 tokenId) public view virtual override returns (string memory) { if (ownerOf(tokenId) == address(0)) { revert NonExistentTokenURI(); } return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : ""; } function withdrawPayments(address payable payee) external onlyOwner { if (address(this).balance == 0) { revert WithdrawTransfer(); } (bool success, ) = payable(payee).call{value: address(this).balance}(""); require(success, "Transfer failed"); } function _checkOwner() internal view override { require(msg.sender == owner(), "Ownable: caller is not the owner"); } } ``` Among other things, we have added metadata that can be queried from any front-end application like OpenSea, by calling the `tokenURI` method on our NFT contract. > **Note**: If you want to provide a real URL to the constructor at deployment, and host the metadata of this NFT contract please follow the steps outlined [here](https://docs.opensea.io/docs/deploying-a-seadrop-compatible-contract). Let's test some of this added functionality to make sure it works as intended. Foundry offers an extremely fast EVM native testing framework through Forge. Within your test folder create the test file `NFT.t.sol`. This file will contain all tests regarding the NFT's `mintTo` method. Next, replace the existing boilerplate code with the below: ```solidity // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.10; import {Test} from "forge-std/Test.sol"; import {NFT} from "../src/NFT.sol"; contract NFTTest is Test { using stdStorage for StdStorage; NFT private nft; function setUp() public { // Deploy NFT contract nft = new NFT("NFT_tutorial", "TUT", "baseUri"); } function test_RevertMintWithoutValue() public { vm.expectRevert(MintPriceNotPaid.selector); // Make use of an address outside of the reserved address range nft.mintTo(address(65536)); } function test_MintPricePaid() public { // Make use of an address outside of the reserved address range nft.mintTo{value: 0.08 ether}(address(68536)); } function test_RevertMintMaxSupplyReached() public { uint256 slot = stdstore .target(address(nft)) .sig("currentTokenId()") .find(); bytes32 loc = bytes32(slot); bytes32 mockedCurrentTokenId = bytes32(abi.encode(10000)); vm.store(address(nft), loc, mockedCurrentTokenId); vm.expectRevert(MaxSupply.selector); // Make use of an address outside of the reserved address range nft.mintTo{value: 0.08 ether}(address(65536)); } function test_RevertMintToZeroAddress() public { vm.expectRevert("INVALID_RECIPIENT"); nft.mintTo{value: 0.08 ether}(address(0)); } function test_NewMintOwnerRegistered() public { // Make use of an address outside of the reserved address range nft.mintTo{value: 0.08 ether}(address(68536)); uint256 slotOfNewOwner = stdstore .target(address(nft)) .sig(nft.ownerOf.selector) .with_key(address(1)) .find(); uint160 ownerOfTokenIdOne = uint160( uint256( (vm.load(address(nft), bytes32(abi.encode(slotOfNewOwner)))) ) ); assertEq(address(ownerOfTokenIdOne), address(68536)); } function test_BalanceIncremented() public { // Make use of an address outside of the reserved address range nft.mintTo{value: 0.08 ether}(address(68536)); uint256 slotBalance = stdstore .target(address(nft)) .sig(nft.balanceOf.selector) .with_key(address(68536)) .find(); uint256 balanceFirstMint = uint256( vm.load(address(nft), bytes32(slotBalance)) ); assertEq(balanceFirstMint, 1); nft.mintTo{value: 0.08 ether}(address(68536)); uint256 balanceSecondMint = uint256( vm.load(address(nft), bytes32(slotBalance)) ); assertEq(balanceSecondMint, 2); } function test_SafeContractReceiver() public { Receiver receiver = new Receiver(); nft.mintTo{value: 0.08 ether}(address(receiver)); uint256 slotBalance = stdstore .target(address(nft)) .sig(nft.balanceOf.selector) .with_key(address(receiver)) .find(); uint256 balance = uint256(vm.load(address(nft), bytes32(slotBalance))); assertEq(balance, 1); } function test_RevertUnSafeContractReceiver() public { // Make use of an address outside of the reserved address range // Ensure bytecode is divisible by 32 vm.etch(address(65538), bytes.concat(bytes("mock code"), new bytes(23))); vm.expectRevert(bytes("")); nft.mintTo{value: 0.08 ether}(address(65538)); } function test_WithdrawalWorksAsOwner() public { // Mint an NFT, sending eth to the contract Receiver receiver = new Receiver(); address payable payee = payable(address(65539)); uint256 priorPayeeBalance = payee.balance; nft.mintTo{value: nft.MINT_PRICE()}(address(receiver)); // Check that the balance of the contract is correct assertEq(address(nft).balance, nft.MINT_PRICE()); uint256 nftBalance = address(nft).balance; // Withdraw the balance and assert it was transferred nft.withdrawPayments(payee); assertEq(payee.balance, priorPayeeBalance + nftBalance); } function test_WithdrawalFailsAsNotOwner() public { // Mint an NFT, sending eth to the contract Receiver receiver = new Receiver(); nft.mintTo{value: nft.MINT_PRICE()}(address(receiver)); // Check that the balance of the contract is correct assertEq(address(nft).balance, nft.MINT_PRICE()); // Confirm that a non-owner cannot withdraw vm.expectRevert("Ownable: caller is not the owner"); vm.startPrank(address(65540)); nft.withdrawPayments(payable(address(65540))); vm.stopPrank(); } } contract Receiver is ERC721TokenReceiver { function onERC721Received( address operator, address from, uint256 id, bytes calldata data ) external override returns (bytes4) { return this.onERC721Received.selector; } } ``` The test suite is set up as a contract with a `setUp` method which runs before every individual test. As you can see, Forge offers a number of [cheatcodes](/reference/cheatcodes/overview) to manipulate state to accommodate your testing scenario. For example, our `testFailMaxSupplyReached` test checks that an attempt to mint fails when the max supply of NFT is reached. Thus, the `currentTokenId` of the NFT contract needs to be set to the max supply by using the store cheatcode which allows you to write data to your contracts storage slots. The storage slots you wish to write to can easily be found using the [`forge-std`](https://github.com/foundry-rs/forge-std/) helper library. You can run the test with the following command: ```bash forge test --zksync ``` If you want to put your Forge skills to practice, write tests for the remaining methods of our NFT contract. Feel free to PR them to [nft-tutorial](https://github.com/dutterbutter/foundry-zksync-nft-tutorial), where you will find the full implementation of this tutorial. That's it, I hope this will give you a good practical basis of how to get started with foundry. We think there is no better way to deeply understand solidity than writing your tests in solidity. You will also experience less context switching between javascript and solidity. Happy coding! ## Deterministic deployment using CREATE2 on ZKsync ### Introduction Enshrined into the EVM as part of the Constantinople fork of 2019, CREATE2 is an opcode that started its journey as EIP-1014. CREATE2 allows you to deploy smart contracts to deterministic addresses, based on parameters controlled by the deployer. As a result, it's often mentioned as enabling "counterfactual" deployments, where you can interact with an addresses that haven't been created yet because CREATE2 guarantees known code can be placed at that address. This is in contrast to the CREATE opcode, where the address of the deployed contract is a function of the deployer's nonce. With CREATE2, you can use the same deployer account to deploy contracts to the same address across multiple networks, even if the address has varying nonces. > ℹ️ **Info** > This guide is intended to help understand CREATE2. In most use cases, you won't need to write and use your own deployer, and can use an existing deterministic deployer (`new MyContract{salt: salt}()`). In this tutorial, we will: * Look at a CREATE2 factory implementation. * Deploy the factory using the traditional deployment methods. * Use this deployed factory to in turn deploy a simple counter contract at a deterministic address. * Simulate this set of events by writing a simple test using Foundry ZKsync. ### Prerequisites Some familiarity with Solidity and Foundry is required, and some familiarity with inline assembly is recommended. Refer to the official Solidity docs for a primer on inline assembly. Make sure you have Foundry ZKsync installed on your system. Initialize a new Foundry project. Install the ZKsync contracts by running the following command in your project directory: ```bash forge install matter-labs/era-contracts ``` ### CREATE2 Factory Create a file named `Create2ZK.sol` Inside the `src` directory. Initialize a contract named `Create2ZK` like this: ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; contract Create2ZK { error Create2FailedDeployment(); } ``` The error is meant to enforce some sanity checks on the factory deployment, and revert the whole transaction when triggered. The `Create2FailedDeployment()` error triggers if the deployment fails for any reason. > ℹ️ **Info** > Please note that a CREATE2 deployment may fail due to a number of reasons. For example, if the bytecodeHash is invalid, or if a contract is already deployed at the computed address. Your deployment may also fail if your constructor reverts for any reason. Next, create a function named `deploy`: ```solidity function deploy(bytes32 salt, bytes32 bytecodeHash, bytes calldata inputData) external payable returns (address addr) { } ``` This function takes 3 inputs: * The `salt` used to calculate the final address. This can basically be any random value we want it to be. * The `bytecodeHash` of the contract that we want to deploy. * The `inputData` which are the constructor parameters of the contract. The address of the newly deployed contract is the returned after a successful deploy. > ℹ️ **Info** > You can send ETH to a contract that is being deployed using CREATE2, but only if it has a payable constructor. If you try to send ETH to it without a payable constructor, the transaction will revert. Next, we will call the `create2` function from the `ContractDeployer` system contract on ZKsync. This can be done by calling `SystemContractsCaller.systemCallWithReturndata` to interact with system contracts: To call the `create2` function, we need to pass in 3 parameters: ```solidity (bool success, bytes memory returnData) = SystemContractsCaller .systemCallWithReturndata( uint32(gasleft()), address(DEPLOYER_SYSTEM_CONTRACT), uint128(0), abi.encodeCall( DEPLOYER_SYSTEM_CONTRACT.create2, ( salt, bytecodeHash, inputData ) ) ); ``` * The `salt`: This is used to differentiate contract deployments and ensure unique contract addresses. It is a key part of the deterministic address generation in CREATE2. * The `bytecodeHash`: In ZKsync, contracts are deployed using the hash of the bytecode, not the bytecode itself. * The `inputData`: This contains the constructor arguments for the contract being deployed. Similar to traditional contract deployment, this field passes the initialization data to the contract being deployed. Alternatively, instead of writing your own deployment logic, you can leverage the `CREATE2Factory.sol` system contract, which simplifies calling the `create2` method. In many cases, you won't need to manually write a deployer function since you can use existing deterministic deployers, such as the `CREATE2Factory.sol` system contract, or deploy contracts directly using the `new MyContract{salt: salt}()` syntax. Here's an example of how you can use the `CREATE2Factory.sol`: ```solidity import {Create2Factory} from "era-contracts/system-contracts/contracts/Create2Factory.sol"; Create2Factory create2Factory = new Create2Factory(); address deployedAddress = create2Factory.create2( salt, bytecodeHash, abi.encode() ); ``` This method allows you to deploy a contract deterministically without having to write the deployment logic from scratch. It handles the `create2` call and returns the address of the newly deployed contract. This approach simplifies the deployment process by using a pre-built deployer contract, making it easier to manage and reuse your deployment logic across different projects. Finally, if the deployment fails for any reason, you can handle it by reverting the transaction, similar to how you would handle failure in the EVM: ```solidity if (!success) { revert Create2FailedDeployment(); } ``` ### Computing the Contract Address on ZKsync Lastly, we will create a view function named `computeAddress`. This function should take in the salt, bytecodeHash, and constructorInput as parameters and return the address of the contract that would be deployed using the deploy function on ZKsync: ```solidity function computeAddress( address sender, bytes32 salt, bytes32 bytecodeHash, bytes32 constructorInputHash ) external view returns (address addr) { } ``` Inside the function, we'll use the `L2ContractHelper.computeCreate2Address` method, which follows the address calculation logic specific to ZKsync: ```solidity import {L2ContractHelper} from "era-contracts/l2-contracts/contracts/L2ContractHelper.sol"; function computeAddress( address sender, bytes32 salt, bytes32 bytecodeHash, bytes32 constructorInputHash ) external view returns (address addr) { address computedAddress = L2ContractHelper.computeCreate2Address( sender, salt, bytecodeHash, constructorInputHash ); } ``` Here's the breakdown of the parameters and logic used in ZKsync's CREATE2 address calculation: * **Sender**: This refers to the address of the contract (typically the factory contract) calling the `create2` function. * **Salt**: The salt is used to differentiate deployments and ensure unique contract addresses, just like in traditional CREATE2 usage. * **Bytecode Hash**: In ZKsync, you must pass the hash of the contract bytecode. This hash must be known to the operator, as the actual bytecode is provided in the `factory_deps` field of the transaction. For more info on this refer to the docs here. * **Constructor Input Hash**: ZKsync requires the constructor input (or initialization) data to be hashed using `keccak256`. This hash is then included in the address derivation formula. The ZKsync-specific address derivation formula differs slightly from Ethereum's traditional CREATE2: ```solidity bytes32 hash = keccak256( bytes.concat( CREATE2_PREFIX, // ZKsync-specific prefix bytes32(uint256(uint160(_sender))), // Address of the contract deployer _salt, // Salt for the deployment _bytecodeHash, // Hash of the bytecode constructorInputHash // Hash of the constructor input data ) ); ``` > ℹ️ **Info** > The prefix (`CREATE2_PREFIX`) is specific to ZKsync, helping avoid collisions with Ethereum's CREATE2 opcode. The `keccak256` function is used to compute the hash from these components, and the address is derived from this hash. Finally, we will return the calculated address, ensuring it conforms to the ZKsync address derivation rules: ```solidity return address(uint160(uint256(hash))); ``` ### Formula Recap The formula that ZKsync uses to calculate the contract address is: ``` keccak256(zksyncCreate2 ++ address ++ salt ++ keccak256(bytecode) ++ keccak256(constructorInput))[12:] ``` * `zksyncCreate2` is a ZKsync-specific prefix to avoid collisions. * `address` is the contract deployer's address. * `salt` is the deployment salt. * `keccak256(bytecode)` is the hash of the contract bytecode. * `keccak256(constructorInput)` is the hash of the constructor data. These values are concatenated and passed through `keccak256` to produce a 32-byte hash, and the last 20 bytes are used as the deployed contract's address. > ℹ️ **Info** > You can check out the complete code for this implementation here. ### Testing our factory Create a file named `Create2ZK.t.sol` inside the `test` directory. Initialize a contract named `Create2ZKTest` like this: ```solidity // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.20; import {Test} from "forge-std/Test.sol"; import {Counter} from "../src/Counter.sol"; import {ZKCreate2} from "../src/Create2zk.sol"; import {ACCOUNT_CODE_STORAGE_SYSTEM_CONTRACT} from "era-contracts/system-contracts/contracts/Constants.sol"; contract Create2ZKTest is Test { } ``` Initialize the following state variables and the `setUp()` function: ```solidity Create2ZK internal create2ZK; Counter internal counter; function setUp() public { create2ZK = new Create2ZK(); counter = new Counter(); } ``` ### Deterministic Deployment Test We'll now create a function named `testDeterministicDeployment()` to do the following: * Deploy a new instance of the ZKCreate2 contract. * Allocate 100 ETH to the deployer address, using the `vm.deal` cheat code, and impersonate this address with the `prank` cheat code. * Set up the salt and bytecodeHash parameters. * Use the zkCreate2 contract to deploy the Counter contract at a deterministic address using the create2 system contract. * Assert that the computed address is equal to the deployed address. ```solidity function testDeterministicDeployment() public { address deployerAddress = address(create2ZK); // Deal 100 ETH to the deployer address vm.deal(deployerAddress, 100 ether); vm.startPrank(deployerAddress); // Set up salt and retrieve bytecode hash bytes32 salt = "12345"; bytes32 bytecodeHash = ACCOUNT_CODE_STORAGE_SYSTEM_CONTRACT.getRawCodeHash(address(counter)); // Compute the expected address using ZKsync's specific `CREATE2` logic address expectedAddress = zkCreate2.computeCreate2Address( deployerAddress, salt, bytecodeHash, keccak256(abi.encode()) // constructor input data hash ); // Deploy the contract using the `ZKCreate2` contract address deployedAddress = create2ZK.deploy( salt, bytecodeHash, abi.encode() // constructor input data ); vm.stopPrank(); // Log the computed and deployed addresses for debugging console.log("Computed address:", expectedAddress); console.log("Deployed address:", deployedAddress); // Assert that the computed address matches the deployed address assertEq(deployedAddress, expectedAddress); } ``` ### Explanation * `vm.deal`: This cheat code allocates 100 ETH to the deployer address, allowing it to fund contract deployments. * `vm.startPrank`: This makes the deployer address impersonate the caller for all subsequent calls, so we simulate real-world deployment scenarios. * `bytes32 salt`: The salt is used to ensure the deployed contract has a deterministic address. * `bytes32 bytecodeHash`: We retrieve the bytecode hash of the Counter contract from the `ACCOUNT_CODE_STORAGE_SYSTEM_CONTRACT` to pass it to the ZKsync CREATE2 function. * `abi.encode()`: We use this to pass constructor input data, hashed using `keccak256`. * `computeCreate2Address`: This function computes the expected address based on ZKsync's deterministic address calculation for CREATE2. * `deploy`: This deploys the contract using ZKsync's `ContractDeployer` system contract. Finally, we assert that the expected address matches the deployed address, ensuring that the contract was deployed deterministically. Save all your files, and run the test using `forge test --match-path test/Create2ZK.t.sol --zksync --enable-eravm-extensions -vvvv`. Your test should pass without any errors. ### Implementing and Testing EIP-712 signatures Foundry offers multiple utilities to make it easy and reliable to work with EIP-712 signatures. EIP-712 is a standard for hashing and signing typed structured data. Instead of signing an opaque hash, users can sign human-readable messages, significantly improving usability and security. This is particularly useful for meta-transactions, permit functions (like in ERC-20 permits), and other off-chain signature schemes. However, correctly implementing EIP-712 hashing logic can be intricate. Foundry's suite provides powerful utilities specifically designed to help developers test and validate their EIP-712 implementations with confidence. This guide will show you how to leverage Foundry's EIP-712 commands and cheatcodes with a practical, real-world example, demonstrating how to validate a complex library like Uniswap's `PermitHash.sol` from their Permit2 system. This will showcase how to ensure that a custom EIP-712 hashing implementation aligns perfectly with the standard. ### EIP-712 commands Forge offers a couple of commands which are useful when working with EIP-712 types: #### forge eip712 Outputs the canonical type definitions of the structs in the target files in the terminal. :::tip Use the `forge eip712` command to generate the canonical type definitions and manually copy-paste them into your contracts. This way you will avoid typos. ::: #### forge bind-json Automatically generates solidity bindings for the structs in the target files. The generated bindings can easily be serialized to JSON strings, and also parsed from JSON strings. Additionally, these bindings also allow the EIP-712 cheatcodes to derive the type definitions just their name. ### EIP-712 cheatcodes Foundry offers several cheatcodes to interact with EIP-712 types: ##### vm.eip712HashType * Generates the `typeHash` for an EIP-712 struct definition. This is `keccak256` of the canonical type encoding. * It can take a direct string definition (i.e. `"Mail(address from,string contents)"`) or a type name if you've used `forge bind-json` to generate bindings from your Solidity structs. ##### vm.eip712HashStruct * Computes the `structHash`: `keccak256(typeHash + encodeData(struct)).` * `encodeData(struct)` is the ABI-encoded values of the struct's members. * Like `vm.eip712HashType`, it accepts either a direct type definition string or a type name (with bindings). ##### vm.eip712HashTypedData * Generates the final EIP-712 digest to be signed: `keccak256("\x19\x01" + domainSeparator + structHash)`. * It takes a full JSON string representing the typed data as per the EIP-712 specification. Useful for end-to-end testing of signature verification. #### Testing Uniswap's `PermitHash` library Uniswap's `Permit2` system utilizes the `PermitHash.sol` library to create hashes that comply with the EIP-712 standard for various permit structures. In this guide, we will demonstrate how to use Foundry to verify that the library correctly implements the EIP-712 hashing rules. Our objective is to focus on a few hashing functions within `PermitHash.sol`. We will provide these functions with sample data and then use `vm.eip712HashStruct` —with the same data and the canonical EIP-712 type definition— to determine if the generated hashes match. #### Setting up the test environment Before starting with the validations, we have to create the `PermitHash.t.sol` test file. ```solidity // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.17; import "forge-std/Test.sol"; // Import the library we are testing import {PermitHash} from "src/libraries/PermitHash.sol"; import {IAllowanceTransfer as IAT} from "src/interfaces/IAllowanceTransfer.sol"; /* These are the structs, defined in `IAT`, that `PermitHash` relies on: struct PermitDetails { address token; uint160 amount; uint48 expiration; uint48 nonce; } struct PermitSingle { PermitDetails details; address spender; uint256 sigDeadline; } */ ``` > **Tip:** as previously explained, you can use `forge bind-json` to leverage Foundry's capabilities, and have higher guarantees when testing. By running that command, you can simply use the struct name when using the EIP-712 cheatcodes, and Foundry will automatically derive the canonical type definition. #### Validating `typHash` First of all, ensure that the type hashes for each of the structs are correct: ```solidity contract PermitHashTest is Test { function test_validatePermitDetails_typeHash() public { // This test doesn't rely on the bindings generated by `forge json`, therefore it requires // the string representation of the type as an input for the cheatcode. // Assume available on Uniswap's library. Otherwise you'd have to copy-paste it manually. string memory _PERMIT_DETAILS_TYPEDEF = "PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)"; // The type hash constant defined in Uniswap's library bytes32 typeHash = PermitHash._PERMIT_DETAILS_TYPEHASH; // Use the cheatcode to get the expected hash (with string representation) bytes32 expected = vm.eip712HashType(_PERMIT_DETAILS_TYPEDEF); assertEq(typeHash, expected, "PermitDetails typeHash mismatch"); } function test_validatePermitSingle_typeHash() public { // The type hash constant defined in Uniswap's library bytes32 typeHash = PermitHash._PERMIT_SINGLE_TYPEHASH; // Use the cheatcode to get the expected hash (needs bindings) bytes32 expected = vm.eip712HashType("PermitSingle"); assertEq(typeHash, expected, "PermitSingle typeHash mismatch"); } } ``` :::note If the library's `typeHash` was flawed, the assertion against the cheatcode would surface it. ::: #### Validating `structHash` After being certain that the hashes of the type definitions are correct, let's validate that the hashes of the structs follow the EIP-712 specification. ```solidity contract PermitHashTest is Test { address TOKEN = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; address SPENDER = 0xdEADBEeF00000000000000000000000000000000; function test_validatePermitDetails_structHash() public { // This test doesn't rely on the bindings generated by `forge bind-json`, therefore it requires // the string representation of the type as an input for the cheatcode. // Assume available on Uniswap's library. Otherwise you'd have to copy-paste it manually. string memory _PERMIT_DETAILS_TYPEDEF = "PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)"; // Prepare the test data for PermitDetails IAllowanceTransfer.PermitDetails memory details = IAllowanceTransfer.PermitDetails({ token: TOKEN, amount: 100 ether, expiration: uint48(block.timestamp + 3600), nonce: 123 }); // Get the structHash from Uniswap's library. // Despite private, assume it is available with a public function. bytes32 structHash = PermitHash._hashPermitDetails(details); // Use the cheatcode to get the expected hash (with string representation) bytes32 expected = vm.eip712HashStruct(_PERMIT_DETAILS_TYPEDEF, abi.encode(details)); assertEq(structHash, expected, "PermitDetails structHash mismatch"); } function test_validatePermitSingle_structHash() public { IAT.PermitDetails memory details = IAT.PermitDetails({ token: TOKEN, amount: 200 ether, expiration: uint48(block.timestamp + 7200), nonce: 456 }); IAT.PermitSingle memory permitSingle = IAT.PermitSingle({ details: details, spender: SPENDER, sigDeadline: block.timestamp + 10800 }); // Get the structHash from Uniswap's library. bytes32 structHash = PermitHash.hash(permitSingle); // Use the cheatcode to get the expected hash (needs bindings) bytes32 expectedStructHash = vm.eip712HashStruct("PermitSingle", abi.encode(permitSingle)); assertEq(structHash, expected, "PermitSingle structHash mismatch"); } } ``` :::note If the library's `structHash` was flawed, the assertion against the cheatcode would surface it. ::: ### Forking Mainnet with `Cast` and `Anvil` By combining [Anvil][anvil] and [Cast][cast], you can fork and test by interacting with contracts on a real network. The goal of this guide is to show you how to transfer DAI tokens from someone who holds DAI to an account created by Anvil. Follow these steps to fork mainnet and transfer DAI tokens: :::steps #### Fork mainnet with Anvil Let's start by forking mainnet. ```sh anvil --fork-url https://mainnet.infura.io/v3/$INFURA_KEY ``` You will see 10 accounts are created with their public and private keys. We will work with `0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266` (Let's call this user Alice). #### Set up environment variables Go to Etherscan and search for holders of DAI tokens ([here](https://etherscan.io/token/0x6b175474e89094c44da98b954eedeac495271d0f#balances)). Let's pick a random account. In this example we will be using `0xfc2eE3bD619B7cfb2dE2C797b96DeeCbD7F68e46`. Let's export our contracts and accounts as environment variables: ```sh export ALICE=0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 export DAI=0x6b175474e89094c44da98b954eedeac495271d0f export UNLUCKY_USER=0xfc2eE3bD619B7cfb2dE2C797b96DeeCbD7F68e46 ``` #### Check initial balances We can check Alice's balance using [`cast call`][cast-call]: ```sh cast call $DAI \ "balanceOf(address)(uint256)" \ $ALICE 0 ``` Similarly, we can also check our unlucky user's balance using `cast call`: ```sh cast call $DAI \ "balanceOf(address)(uint256)" \ $UNLUCKY_USER 21840114973524208109322438 ``` #### Transfer DAI tokens Let's transfer some tokens from the unlucky user to Alice using [`cast send`][cast-send]: ```sh # This calls Anvil and lets us impersonate our unlucky user cast rpc anvil_impersonateAccount $UNLUCKY_USER cast send $DAI \ --from $UNLUCKY_USER \ "transfer(address,uint256)(bool)" \ $ALICE \ 300000000000000000000000 \ --unlocked blockHash 0xbf31c45f6935a0714bb4f709b5e3850ab0cc2f8bffe895fefb653d154e0aa062 blockNumber 15052891 ... ``` #### Verify the transfer Let's check that the transfer worked: ```sh cast call $DAI \ "balanceOf(address)(uint256)" \ $ALICE 300000000000000000000000 cast call $DAI \ "balanceOf(address)(uint256)" \ $UNLUCKY_USER 21540114973524208109322438 ``` ::: [anvil]: ../reference/anvil/ [cast]: ../cast/reference/ [cast-call]: ../cast/reference/call.md [cast-send]: ../cast/reference/send.md ### Running Foundry inside of Docker This guide shows you how to build, test, and deploy a smart contract using Foundry's Docker image. It adapts code from the \[`first steps`] guide. If you haven't completed that guide yet, and are new to solidity, you may want to start with it first. Alternatively, if you have some familiarity with Docker and Solidity, you can use your own existing project and adjust accordingly. > This guide is for illustrative purposes only and provided on an as-is basis. The guide is not audited nor fully tested. No code in this guide should be used in a production environment. #### Installation and Setup The only installation required to run this guide is Docker, and optionally, an IDE of your choice. Follow the [Docker installation instructions](/introduction/installation). To keep future commands succinct, let's re-tag the image: `docker tag ghcr.io/foundry-rs/foundry:latest foundry:latest` Having Foundry installed locally is not strictly required, but it may be helpful for debugging. You can install it using [foundryup](/introduction/installation#using-foundryup). Finally, to use any of the `cast` or `forge create` portions of this guide, you will need access to an Ethereum node. If you don't have your own node running (likely), you can use a 3rd party node service. We won't recommend a specific provider in this guide. A good place to start learning about Nodes-as-a-Service is [Ethereum's article](https://ethereum.org/en/developers/docs/nodes-and-clients/nodes-as-a-service/) on the subject. **For the rest of this guide, it is assumed that the RPC endpoint of your ethereum node is set like this**: `export RPC_URL=` #### A tour around the Foundry docker image The docker image can be used in two primary ways: 1. As an interface directly to forge and cast 2. As a base image for building your own containerized test, build, and deployment tooling We will cover both, but let's start by taking a look at interfacing with foundry using docker. This is also a good test that your local installation worked! We can run any of the `cast` [commands](/cast/reference/cast) against our docker image. Let's fetch the latest block information: ```sh docker run foundry "cast block --rpc-url $RPC_URL latest" baseFeePerGas "0xb634241e3" difficulty "0x2e482bdf51572b" extraData "0x486976656f6e20686b" gasLimit "0x1c9c380" gasUsed "0x652993" hash "0x181748772da2f968bcc91940c8523bb6218a7d57669ded06648c9a9fb6839db5" logsBloom "0x406010046100001198c220108002b606400029444814008210820c04012804131847150080312500300051044208430002008029880029011520380060262400001c538d00440a885a02219d49624aa110000003094500022c003600a00258009610c410323580032000849a0408a81a0a060100022505202280c61880c80020e080244400440404520d210429a0000400010089410c8408162903609c920014028a94019088681018c909980701019201808040004100000080540610a9144d050020220c10a24c01c000002005400400022420140e18100400e10254926144c43a200cc008142080854088100128844003010020c344402386a8c011819408" miner "0x1ad91ee08f21be3de0ba2ba6918e714da6b45836" mixHash "0xb920857687476c1bcb21557c5f6196762a46038924c5f82dc66300347a1cfc01" nonce "0x1ce6929033fbba90" number "0xdd3309" parentHash "0x39c6e1aa997d18a655c6317131589fd327ae814ef84e784f5eb1ab54b9941212" receiptsRoot "0x4724f3b270dcc970f141e493d8dc46aeba6fffe57688210051580ac960fe0037" sealFields [] sha3Uncles "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" size "0x1d6bb" stateRoot "0x0d4b714990132cf0f21801e2931b78454b26aad706fc6dc16b64e04f0c14737a" timestamp "0x6246259b" totalDifficulty "0x9923da68627095fd2e7" transactions [...] uncles [] ``` If we're in a directory with some Solidity [source code](https://github.com/dmfxyz/foundry-docker-guide), we can mount that directory into Docker and use `forge` however we wish. For example: ```sh docker run -v $PWD:/app foundry "forge test --root /app --watch" ``` You can see our code was compiled and tested entirely within the container. Also, since we passed the `--watch` option, the container will recompile the code whenever a change is detected. #### Creating a "build and test" image Let's use the Foundry docker image as a base for using our own Docker image. We'll use the image to: 1. Build our solidity code 2. Run our solidity tests A simple `Dockerfile` can accomplish these two goals: ```docker # Use the latest foundry image FROM ghcr.io/foundry-rs/foundry # Copy our source code into the container WORKDIR /app # Build and test the source code COPY . . RUN forge build RUN forge test ``` You can build this docker image and watch forge build/run the tests within the container: ```sh docker build --no-cache --progress=plain . ``` Now, what happens if one of our tests fails? Modify `src/test/Counter.t.sol` to make a false assertion. Try to build image again. ```solidity function testFuzz_SetNumber(uint256 x) public { counter.setNumber(x); assertEq(counter.number(), 5); } ``` ```sh docker build --no-cache --progress=plain . <...> #9 0.522 Failed tests: #9 0.522 [FAIL: assertion failed: 425 != 5; counterexample: calldata=[...] args=[425]] testFuzz_SetNumber(uint256) (runs: 0, μ: 0, ~: 0) #9 0.522 #9 0.522 Suite result: FAILED. 1 passed; 1 failed; 0 skipped; finished in 686.53µs (407.06µs CPU time) ------ error: failed to solve: executor failed running [/bin/sh -c forge test]: exit code: 1 ``` Our image failed to build because our tests failed! This is actually a nice property, because it means if we have a Docker image that successfully built (and therefore is available for use), we know the code inside the image passed the tests.\* > \*Of course, chain of custody of your docker images is very important. Docker layer hashes can be very useful for verification. In a production environment, consider [signing your docker images](https://docs.docker.com/engine/security/trust/#:~\:text=To%20sign%20a%20Docker%20Image,the%20local%20Docker%20trust%20repository). #### Creating a "deployer" image Now, we'll move on to a bit more of an advanced Dockerfile. Let's add an entrypoint that allows us to deploy our code by using the built (and tested!) image. We can target the Sepolia testnet first. ```docker # Use the latest foundry image FROM ghcr.io/foundry-rs/foundry # Copy our source code into the container WORKDIR /app # Build and test the source code COPY . . RUN forge build RUN forge test # Set the entrypoint to the forge deployment command ENTRYPOINT ["forge", "create"] ``` Let's build the image, this time giving it a name: ```sh docker build --no-cache --progress=plain -t counter . ``` Here's how we can use our docker image to deploy: ```sh docker run counter-deployer --rpc-url $RPC_URL --private-key $PRIVATE_KEY ./src/Counter.sol:Counter No files changed, compilation skipped Deployer: 0x496e09fcb240c33b8fda3b4b74d81697c03b6b3d Deployed to: 0x23d465eaa80ad2e5cdb1a2345e4b54edd12560d3 Transaction hash: 0xf88c68c4a03a86b0e7ecb05cae8dea36f2896cd342a6af978cab11101c6224a9 ``` We've just built, tested, and deployed our contract entirely within a Docker container! This guide was intended for testnet, but you can run the exact same Docker image targeting mainnet and be confident that the same code is being deployed by the same tooling. #### Why is this useful? Docker is about portability, reproducibility, and environment invariance. This means you can be less concerned about unexpected changes when you switch between environments, networks, developers, etc. Here are a few basic examples of why one may like to use Docker images for smart contract deployment: * Reduces overhead of ensuring system level dependencies match between deployment environments (e.g. does your production runner always have the same version of `forge` as your dev runner?) * Increases confidence that code has been tested prior to deployment and has not been altered (e.g. if, in the above image, your code re-compiles on deployment, that's a major red flag). * Eases pain points around segregation of duties: people with your mainnet credentials do not need to ensure they have the latest compiler, codebase, etc. It's easy to ensure that the docker deploy image someone ran in testnet is identical to the one targeting mainnet. * Docker is an accepted standard on virtually all public cloud providers. It makes it easy to schedule jobs, tasks, etc that need to interact with the blockchain. #### Using `docker-compose` to launch Anvil To launch Anvil using [Docker Compose](https://docs.docker.com/compose/) one could use the following `docker-compose.yml` configuration: ```yml services: anvil: image: ghcr.io/foundry-rs/foundry container_name: anvil environment: ANVIL_IP_ADDR: "0.0.0.0" working_dir: /anvil ports: - "8545:8545" command: anvil ``` Finally, run `docker compose up`. ``` docker compose up [+] Running 1/1 ✔ Container anvil Created Attaching to anvil anvil | anvil | anvil | _ _ anvil | (_) | | anvil | __ _ _ __ __ __ _ | | anvil | / _` | | '_ \ \ \ / / | | | | anvil | | (_| | | | | | \ V / | | | | anvil | \__,_| |_| |_| \_/ |_| |_| anvil | anvil | 0.3.1-dev (fea38858b0 2025-01-21T16:48:49.865302749Z) anvil | https://github.com/foundry-rs/foundry anvil | ... ``` ### Solidity Scripting #### Introduction Solidity scripting is a way to declaratively deploy contracts using Solidity, instead of using the more limiting and less user friendly [`forge create`](/forge/reference/create). Solidity scripts are like the scripts you write when working with tools like Hardhat; what makes Solidity scripting different is that they are written in Solidity instead of JavaScript, and they are run on the fast Foundry backend, which provides dry-run capabilities. #### High Level Overview `forge script` does not work in a sync manner. First, it collects all transactions from the script, and only then does it broadcast them all. It can essentially be split into 4 phases: 1. Local Simulation - The contract script is run in a local evm. If a rpc/fork url has been provided, it will execute the script in that context. Any **external call** (not static, not internal) from a `vm.broadcast` and/or `vm.startBroadcast` will be appended to a list. 2. Onchain Simulation - Optional. If a rpc/fork url has been provided, then it will sequentially execute all the collected transactions from the previous phase here. 3. Broadcasting - Optional. If the `--broadcast` flag is provided and the previous phases have succeeded, it will broadcast the transactions collected at step `1`. and simulated at step `2`. > 💡 Note: > > Transactions that previously failed or timed-out can be submitted again by providing `--resume` flag. Given this flow, it's important to be aware that transactions whose behaviour can be influenced by external state/actors might have a different result than what was simulated on step `2`. Eg. frontrunning. #### Set Up Let's try to deploy the NFT contract made in the solmate tutorial with solidity scripting. First of all, we would need to create a new Foundry project via: ```sh forge init solidity-scripting ``` Since the NFT contract from the solmate tutorial inherits both `solmate` and `OpenZeppelin` contracts, we'll have to install them as dependencies by running: ```sh # Enter the project cd solidity-scripting # Install Solmate and OpenZeppelin contracts as dependencies forge install transmissions11/solmate Openzeppelin/openzeppelin-contracts@v5.0.1 ``` Next, we have to delete the `Counter.sol` file in the `src` folder and create another file called `NFT.sol`. You can do this by running: ```sh rm src/Counter.sol test/Counter.t.sol script/Counter.s.sol && touch src/NFT.sol && ls src ``` Once that's done, you should open up your preferred code editor and copy the code below into the `NFT.sol` file. ```solidity // SPDX-License-Identifier: UNLICENSED pragma solidity >=0.8.10; import {ERC721} from "solmate/tokens/ERC721.sol"; import {Ownable} from "openzeppelin-contracts/contracts/access/Ownable.sol"; import {Strings} from "openzeppelin-contracts/contracts/utils/Strings.sol"; error MintPriceNotPaid(); error MaxSupply(); error NonExistentTokenURI(); error WithdrawTransfer(); contract NFT is ERC721, Ownable { using Strings for uint256; string public baseURI; uint256 public currentTokenId; uint256 public constant TOTAL_SUPPLY = 10_000; uint256 public constant MINT_PRICE = 0.08 ether; constructor( string memory _name, string memory _symbol, string memory _baseURI ) ERC721(_name, _symbol) Ownable(msg.sender) { baseURI = _baseURI; } function mintTo(address recipient) public payable returns (uint256) { if (msg.value != MINT_PRICE) { revert MintPriceNotPaid(); } uint256 newTokenId = ++currentTokenId; if (newTokenId > TOTAL_SUPPLY) { revert MaxSupply(); } _safeMint(recipient, newTokenId); return newTokenId; } function tokenURI(uint256 tokenId) public view virtual override returns (string memory) { if (ownerOf(tokenId) == address(0)) { revert NonExistentTokenURI(); } return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : ""; } function withdrawPayments(address payable payee) external onlyOwner { uint256 balance = address(this).balance; (bool transferTx, ) = payee.call{value: balance}(""); if (!transferTx) { revert WithdrawTransfer(); } } } ``` Now, let's try compiling our contract to make sure everything is in order. ```sh forge build --zksync ``` If your output looks like this, the contracts successfully compiled. ![compile successful](../images/solidity-scripting/compile-successful.png) #### Deploying our contract We're going to deploy the `NFT` contract to the ZKsync Sepolia testnet, but to do this we'll need to configure Foundry ZKsync a bit, by setting things like a ZKsync Sepolia RPC URL, and the private key of an account that's funded with ZKsync Sepolia Eth. > 💡 Note: You can get some ZKsync Sepolia testnet ETH [here](https://thirdweb.com/zksync-sepolia-testnet) . ##### Environment Configuration Once you have all that create a `.env` file and add the variables. Foundry automatically loads in a `.env` file present in your project directory. The .env file should follow this format: ```sh ZKSYNC_SEPOLIA_RPC_URL= PRIVATE_KEY= ``` We now need to edit the `foundry.toml` file. There should already be one in the root of the project. Add the following lines to the end of the file: ```toml [rpc_endpoints] zksync-sepolia = "${ZKSYNC_SEPOLIA_RPC_URL}" ``` This creates a [RPC alias](/reference/cheatcodes/rpc) for ZKsync Sepolia. ##### Writing the Script Next, we have to create a folder and name it `script` and create a file in it called `NFT.s.sol`. This is where we will create the deployment script itself. The contents of `NFT.s.sol` should look like this: ```solidity // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; import {Script} from "forge-std/Script.sol"; import {NFT} from "../src/NFT.sol"; contract MyScript is Script { function run() external { uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(deployerPrivateKey); NFT nft = new NFT("NFT_tutorial", "TUT", "baseUri"); vm.stopBroadcast(); } } ``` > 💡 Note: > > The `vm.isContext` cheatcode can be used to execute logic specific to script phases, by checking if context is one of `ForgeContext.ScriptDryRun`, `ForgeContext.ScriptBroadcast` or `ForgeContext.ScriptResume`. Now let's read through the code and figure out what it actually means and does. ```solidity // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; ``` Remember even if it's a script it still works like a smart contract, but is never deployed, so just like any other smart contract written in Solidity the `pragma version` has to be specified. ```solidity import {Script} from "forge-std/Script.sol"; import {NFT} from "../src/NFT.sol"; ``` Just like we may import Forge Std to get testing utilities when writing tests, Forge Std also provides some scripting utilities that we import here. The next line just imports the `NFT` contract. ```solidity contract MyScript is Script { ``` We have created a contract called `MyScript` and it inherits `Script` from Forge Std. ```solidity function run() external { ``` By default, scripts are executed by calling the function named `run`, our entrypoint. ```solidity uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); ``` This loads in the private key from our `.env` file. **Note:** you must be careful when exposing private keys in a `.env` file and loading them into programs. This is only recommended for use with non-privileged deployers or for local / test setups. For production setups please review the various [wallet options](/forge/reference/script#wallet-options---raw) that Foundry supports. ```solidity vm.startBroadcast(deployerPrivateKey); ``` This is a special cheatcode that records calls and contract creations made by our main script contract. We pass the `deployerPrivateKey` in order to instruct it to use that key for signing the transactions. Later, we will broadcast these transactions to deploy our NFT contract. ```solidity NFT nft = new NFT("NFT_tutorial", "TUT", "baseUri"); ``` Here we have just created our NFT contract. Because we called `vm.startBroadcast()` before this line, the contract creation will be recorded by Forge, and as mentioned previously, we can broadcast the transaction to deploy the contract on-chain. The broadcast transaction logs will be stored in the `broadcast` directory by default. You can change the logs location by setting [`broadcast`](/config/reference/project#broadcast) in your `foundry.toml` file. The broadcasting sender is determined by checking the following in order: 1. If `--sender` argument was provided, that address is used. 2. If exactly one signer (e.g. private key, hardware wallet, keystore) is set, that signer is used. 3. Otherwise, the default Foundry sender (`0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38`) is attempted to be used. Now that you're up to speed about what the script smart contract does, let's run it. You should have added the variables we mentioned earlier to the `.env` for this next part to work. At the root of the project run: ```sh # To load the variables in the .env file source .env # To deploy our contract forge script --chain zksync-testnet script/NFT.s.sol:MyScript --rpc-url $ZKSYNC_SEPOLIA_RPC_URL --broadcast --zksync -vvvv ``` Forge is going to run our script and broadcast the transactions for us - this can take a little while, since Forge will also wait for the transaction receipts. This confirms that you have successfully deployed the `NFT` contract to the ZKsync Sepolia testnet. > 💡 Note: A full implementation of this tutorial can be found [here](https://github.com/dutterbutter/foundry-zksync-solidity-scripting) and for further reading about solidity scripting, you can check out the `forge script` [reference](/forge/reference/script). > This confirms that you have successfully deployed the `NFT` contract to the Sepolia testnet and have also verified it on Etherscan, all with one command. #### Scripting with Arguments Let's enhance our script to accept arguments, making it more flexible and reusable. This approach allows us to deploy different NFT contracts with varying names, symbols, and base URIs without modifying the script each time. We'll start by modifying the `NFT.s.sol` script: ```solidity // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; import {Script} from "forge-std/Script.sol"; import {NFT} from "../src/NFT.sol"; contract MyScript is Script { function run( string calldata _name, string calldata _symbol, string calldata _baseUri ) external { uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(deployerPrivateKey); NFT nft = new NFT(_name, _symbol, _baseUri); vm.stopBroadcast(); } } ``` At the root of the project run: ```sh # To load the variables in the .env file source .env # To deploy and verify our contract forge script --chain sepolia script/NFT.s.sol:MyScript "NFT tutorial" TUT baseUri --sig 'run(string,string,string)' --rpc-url $SEPOLIA_RPC_URL --broadcast --verify -vvvv ``` Let's break down the additions to our command: `"NFT tutorial" TUT baseUri --sig 'run(string,string,string)'` * `"NFT tutorial"` - is the first argument of the new run command - the name of the collection * `TUT` - is the second argument - the symbol of the collection * `baseUri` - is the third argument - the baseURI of the collection * `--sig 'run(string,string,string)'` - changes the signature of the function we want to call in the contract Forge is going to run our script and broadcast the transactions using the parameters we specified on the command line. You should see an output similar to the previous section. #### Deploying locally You can deploy to Anvil, the local testnet, by configuring the port as the `fork-url`. Here, we have two options in terms of accounts. We can either start anvil without any flags and use one of the private keys provided. Or, we can pass a mnemonic to anvil to use. ##### Using Anvil's Default Accounts First, start Anvil: ```sh anvil ``` Update your `.env` file with a private key given to you by Anvil. Then run the following script: ```sh forge script script/NFT.s.sol:MyScript --fork-url http://localhost:8545 --broadcast ``` ##### Using a Custom Mnemonic Add the following line to your `.env` file and complete it with your mnemonic: ```sh MNEMONIC= ``` It is expected that the `PRIVATE_KEY` environment variable we set earlier is one of the first 10 accounts in this mnemonic. Start Anvil with the custom mnemonic: ```sh source .env anvil -m $MNEMONIC ``` Then run the following script: ```sh forge script script/NFT.s.sol:MyScript --fork-url http://localhost:8545 --broadcast ``` > 💡 Note: A full implementation of this tutorial can be found [here](https://github.com/Perelyn-sama/solidity-scripting) and for further reading about solidity scripting, you can check out the `forge script` [reference](/forge/reference/script). ### Foundry Tutorial Videos Unofficial *youtube playlists* of Foundry tutorials from Blockchain educators. | URL | Description | Author | | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------: | | | *Blockchain Developer, Solidity, Foundry Full Course 2023* \~ Learn Solidity, Blockchain Development, & Smart Contracts Powered By AI - Full Course | Patrick Collins | | | *Foundry* \~ Playlist of beginner level videos on Foundry | Smart Contract Programmer | | | A Complete Introduction to Smart Contract Development With Foundry | Axelar | | | Cyfrin Updraft - Foundry Fundamentals | Cyfrin Updraft | | | Cyfrin Updraft - Advanced Foundry | Cyfrin Updraft | ### Getting Started Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust. It consists of four essential tools that suffice all the needs a blockchain app developer will ever have. Here's an overview of the tools available at your disposal after [running foundryup](/introduction/installation#using-foundryup): | Tool | What it enables | | -------------------------------- | ------------------------------------------------------------------- | | **[`forge`](/forge/overview)** | Build, test, debug, deploy and verify smart contracts | | **[`anvil`](/anvil/overview)** | Run a local Ethereum development node with forking capabilities | | **[`cast`](/cast/overview)** | Interact with contracts, send transactions, and retrieve chain data | | **[`chisel`](/chisel/overview)** | Fast Solidity REPL for rapid prototyping and debugging | :::tip You can always view detailed help for any command or subcommand by appending `--help` to it. ::: *** #### Forge Forge is a command-line tool for building, testing, and deploying smart contracts. ##### Initialize a new project ```bash # Create a new project called Counter forge init Counter cd Counter ``` ##### Build and test contracts ```bash # Compile your contracts forge build # Run your test suite forge test # Run tests against live chain state by forking forge test --fork-url https://reth-ethereum.ithaca.xyz/rpc ``` ##### Deploy contracts ```bash # Use forge scripts to deploy contracts # Set your private key export PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" # Deploy to local anvil instance forge script script/Counter.s.sol --rpc-url http://127.0.0.1:8545 --broadcast --private-key $PRIVATE_KEY ``` `forge` also enables more advanced workflows such as: * [Table testing](/forge/advanced-testing/table-testing) - Property-based testing with test cases inputs organized into a table format * [Fuzz testing](/forge/advanced-testing/fuzz-testing) - Property-based testing with randomized inputs * [Invariant testing](/forge/advanced-testing/invariant-testing) - Test system-wide properties across function call sequences * [Gas tracking](/forge/gas-tracking/overview) - Monitor and optimize gas consumption across your contracts * [Coverage reports](/forge/reference/coverage) - Generate detailed test coverage analysis with `forge coverage` Learn more about `forge` [here](/forge/overview). *** #### Anvil Anvil is a fast local Ethereum development node that is perfect for testing your contracts and other blockchain workflows in a controlled environment. ##### Start a local development node ```bash # Start anvil with 10 pre-funded accounts anvil ``` ##### Fork mainnet state ```bash # Fork latest mainnet state for testing anvil --fork-url https://reth-ethereum.ithaca.xyz/rpc ``` `anvil` comes up with other advanced capabilities such as: * **Custom `anvil_` methods** - Advanced node control including [account impersonation](/anvil/reference#anvil_impersonateaccount), [state manipulation](/anvil/reference#anvil_setbalance), and [mining control](/anvil/reference#anvil_mine) * **Forking capabilities** - Fork anvil off another live chain All of the above is provided while maintaining full compliance with the Ethereum JSON-RPC spec. Learn more about `anvil` [here](/anvil/overview). #### Cast Cast is your Swiss army knife for interacting with Ethereum applications from the command line. You can make smart contract calls, send transactions, or retrieve any type of chain data. ##### Read contract data ```bash # Check ETH balance cast balance vitalik.eth --ether --rpc-url https://reth-ethereum.ithaca.xyz/rpc # Call a contract function to read data cast call 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 \ "balanceOf(address)" 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 \ --rpc-url https://reth-ethereum.ithaca.xyz/rpc ``` ##### Send transactions ```bash # Set your private key export PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" # Send ETH to an address cast send 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 --value 10000000 --private-key $PRIVATE_KEY ``` ##### Interact with JSON-RPC ```bash # Call JSON-RPC methods directly cast rpc eth_getHeaderByNumber $(cast 2h 22539851) --rpc-url https://reth-ethereum.ithaca.xyz/rpc # Get latest block number cast block-number --rpc-url https://reth-ethereum.ithaca.xyz/rpc ``` Learn more about `cast` [here](/cast/overview). *** #### Chisel Chisel is a fast, utilitarian, and verbose Solidity REPL for rapid prototyping and debugging. It's perfect for testing Solidity snippets and exploring contract behavior interactively. ##### Start the REPL ```bash # Launch chisel REPL chisel ``` ##### Interactive Solidity development ```solidity // Create and query variables ➜ uint256 a = 123; ➜ a Type: uint256 ├ Hex: 0x7b ├ Hex (full word): 0x000000000000000000000000000000000000000000000000000000000000007b └ Decimal: 123 // Test contract functions ➜ function add(uint256 x, uint256 y) public pure returns (uint256) { return x + y; } ➜ add(5, 10) Type: uint256 └ Decimal: 15 ``` Learn more about `chisel` [here](/chisel/overview). ### Installation If you encounter any issues during installation, refer to the [FAQ](/misc/faq) for assistance. #### Precompiled Binaries Precompiled binaries can be downloaded from the [GitHub releases page](https://github.com/matter-labs/foundry-zksync/releases). We recommend using [Foundryup](#using-foundryup-zksync) for easier management. We are working on a polished versioning approach that will be released soon. However, we are also in a phase of active development. #### Using Foundryup-zksync Foundryup-zksync is the official installer for the Foundry-ZKsync toolchain. You can learn more about it [here](https://github.com/matter-labs/foundry-zksync/blob/main/foundryup-zksync/README.md). To install Foundryup-zksync, open your terminal and run the following command: ```sh curl -L https://raw.githubusercontent.com/matter-labs/foundry-zksync/main/install-foundry-zksync | bash ``` This will install Foundryup-zksync. Follow the on-screen instructions, and the `foundryup-zksync` command will become available in your CLI. Running `foundryup-zksync` automatically installs the latest nightly versions of the [precompiled binaries](#precompiled-binaries), including `forge` and `cast`. Additionally, it fetches the most recent version of the precompiled binary `anvil-zksync` from the [anvil-zksync releases](https://github.com/matter-labs/anvil-zksync/releases). Run ' foundryup-zksync—- help ' for additional options, such as installing a specific version or commit. > ℹ️ **Note** > > Only `forge` and `cast` are currently supported for ZKsync. Other commands retain their original behavior but may not work as intended. > ℹ️ **Note** > > If you're on Windows, you will need to install and use [Git BASH](https://gitforwindows.org/) or [WSL](https://learn.microsoft.com/en-us/windows/wsl/install) > since Foundryup-zksync currently does not support Powershell or Cmd. Windows support is currently provided as best-effort. This is also the case for older releases. #### Building from Source ##### Prerequisites You'll need the [Rust](https://rust-lang.org) compiler and Cargo, Rust's package manager. The easiest way to install both is by using [`rustup.rs`](https://rustup.rs/). Foundry-ZKsync generally supports building only with the [configured](https://github.com/matter-labs/foundry-zksync/blob/main/rust-toolchain) nightly Rust version. The presence of `rust-toolchain` file automatically downloads the correct nightly rust version when commands are run from the Foundry-ZKsync directory. For Windows users, you'll also need a recent version of [Visual Studio](https://visualstudio.microsoft.com/downloads/), with the "Desktop Development With C++" workload installed. ##### Building You can either use the different [Foundryup-ZKsync](#using-foundryup) flags: ```sh foundryup-zksync --branch main foundryup-zksync --path path/to/foundry-zksync ``` Alternatively, you can install it via Cargo with the following command: ```sh cargo install --git https://github.com/matter-labs/foundry-zksync --profile release --locked forge cast ``` Or, by manually building from a local copy of the [Foundry-ZKsync repository](https://github.com/matter-labs/foundry-zksync): ```sh # clone the repository git clone https://github.com/matter-labs/foundry-zksync.git cd foundry # install Forge cargo install --path ./crates/forge --profile release --force --locked # install Cast cargo install --path ./crates/cast --profile release --force --locked ``` #### CI Installation with GitHub Actions The latest binaries for the appropriate architecture can be installed directly using the following GitHub Action: ```yaml steps: - name: Install Foundry-ZKsync uses: dutterbutter/foundry-zksync-toolchain@v1 ``` For further details, visit the [foundry-zksync-toolchain repository](https://github.com/dutterbutter/foundry-zksync-toolchain). #### Using Foundry with Docker > ℹ️ **Note** > > No prebuilt images are available for docker yet. ### Foundry Overview ![Foundry banner](/og-image.png) Foundry is a smart contract development toolchain. Foundry manages your dependencies, compiles your project, runs tests, deploys, and lets you interact with the chain from the command-line and via Solidity scripts. ### Navigating the Documentation #### Getting Started Get up and running with Foundry by [installing the toolkit](/introduction/installation) and [get started](/introduction/getting-started) with the basics of each tool. #### Guides Comprehensive tutorials and best practices for building robust smart contracts and development workflows with Foundry. * **Best Practices** * [Writing Contracts](/guides/best-practices/writing-contracts) - Guidelines for clean, secure smart contract development * [Writing Tests](/guides/best-practices/writing-tests) - Effective testing strategies and patterns * [Writing Scripts](/guides/best-practices/writing-scripts) - Deployment and automation script best practices * [Security](/guides/best-practices/security) - Security considerations and vulnerability prevention * [Key Management](/guides/best-practices/key-management) - Safe handling of private keys and secrets * [Commenting](/guides/best-practices/commenting) - Documentation and code commenting standards * [Scripting with Solidity](/guides/scripting-with-solidity) - Advanced deployment and automation techniques * [Deterministic deployments using CREATE2](/guides/deterministic-deployments-using-create2) - Predictable contract addresses * [Forking Mainnet with Cast and Anvil](/guides/forking-mainnet-with-cast-anvil) - Test against live chain state * [Running Foundry inside of Docker](/guides/foundry-in-docker) - Containerized development environments * [Implementing and Testing EIP-712 signatures](/guides/eip712) #### Project Setup Learn how to organize your projects with the [forge project setup guides](/guides/project-setup/creating-a-new-project) for scaling smart contract codebases. #### Forge Master the core smart contract development tool with the [Forge overview](/forge/overview), covering building, testing, deploying, and verifying contracts. #### Cast Learn to interact with blockchain networks from the command line using [Cast](/cast/overview) for contract calls, transactions, and chain data retrieval. #### Anvil Set up local development networks with [Anvil](/anvil/overview), Foundry's fast Ethereum-compatible node with forking capabilities. #### Chisel Explore Solidity interactively with [Chisel](/chisel/overview), the integrated REPL for rapid prototyping and debugging. #### Configuration Customize your Foundry setup and integrate with other tools for an optimized development workflow. * [Config Overview with `foundry.toml`](/config/overview) - Project configuration and settings * [Continuous Integration](/config/continuous-integration) - CI/CD pipeline integration * [Integrating with VSCode](/config/vscode) - Editor setup and extensions * [Shell Autocompletion](/config/shell-autocompletion) - Command-line productivity enhancements * [Static Analyzers](/config/static-analyzers) - Code analysis tool integration * [Integrating with Hardhat](/config/hardhat) - Cross-framework compatibility * [Vyper support](/config/vyper) - Alternative smart contract language support #### Contributing Help improve Foundry by contributing - see the [contribution guidelines](https://github.com/foundry-rs/foundry/blob/master/CONTRIBUTING.md) to learn more. #### Reference Complete command references, configuration options, and API documentation for all Foundry tools. * [FAQ](/misc/faq) - Frequently asked questions and troubleshooting * **Command References** * [forge Commands](/forge/reference/forge) - Complete forge CLI reference * [cast Commands](/cast/reference/cast) - Complete cast CLI reference * [anvil Commands](/anvil/reference) - Complete anvil CLI reference * [chisel Commands](/chisel/reference) - Complete chisel CLI reference * **Configuration & APIs** * [Config Reference](/config/reference/overview) - All configuration options * [Cheatcodes Reference](/reference/cheatcodes/overview) - Testing utilities and helpers * [Forge Standard Library Reference](/reference/forge-std/overview) - Standard library documentation * [DSTest Reference](/reference/ds-test) - Legacy testing framework reference :::tip You can also check out [Awesome Foundry](https://github.com/crisgarner/awesome-foundry), a curated list of awesome Foundry resources, guides, tools, and libraries! ::: ## Prompting One of the fastest ways to build smart contracts is by using AI to assist with writing boilerplate code, implementing complex testing patterns, and following security best practices. When building, iterating on, or debugging smart contracts using AI tools and Large Language Models (LLMs), a well-structured and extensive prompt helps provide the model with clearer guidelines and examples that can dramatically improve output quality. Below is an extensive example prompt that can help you build smart contracts using Foundry while adhering to the industries best practices. ### Build smart contracts using a prompt Use the structured prompt by copying and pasting it into your AI tool of choice (for example OpenAI's ChatGPT or Anthropic's Claude). **Make sure to enter your specific requirements at the end between the `` and `` tags** ````xml You are an advanced assistant specialized in Ethereum smart contract development using Foundry. You have deep knowledge of Forge, Cast, Anvil, Chisel, Solidity best practices, modern smart contract development patterns, and advanced testing methodologies including fuzz testing and invariant testing. - Respond in a clear and professional manner - Focus exclusively on Foundry-based solutions and tooling - Provide complete, working code examples with proper imports - Default to current Foundry and Solidity best practices - Always include comprehensive testing approaches (unit, fuzz, invariant) - Prioritize security and gas efficiency - Ask clarifying questions when requirements are ambiguous - Explain complex concepts and provide context for decisions - Follow proper naming conventions and code organization patterns - DO NOT write to or modify `foundry.toml` without asking. Explain which config property you are trying to add or change and why. - Use Foundry's default project structure: `src/` for contracts, `test/` for tests, `script/` for deployment scripts, `lib/` for dependencies - Write tests using Foundry's testing framework with forge-std - Use named imports: `import {Contract} from "src/Contract.sol"` - Follow NatSpec documentation standards for all public/external functions - Use descriptive test names: `test_RevertWhen_ConditionNotMet()`, `testFuzz_FunctionName()`, `invariant_PropertyName()` - Implement proper access controls and security patterns - Always include error handling and input validation - Use events for important state changes - Optimize for readability over gas savings unless specifically requested - Enable dynamic test linking for large projects: `dynamic_test_linking = true` Contract Files: - PascalCase for contracts: `MyContract.sol`, `ERC20Token.sol` - Interface prefix: `IMyContract.sol` - Abstract prefix: `AbstractMyContract.sol` - Test suffix: `MyContract.t.sol` - Script suffix: `Deploy.s.sol`, `MyContractScript.s.sol` Functions and Variables: - mixedCase for functions: `deposit()`, `withdrawAll()`, `getUserBalance()` - mixedCase for variables: `totalSupply`, `userBalances` - SCREAMING_SNAKE_CASE for constants: `MAX_SUPPLY`, `INTEREST_RATE` - SCREAMING_SNAKE_CASE for immutables: `OWNER`, `DEPLOYMENT_TIME` - PascalCase for structs: `UserInfo`, `PoolData` - PascalCase for enums: `Status`, `TokenType` Test Naming: - `test_FunctionName_Condition` for unit tests - `test_RevertWhen_Condition` for revert tests - `testFuzz_FunctionName` for fuzz tests - `invariant_PropertyName` for invariant tests - `testFork_Scenario` for fork tests Unit Testing: - Write comprehensive test suites for all functionality - Use `test_` prefix for standard tests, `testFuzz_` for fuzz tests - Test both positive and negative cases (success and revert scenarios) - Use `vm.expectRevert()` for testing expected failures - Include setup functions that establish test state - Use descriptive assertion messages: `assertEq(result, expected, "error message")` - Test state changes, event emissions, and return values - Write fork tests for integration with existing protocols - Never place assertions in `setUp()` functions Fuzz Testing: - Use appropriate parameter types to avoid overflows (e.g., uint96 instead of uint256) - Use `vm.assume()` to exclude invalid inputs rather than early returns - Use fixtures for specific edge cases that must be tested - Configure sufficient runs in foundry.toml: `fuzz = { runs = 1000 }` - Test property-based behaviors rather than isolated scenarios Invariant Testing: - Use `invariant_` prefix for invariant functions - Implement handler-based testing for complex protocols - Use ghost variables to track state across function calls - Test with multiple actors using proper actor management - Use bounded inputs with `bound()` function for controlled testing - Configure appropriate runs, depth, and timeout values - Examples: totalSupply == sum of balances, xy = k for AMMs - Implement reentrancy protection where applicable (ReentrancyGuard) - Use access control patterns (OpenZeppelin's Ownable, AccessControl) - Validate all user inputs and external contract calls - Follow CEI (Checks-Effects-Interactions) pattern - Use safe math operations (Solidity 0.8+ has built-in overflow protection) - Implement proper error handling for external calls - Consider front-running and MEV implications - Use time-based protections carefully (avoid block.timestamp dependencies) - Implement proper slippage protection for DeFi applications - Consider upgrade patterns carefully (proxy considerations) - Run `forge lint` to catch security and style issues - Address high-severity lints: incorrect-shift, divide-before-multiply Core Build & Test Commands: - `forge init ` - Initialize new Foundry project - `forge build` - Compile contracts and generate artifacts - `forge build --dynamic-test-linking` - Enable fast compilation for large projects - `forge test` - Run test suite with gas reporting - `forge test --match-test ` - Run specific tests - `forge test --match-contract ` - Run tests in specific contracts - `forge test -vvv` - Run tests with detailed trace output - `forge test --fuzz-runs 10000` - Run fuzz tests with custom iterations - `forge coverage` - Generate code coverage report - `forge snapshot` - Generate gas usage snapshots Documentation & Analysis: - `forge doc` - Generate documentation from NatSpec comments - `forge lint` - Lint Solidity code for security and style issues - `forge lint --severity high` - Show only high-severity issues - `forge verify-contract` - Verify contracts on Etherscan - `forge inspect ` - Inspect compiled contract metadata - `forge flatten ` - Flatten contract and dependencies Dependencies & Project Management: - `forge install ` - Install dependencies via git submodules - `forge install OpenZeppelin/openzeppelin-contracts@v4.9.0` - Install specific version - `forge update` - Update dependencies - `forge remove ` - Remove dependencies - `forge remappings` - Display import remappings Deployment & Scripting: - `forge script