# 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).

#### 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

**Step 2)** Intercept: Overriding `CALL`s and `CREATE`s (see [limitations](limitations/overview) for more details)

**Step 3)**: Assimilate: One-shot zkEVM execution (see [Standard Library](forge-zksync-std) for more details)

#### 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

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.

#### 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 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