This repository provides utilities to help you use the Foundry smart contract development toolchain to develop, test, and deploy projects on the Kadena Chainweb EVM blockchain. These utilities extend the Foundry toolchain to support the Kadena multi-chain network, where transactions can be processed by multiple, parallel, EVM-compatible chains.
For information about installing the Foundry smart contract development toolchain and getting started with Solidity smart contract development, see the Foundry documentation.
Verify that your development environment meets the following basic requirements:
- You have the Foundry toolchain—with the
forge,cast,anvil, andchiselbinaries—installed locally or running in a Docker container. - You have Git installed for managing your project.
If you have the Foundry toolchain installed, you can clone the foundry-chainweb repository, navigate to the /examples/Counter folder, and begin experimenting with building, testing, and running scripts without first creating and configuring your own project.
To explore the sample Counter project:
-
Clone the
foundry-chainwebrepository:https://github.com/kadena-io/foundry-chainweb.git && cd foundry-chainweb
-
Change to the
examples/Counterdirectory:cd examples/Counter -
Build the sample
Counterproject:forge build
You should see that the sample project compiles successfully:
[⠊] Compiling... [⠒] Compiling 1 files with Solc 0.8.30 [⠑] Solc 0.8.30 finished in 568.10ms Compiler run successful!
-
Test the sample
Counterproject:forge testYou should see that the tests for the sample project are successful:
[⠊] Compiling... No files changed, compilation skipped Ran 2 tests for test/Counter.t.sol:CounterTest [PASS] test_IncrementMultiChain() (gas: 433714) [PASS] test_SetNumberMultiChain() (gas: 430552) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 1.72s (4.07ms CPU time)
-
Review the sample project configuration files and contracts for an overview of changes before configuring your own projects:
- foundry-chainweb/examples/Counter/chainweb.config.json
- foundry-chainweb//examples/Counter/foundry.toml
- foundry-chainweb/examples/Counter/script/Counter.s.sol
- foundry-chainweb/examples/Counter/script/CounterCreate2.s.sol
- foundry-chainweb/examples/Counter/src/Counter.sol
- foundry-chainweb/examples/Counter/test/Counter.t.sol
To configure your development environment to use Foundry with Chainweb EVM:
-
Create a new project directory with template files by running a command similar to the following:
forge init myKadenaCounter && cd myKadenaCounter
If you already have a Foundry project, navigate to the root directory for that project.
-
Install the
foundry-chainwebpackage by running the following command:forge install kadena-io/foundry-chainweb
-
Create a
remappings.txtfile in the root of your project. -
Add the following to the
remappings.txtfile to override the default dependency mapping:kadena-io/foundry-chainweb/=lib/foundry-chainweb/src/ -
Open the
foundry.tomlconfiguration file for your project and enable the Foreign Function Interface (FFI) support by setting theffioption totruein your profile.For example:
[profile.default] ffi = true
The FFI feature allows Solidity tests and scripts to execute external commands or interact with the file system.
-
Enable
readaccess to thechainweb.config.jsonfile by adding thefs_permissionsoption to thefoundry.tomlfile:fs_permissions = [{ access = "read", path = "./chainweb.config.json" }]
By default, projects have no file system permissions, and can't read or write to any file system files or directories. The
fs_permissionsoption enables you to configure specific file system permissions for specific files or directories. In this case, you're only allowing the project to read thechainweb.config.jsonfile.
You can use the chainweb.config.json file to configure different Chainweb EVM network settings to deploy and run contracts on multiple Chainweb EVM chains.
For example, you can modify the configuration file to define the number of EVM-compatible chains and the chain identifiers to use for different target networks, including anvil, a local Chainweb EVM node, the Chainweb EVM testnet, and Kadena mainnet.
The following sample chainweb.config.json file configures two deployment targets, the anvil target and the testnet target, with five EVM-compatible chains but different Ethereum base chain identifiers:
{
"anvil": {
"numberOfChains": 5,
"chainwebChainIdOffset": 20,
"chainIdOffset": 62600
},
"testnet": {
"numberOfChains": 5,
"chainwebChainIdOffset": 20,
"chainIdOffset": 5920,
"externalHostUrl": "https://evm-testnet.chainweb.com/chainweb/0.0/evm-testnet"
}
}You can set the following configuration parameters in the chainweb.config.json file:
| Parameter | Type | Description |
|---|---|---|
numberOfChains |
number |
Number of chains to initialize and fork. |
chainwebChainIdOffset |
number |
Index to start mapping Chainweb EVM chain identifiers to prevent collisions between Chainweb Pact and EVM chains. |
chainIdOffset |
number |
Base Ethereum chain identifier to use as the starting point for Chainweb EVM chain identifiers. For example, if you set the base chainIdOffset to 1000, numberOfChains to three, and the chainwebChainIdOffset index to zero, the resulting chain identifiers would be 1000, 1001, and 1002. |
externalHostUrl |
string |
Base URL for external Chainweb network access. |
You should note that the chainid (blockchain network Id) used in Chainweb EVM tests and scripts for Foundry projects is computed using the chainIdOffset defined in the project chainweb.config.json file as a base and incremented for each chain where a test or script runs.
The Chainweb chain Id (for instance 20, 21, 22, etc) is computed using the chainwebChainIdOffset defined in the project chainweb.config.json file as a base and incremented for each chain where a test or script runs.
Important: The chainweb.config.json configuration file is only used when running scripts with ChainwebScript.
When running tests with ChainwebTest, the configuration parameters are passed directly to the constructor and the configuration file is ignored.
To add custom setup logic to tests or scripts, you should use the optional userSetUp method instead of the default Foundry setUp method.
For example:
contract CounterTest is ChainwebTest(2, 0) {
function userSetUp() public override {
// Custom setup can be done here if needed
console.log("Setting up your test here");
}
}To write tests that run on Chainweb EVM and support the multi-chain network, you should extend the ChainwebTest contract that's defined in the lib/foundry-chainweb/src/Chainweb.sol file instead of using the default Foundry Test base contract.
The ChainwebTest code extends the Foundry Test contract by adding a chainweb property that provides two additional methods—the getChainIds and switchChain methods—to support the multi-chain network.
chainweb.getChainIdsreturns a list of available Chainweb chain identifiers.chainweb.switchChainswitches from the current active chain to a specified chain identifier in the Chainweb network.
The ChainwebTest constructor takes the following parameters to set up multi-chain testing:
| Parameter | Type | Description |
|---|---|---|
chainNumbers |
uint24 |
Number of chains to initialize and fork. |
chainwebChainIdOffset |
uint24 |
Index to start mapping Chainweb EVM chain identifiers to prevent collisions between Chainweb Pact and EVM chains. |
The following example demonstrates using ChainwebTest with the switchChain method to test the setNumber function from the Counter contract on four chains:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import {console} from "forge-std/Test.sol";
import {Counter} from "../src/Counter.sol";
import {ChainwebTest} from "kadena-io/foundry-chainweb/Chainweb.sol";
contract CounterTest is ChainwebTest(4, 0) {
function test_SetNumberMultiChain() public {
uint256[] memory chainIds = chainweb.getChainIds();
console.log("Chains in the chainweb:", chainIds.length);
for (uint256 i = 0; i < chainIds.length; i++) {
console.log("i:", i);
chainweb.switchChain(chainIds[i]);
Counter counter = new Counter();
console.log("Running script on chain:", block.chainid);
console.log("Counter deployed at:", address(counter));
counter.setNumber(1);
assertEq(counter.number(), 1);
}
}
function test_IncrementMultiChain() public {
uint256[] memory chainIds = chainweb.getChainIds();
console.log("Chains in the chainweb:", chainIds.length);
for (uint256 i = 0; i < chainIds.length; i++) {
console.log("i:", i);
chainweb.switchChain(chainIds[i]);
Counter counter = new Counter();
console.log("Running script on chain:", block.chainid);
console.log("Counter deployed at:", address(counter));
counter.setNumber(1);
counter.increment();
assertEq(counter.number(), 2);
}
}
}
You can execute the tests defined for your contract by running the forge test command.
For example, you can run the following command to display test results with additional details about test execution:
forge test -vvThis command displays information about the tests executed similar to the following:
Ran 2 tests for test/Counter.t.sol:CounterTest
[PASS] test_IncrementMultiChain() (gas: 433714)
Logs:
Setting up Chainweb for tests...
TEST: Setting main RPC node for 4 chains
Deploying chainId precompile for chainId: 0 at address: 0x9b02c3e2dF42533e0FD166798B5A616f59DBd2cc
Switching to chain: 0
Switched to chain: 0
Actually stored: 0
Deploying chainId precompile for chainId: 1 at address: 0x9b02c3e2dF42533e0FD166798B5A616f59DBd2cc
Switching to chain: 1
Switched to chain: 0
Actually stored: 1
Deploying chainId precompile for chainId: 2 at address: 0x9b02c3e2dF42533e0FD166798B5A616f59DBd2cc
Switching to chain: 2
Switched to chain: 0
Actually stored: 2
Deploying chainId precompile for chainId: 3 at address: 0x9b02c3e2dF42533e0FD166798B5A616f59DBd2cc
Switching to chain: 3
Switched to chain: 0
Actually stored: 3
Switching to chain: 0
Switched to chain: 0
Chains in the chainweb: 4
i: 0
Switching to chain: 0
Switched to chain: 0
Running script on chain: 31337
Counter deployed at: 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f
i: 1
Switching to chain: 1
Switched to chain: 1
Running script on chain: 31338
Counter deployed at: 0x2e234DAe75C793f67A35089C9d99245E1C58470b
i: 2
Switching to chain: 2
Switched to chain: 2
Running script on chain: 31339
Counter deployed at: 0xF62849F9A0B5Bf2913b396098F7c7019b51A820a
i: 3
Switching to chain: 3
Switched to chain: 3
Running script on chain: 31340
Counter deployed at: 0x5991A2dF15A8F6A256D3Ec51E99254Cd3fb576A9
[PASS] test_SetNumberMultiChain() (gas: 430552)
Logs:
Setting up Chainweb for tests...
TEST: Setting main RPC node for 4 chains
Deploying chainId precompile for chainId: 0 at address: 0x9b02c3e2dF42533e0FD166798B5A616f59DBd2cc
Switching to chain: 0
Switched to chain: 0
Actually stored: 0
Deploying chainId precompile for chainId: 1 at address: 0x9b02c3e2dF42533e0FD166798B5A616f59DBd2cc
Switching to chain: 1
Switched to chain: 0
Actually stored: 1
Deploying chainId precompile for chainId: 2 at address: 0x9b02c3e2dF42533e0FD166798B5A616f59DBd2cc
Switching to chain: 2
Switched to chain: 0
Actually stored: 2
Deploying chainId precompile for chainId: 3 at address: 0x9b02c3e2dF42533e0FD166798B5A616f59DBd2cc
Switching to chain: 3
Switched to chain: 0
Actually stored: 3
Switching to chain: 0
Switched to chain: 0
Chains in the chainweb: 4
i: 0
Switching to chain: 0
Switched to chain: 0
Running script on chain: 31337
Counter deployed at: 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f
i: 1
Switching to chain: 1
Switched to chain: 1
Running script on chain: 31338
Counter deployed at: 0x2e234DAe75C793f67A35089C9d99245E1C58470b
i: 2
Switching to chain: 2
Switched to chain: 2
Running script on chain: 31339
Counter deployed at: 0xF62849F9A0B5Bf2913b396098F7c7019b51A820a
i: 3
Switching to chain: 3
Switched to chain: 3
Running script on chain: 31340
Counter deployed at: 0x5991A2dF15A8F6A256D3Ec51E99254Cd3fb576A9
Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 686.70ms (4.42ms CPU time)
Ran 1 test suite in 688.38ms (686.70ms CPU time): 2 tests passed, 0 failed, 0 skipped (2 total tests)To write scripts that run on Chainweb EVM and support the multi-chain network, you should import the ChainwebScript contract that's defined in the lib/foundry-chainweb/src/Chainweb.sol file instead of using the default Foundry Script contract.
Like ChainwebTest, the ChainwebScript code extends the default Foundry Script contract by adding a chainweb property that provides two additional methods—the getChainIds and switchChain methods—to support the multi-chain network.
chainweb.getChainIdsreturns a list of available Chainweb chain identifiers.chainweb.switchChainswitches from the current active chain to a specified chain identifier in the Chainweb network.
The following example demonstrates using ChainwebScript with the getChainIds and switchChain methods to deploy the Counter contract on multiple chains:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import {Script, console} from "forge-std/Script.sol";
import {Counter} from "../src/Counter.sol";
import {ChainwebScript} from "kadena-io/foundry-chainweb/Chainweb.sol";
contract CounterScript is ChainwebScript {
function run() public {
uint256[] memory chainIds = chainweb.getChainIds();
for (uint256 i = 0; i < chainIds.length; i++) {
chainweb.switchChain(chainIds[i]);
uint256 activeChainId = chainweb.getActiveChainId();
console.log("Active Chainweb chain ID:", activeChainId);
require(activeChainId == chainIds[i], "Active Chainweb chain ID does not match expected chain ID");
vm.startBroadcast();
Counter counter1 = new Counter();
counter1.setNumber(1);
vm.stopBroadcast();
}
}
}The script automatically reads configuration settings from the chainweb.config.json file and uses the CHAINWEB environment variable, if specified, to determine which configuration settings to apply.
If you don't specify the CHAINWEB environment variable, the script uses the anvil environment by default.
For example, you can deploy the Counter contract with the default configuration that uses the anvil node environment and the private key for a test account by running the Counter.s.sol script like this:
forge script --multi script/Counter.s.sol:CounterScript \
--private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \
--broadcastYou can run the deployment script using the testnet configuration from the chainweb.config.json file as the target environment by running a command similar to the following:
CHAINWEB=testnet forge script --multi script/Counter.s.sol:CounterScript \
--private-key <your_private_key> \
--broadcast --legacyThe following sample chainweb.config.json file demonstrates the configuration settings for the default anvil environment and the Chainweb EVM testnet network.
{
"anvil": {
"numberOfChains": 5,
"chainwebChainIdOffset": 20,
"chainIdOffset": 62600
},
"testnet": {
"numberOfChains": 5,
"chainwebChainIdOffset": 20,
"chainIdOffset": 5920,
"externalHostUrl": "https://evm-testnet.chainweb.com/chainweb/0.0/evm-testnet"
}
}This deployment script deploys the contract on four Chainweb EVM chains forked from anvil with output similar to the following:
[⠊] Compiling...
No files changed, compilation skipped
Warning: Multi chain deployment is still under development. Use with caution.
Script ran successfully.
Gas used: 580269
== Logs ==
Setting up Chainweb for scripts...
SCRIPT: Setting main RPC node for 5 chains
Forking http://127.0.0.1:16311
Forking http://127.0.0.1:27295
Forking http://127.0.0.1:19075
Forking http://127.0.0.1:30736
Forking http://127.0.0.1:16409
Switching to chain: 20
Switched to chain: 20
Switching to chain: 20
Switched to chain: 20
Active Chainweb chain ID: 20
Switching to chain: 21
Switched to chain: 21
Active Chainweb chain ID: 21
Switching to chain: 22
Switched to chain: 22
Active Chainweb chain ID: 22
Switching to chain: 23
Switched to chain: 23
Active Chainweb chain ID: 23
Switching to chain: 24
Switched to chain: 24
Active Chainweb chain ID: 24
## Setting up 5 EVMs.
==========================
Chain 62601
Estimated gas price: 2.000000001 gwei
Estimated total gas used for script: 184264
Estimated amount required: 0.000368528000184264 ETH
==========================
==========================
Chain 62602
Estimated gas price: 2.000000001 gwei
Estimated total gas used for script: 184264
Estimated amount required: 0.000368528000184264 ETH
==========================
==========================
Chain 62603
Estimated gas price: 2.000000001 gwei
Estimated total gas used for script: 184264
Estimated amount required: 0.000368528000184264 ETH
==========================
==========================
Chain 62604
Estimated gas price: 2.000000001 gwei
Estimated total gas used for script: 184264
Estimated amount required: 0.000368528000184264 ETH
==========================
==========================
Chain 62600
Estimated gas price: 2.000000001 gwei
Estimated total gas used for script: 184264
Estimated amount required: 0.000368528000184264 ETH
==========================
##### 62600
✅ [Success] Hash: 0xfd01ec6cd7c564a0faea559e87b04f7417f51de7f0ad4c43ffcfcac45908f3ab
Contract Address: 0x5FbDB2315678afecb367f032d93F642f64180aa3
Block: 1
Paid: 0.000092815000092815 ETH (92815 gas * 1.000000001 gwei)
##### 62600
✅ [Success] Hash: 0x8064737f3b1abf99c290f4cf76b7cc4f40a15f5d5a2a97b0e87c65dd9c544097
Block: 2
Paid: 0.00003808826354886 ETH (43491 gas * 0.87577346 gwei)
##### 62601
✅ [Success] Hash: 0x9ce4f1c32c146f725dbc95300b7706144de14227ed6a829ab6e0024177cb688f
Contract Address: 0x5FbDB2315678afecb367f032d93F642f64180aa3
Block: 1
Paid: 0.000092815000092815 ETH (92815 gas * 1.000000001 gwei)
##### 62601
✅ [Success] Hash: 0x1bbca320d6150b34bcf148047b66283df6faf61ecdf4a6ad4e91f7fb0e32a208
Block: 2
Paid: 0.00003808826354886 ETH (43491 gas * 0.87577346 gwei)
##### 62602
✅ [Success] Hash: 0xe899c82436b07f6f85e4bd621e5789c938b8cb2f962e6ac44ce02aae8fb8f788
Contract Address: 0x5FbDB2315678afecb367f032d93F642f64180aa3
Block: 1
Paid: 0.000092815000092815 ETH (92815 gas * 1.000000001 gwei)
##### 62602
✅ [Success] Hash: 0xc5b082a85d262f97fd04108da1f352a04a1af85d420f404172b98151ebb0028a
Block: 2
Paid: 0.00003808826354886 ETH (43491 gas * 0.87577346 gwei)
##### 62603
✅ [Success] Hash: 0x454015bb53b4e6e671a926ee03744ab727184ef504b1b65ee27f99d0b3e5e69f
Contract Address: 0x5FbDB2315678afecb367f032d93F642f64180aa3
Block: 1
Paid: 0.000092815000092815 ETH (92815 gas * 1.000000001 gwei)
##### 62603
✅ [Success] Hash: 0x176e6b2e94a79c55e842a70aeb6bc77a815aa7a24f4f9fb47deeeec2bfe44bf8
Block: 2
Paid: 0.00003808826354886 ETH (43491 gas * 0.87577346 gwei)
##### 62604
✅ [Success] Hash: 0xa57699c939ae80ff9550b6134bf27e1bb05742c535a4cbf88b693a05233fe3bf
Block: 2
Paid: 0.00003808826354886 ETH (43491 gas * 0.87577346 gwei)
##### 62604
✅ [Success] Hash: 0x791a3e86ba5363e1b79ed84bfe360014144e3e5755765ddfca8c46e2cd6aa932
Contract Address: 0x5FbDB2315678afecb367f032d93F642f64180aa3
Block: 1
Paid: 0.000092815000092815 ETH (92815 gas * 1.000000001 gwei)
✅ Sequence #1 on 62600 | Total Paid: 0.000130903263641675 ETH (136306 gas * avg 0.93788673 gwei)
✅ Sequence #2 on 62601 | Total Paid: 0.000130903263641675 ETH (136306 gas * avg 0.93788673 gwei)
✅ Sequence #3 on 62602 | Total Paid: 0.000130903263641675 ETH (136306 gas * avg 0.93788673 gwei)
✅ Sequence #4 on 62603 | Total Paid: 0.000130903263641675 ETH (136306 gas * avg 0.93788673 gwei)
✅ Sequence #5 on 62604 | Total Paid: 0.000130903263641675 ETH (136306 gas * avg 0.93788673 gwei)
==========================
ONCHAIN EXECUTION COMPLETE & SUCCESSFUL.
Transactions saved to: /Users/hswope/git-repos/kadena/foundry-chainweb/examples/Counter/broadcast/multi/Counter.s.sol-latest/run.json
Sensitive details saved to: /Users/hswope/git-repos/kadena/foundry-chainweb/examples/Counter/cache/multi/Counter.s.sol-latest/run.jsonIn the sample output for deploying the contract on multiple chains, you'll notice that the Foundry chain identifiers are computed using the default value from the chainweb.config.json chainIdOffset as a base value and incremented for each Chainweb EVM chain.
As a result of the computation, Chainweb EVM chain 20 (index 0) maps to the Foundry chain identifier 62600, Chainweb EVM chain 21 (index 1) maps to the Foundry chain identifier 62601, and so on.
You can verify contracts that run on Chainweb EVM by running the standard forge verify-contract command.
The following example demonstrates verifying a contract like Counter.sol that has no constructor arguments on Chainweb EVM testnet chain 20 (network chain Id 5920):
forge verify-contract \
--chain 5920 \
--num-of-optimizations 200 \
--watch \
--verifier blockscout \
--verifier-url https://chain-20.evm-testnet-blockscout.chainweb.com/api/ \
--verifier-api-version v2 \
--compiler-version v0.8.28 \
0x3ee2edc5b2967093bf9b3058cf9803bee7595baf \
src/Counter.sol:CounterIf your contract has constructor arguments, you can pass them using the --constructor-args option.
For example:
--constructor-args $(cast abi-encode "constructor(string,string,uint256,uint256)" "ForgeUSD" "FUSD" 18 1000000000000000000000)You can run the following command to see all the options:
forge verify-contract --helpFor contracts that run on multiple Chainweb EVM chains, you must run the verify-contract command on each chain.
However, the verify-contract command doesn't support verification if a contract with the same bytecode has been previously verified.
If you attempt to verify a contract with the same bytecode as a contract that has been previously verified, verification will fail with a message similar to the following:
[address] is already verified. Skipping verification.To test contract verification on multiple chains, you must change the bytecode, redeploy, then run the verify-contract command on the redeployed contract.
You can change the bytecode by adding a constant at the top of the contract like this:
uint256 public constant DUMMY = 1;Increase the value used for the constant in each contract you deploy to verify contracts that are deployed on multiple chains.
Contract verification is not possible against anvil, as there is no block explorer for anvil.
The /examples/Counter folder in the foundry-chainweb repository includes a sample script that demonstrates how to deploy a contract that has the same address on every chain.
The CounterCreate2.s.sol script demonstrates deterministic deployment across all Chainweb EVM chains using the CREATE2 opcode.
The CREATE2 opcode ensures that the same contract is deployed using a predetermined address that is exactly the same on every chain, making cross-chain interactions predictable and easier to manage.
To simulate the deployment using the sample script:
-
Open a terminal shell on your computer.
-
Change to the
examples/Counterdirectory:cd examples/Counter -
Run a command similar to the following:
forge script script/CounterCreate2.s.sol:CounterCreate2Script \ --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
To deploy the Counter contract using the sample deployment script, add the --broadcast command-line option.
For example:
forge script script/CounterCreate2.s.sol:CounterCreate2Script \
--private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \
--broadcastThe sample script demonstrates the following deployment steps:
- Predicts the deployment address using the account salt and contract bytecode.
- Deploys the
Countercontract to all configured Chainweb EVM chains. - Verifies that each deployment matches the predicted address.
- Displays a summary that shows all contracts were deployed to the same address.
Switch to a different chain in the Chainweb network.
| Parameter | Type | Description |
|---|---|---|
chainId |
uint256 |
The target Chainweb chain ID to switch to |
Retrieve a list of available Chainweb chain IDs.
| Output | Type | Description |
|---|---|---|
chainIds |
uint256[] |
An array containing valid Chainweb chain IDs |
It's possible to condifigure chainId in founder.toml. However, Chainweb EVM has a different concept of chainId. The Chainweb Chain Id of a Chainweb EVM chain is a number like 20, 21, 22, 23, 24 for our testnet. This Chainweb Chain Id should be configured in your project's chainweb.config.json as chainwebChainIdOffset. The value of chainwebChainIdOffset would be 20 for Chainweb EVM testnet. In the example chainweb.config.json , 20 is also configured as the chainwebChainIdOffset for anvil. For internal anvil instances that get spun up for tests, you will see Chainweb chain Ids staring with 0.
The chainIdOffset is the starting point for the network chain Id (akin to 1 for Ethereum mainnet). For Chainweb EVM testnet, the starting value is 5920, as this is the network chain Id of Chainweb EVM testnet chain 20. Chainweb EVM testnet chain 21 has network chain Id 5921, and so on.
The account you use when you execute tests with forge test or run scripts with forge script commands affects how the transaction nonce is maintained in different scenarios.
Because the default msg.sender account is a persisted account that's available across all forks, you can run forge test using the default msg.sender account to ensure transactions have a nonce that's maintained globally across all forks.
If you want to test a contract using isolated nonces that are maintained separately for each fork, you can use vm.startPrank or vm.startBroadcast function to set a specific account instead of using the default msg.sender account.
The nonce for that account is then maintained per fork.
Because the default msg.sender account is a persisted account that's available across all forks, you can run forge script using the default msg.sender account to ensure transactions have a nonce that's maintained globally across all forks.
If you specify the msg.sender account by setting the --sender or --private-key command-line option, the nonce is maintained per fork.
You can also use the vm.startBroadcast function to explicitly set the sender for the next call. The nonce for that sender is maintained per fork.
When running the forge test --gas-report command in the root directory of the repo, the test_Nonce test case will fail. This is because when the --gas-report option is used, Foundry does not properly isolate the forks. The nonces are global instead of per fork.
When running the forge test --gas-report command, the test_setupChainsForScript may not work. This is because when the --gas-report option is used, contract state on forks is not isolated. The chainId precompile is deployed and it's state variable is set to, for instance 0, for the first chain. It is then set to 1 for the second chain. However, the state in the contract on the first chain fork is overwritten to 1 instead of writing that state to a separate fork. It's not clear why this happens, but logging has shown that it happens. Code changes that seeminlgy have nothing to do with this issue can make the test case work or not work in a seemingly random pattern. The cheatcode vm.revokePersistent is used in the deployChainWebChainIdContract function to ensure that the contract addresses are isolated when using --gas-report. However, it can't be guaranteed that this will always work.