Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: document forking related cheatcodes #442

Merged
merged 2 commits into from
Jul 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions src/cheatcodes/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ Below are some subsections for the different Forge cheatcodes.
- [Fuzzer](./fuzzer.md): Cheatcodes that configure the fuzzer
- [External](./external.md): Cheatcodes that interact with external state (files, commands, ...)
- [Utilities](./utilities.md): Smaller utility cheatcodes
- [Forking](./forking.md): Forking mode cheatcodes
- [Snapshots](./snapshots.md): Snapshot cheatcodes

### Cheatcodes Interface

Expand Down Expand Up @@ -162,5 +164,42 @@ interface CheatCodes {

// Stops collecting onchain transactions
function stopBroadcast() external;

// Snapshot the current state of the evm.
// Returns the id of the snapshot that was created.
// To revert a snapshot use `revertTo`
function snapshot() external returns(uint256);
// Revert the state of the evm to a previous snapshot
// Takes the snapshot id to revert to.
// This deletes the snapshot and all snapshots taken after the given snapshot id.
function revertTo(uint256) external returns(bool);

// Creates a new fork with the given endpoint and block and returns the identifier of the fork
function createFork(string calldata,uint256) external returns(uint256);
// Creates a new fork with the given endpoint and the _latest_ block and returns the identifier of the fork
function createFork(string calldata) external returns(uint256);

// Creates _and_ also selects a new fork with the given endpoint and block and returns the identifier of the fork
function createSelectFork(string calldata,uint256) external returns(uint256);
// Creates _and_ also selects a new fork with the given endpoint and the latest block and returns the identifier of the fork
function createSelectFork(string calldata) external returns(uint256);

// Takes a fork identifier created by `createFork` and sets the corresponding forked state as active.
function selectFork(uint256) external;

/// Returns the currently active fork
/// Reverts if no fork is currently active
function activeFork() external returns(uint256);

// Updates the currently active fork to given block number
// This is similar to `roll` but for the currently active fork
function rollFork(uint256) external;
// Updates the given fork to given block number
function rollFork(uint256 forkId, uint256 blockNumber) external;

/// Returns the RPC url for the given alias
function rpcUrl(string calldata) external returns(string memory);
/// Returns all rpc urls and their aliases `[alias, url][]`
function rpcUrls() external returns(string[2][] memory);
}
```
103 changes: 103 additions & 0 deletions src/cheatcodes/forking.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
## forking cheatcodes

- [`RPC cheatcodes`](./rpc.md)

### Signature

```solidity
// Creates a new fork with the given endpoint and block and returns the identifier of the fork
function createFork(string calldata urlOrAlias,uint256 block) external returns(uint256);
// Creates a new fork with the given endpoint and the _latest_ block and returns the identifier of the fork
function createFork(string calldata urlOrAlias) external returns(uint256);
// Creates _and_ also selects a new fork with the given endpoint and block and returns the identifier of the fork
function createSelectFork(string calldata urlOrAlias,uint256 block) external returns(uint256);
// Creates _and_ also selects a new fork with the given endpoint and the latest block and returns the identifier of the fork
function createSelectFork(string calldata urlOrAlias) external returns(uint256);
// Takes a fork identifier created by `createFork` and sets the corresponding forked state as active.
function selectFork(uint256 forkId) external;
/// Returns the currently active fork
/// Reverts if no fork is currently active
function activeFork() external returns(uint256);
// Updates the currently active fork to given block number
// This is similar to `roll` but for the currently active fork
function rollFork(uint256) external;
// Updates the given fork to given block number
function rollFork(uint256 forkId, uint256 blockNumber) external;
```

### Description

Forking cheatcodes allow you to enter forking mode programatically via cheatcodes.
Instead of configuring forking mode via the CLI arguments of `forge` ([Forking Mode](./forge/forking-mode.md)), these cheatcodes allow you to use forking mode on a test-by-test basis and also allow you to use multiple forks during testing.

During testing forks are identified via `uint256` unique identifiers.

### Examples

Important to keep in mind that _all_ test functions are isolated, meaning each test function is executed with a _copy_ of the state after `setUp` and is executed in its own stand-alone evm.

Therefore forks created during `setUp` are available in tests.

The following creates two forks, but does _not_ select them yet.

Enabling a specifc fork is donw via `selectFork(uint256 forkId)`.

There can only be one fork active at a time, the currently active fork id can be retrieved via `activeFork()(uint256)`.

To understand what happens when a fork is selected, it is important to know how the forking mode works in general:

In fork mode, there are two memory sections in the EVM, a _local_ and a _remote_. The local one contains all modified storage slots, while the remote one contains storage slots fetched via rpc that are _not_ modified.
so when the evm does:

- _read_: return from local if present, otherwise fetch from remote
- _write_: always write into local

What `selectFork` does is to set the _remote_ section with the fork's data source, however the _local_ memory remains persistent across fork swaps. This also means the `selectFork` can be called at all times with any fork, to set the _remote_ data source. However, it is important that the above rules for `read/write` access always apply, meaning _writes_ are persistent across fork swaps.

`createSelectFork` is a onliner for `createFork()` + `selectFork()`


```solidity
contract ForkTest is Test {
Cheats constant cheats = Cheats(HEVM_ADDRESS);

// the identifiers of the forks
uint256 mainnetFork;
uint256 optimismFork;

// this will create two _different_ forks during setup
function setUp() public {
mainnetFork = cheats.createFork("mainnet");
optimismFork = cheats.createFork(
"https://opt-mainnet.g.alchemy.com/v2/<api-key>"
);
}

// Fork ids are unique
function testForkIdDiffer() public {
assert(mainnetFork != optimismFork);
}

// ensures forks use different ids
function testCanSelectFork() public {
// select the fork
cheats.selectFork(mainnetFork);
assertEq(mainnetFork, cheats.activeFork());
// from here on data is fetched from the `mainnetFork` if the EVM requests it
}

function testCanSwitchContracts() public {
cheats.selectFork(mainnetFork);
assertEq(mainnetFork, cheats.activeFork());

cheats.selectFork(optimismFork);
assertEq(optimismFork, cheats.activeFork());
}

// Forks can be created at all times
function testCanCreateAndSelectInOneStep() public {
// creates a new fork and also selects it
uint256 anotherFork = cheats.createSelectFork("mainnet");
}
}
```
59 changes: 59 additions & 0 deletions src/cheatcodes/rpc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
## RPC related cheatcodes

### Signature

```solidity
// Returns the URL for a configured alias
function rpcUrl(string calldata alias) external returns(string memory);
// Returns all configured (alias, URL) pairs
function rpcUrls() external returns(string[2][] memory);
```

### Description

Provides cheatcodes to access all RPC endpoints configured in the `rpc_endpoints` object of the `foundry.toml`

### Examples

The following `rpc_endpoints` in `foundry.toml` registers two RPC aliases:

- `optimism` references the URL directly
- `mainnet` references the `RPC_MAINNET` environment value that is expected to contain the actual URL

*Env variables need to be wrapped in `${}`*

```toml
# --snip--
rpc_endpoints = { optimism = "https://optimism.alchemyapi.io/v2/...", mainnet = "${RPC_MAINNET}" }
```

```solidity
string memory url = cheats.rpcUrl("optimism");
assertEq(url, ""https://optimism.alchemyapi.io/v2/...");
```

If a ENV var is missing, `rpcUrl()` will revert:

```solidity
cheats.expectRevert("Failed to resolve env var `RPC_MAINNET`: environment variable not found");
string memory url = cheats.rpcUrl("mainnet");
```

Retrieve all available alias -> URL pairs

```solidity
string[2][] memory allUrls = cheats.rpcUrls();
assertEq(allUrls.length, 2);

string[2] memory val = allUrls[0];
assertEq(val[0], "optimism");

string[2] memory env = allUrls[1];
assertEq(env[0], "mainnet");
```

### SEE ALSO

Forge Config

[Config Reference](./reference/config.md)
57 changes: 57 additions & 0 deletions src/cheatcodes/snapshots.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
## snapshot cheatcodes

### Signature

```solidity
// Snapshot the current state of the evm.
// Returns the id of the snapshot that was created.
// To revert a snapshot use `revertTo`
function snapshot() external returns(uint256);
// Revert the state of the evm to a previous snapshot
// Takes the snapshot id to revert to.
// This deletes the snapshot and all snapshots taken after the given snapshot id.
function revertTo(uint256) external returns(bool);
```

### Description

`snapshot` takes a snapshot of the state of the blockchain and returns the identifier of the created snapshot

`revertTo` reverts the state of the blockchain to the given snapshot. This deletes the given snapshot, as well as any snapshots taken after (e.g.: reverting to id 1 will delete snapshots with ids 2, 3, etc.)

### Examples


```solidity
struct Storage {
uint slot0;
uint slot1;
}

contract SnapshotTest is Test {
Cheats constant cheats = Cheats(HEVM_ADDRESS);

Storage store;

function setUp() public {
store.slot0 = 10;
store.slot1 = 20;
}

function testSnapshot() public {
uint256 snapshot = cheats.snapshot();
store.slot0 = 300;
store.slot1 = 400;

assertEq(store.slot0, 300);
assertEq(store.slot1, 400);

// after resetting to a snapshot all changes are discarded
cheats.revertTo(snapshot);
assertEq(store.slot0, 10, "snapshot revert for slot 0 unsuccessful");
assertEq(store.slot1, 20, "snapshot revert for slot 1 unsuccessful");
}

}

```
2 changes: 1 addition & 1 deletion src/forge/forking-mode.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Forge supports testing in a forked environment.

To run tests in a forked environment - such as a forked Ethereum mainnet - pass a RPC URL via the `--fork-url` flag:
To run all tests in a forked environment - such as a forked Ethereum mainnet - pass a RPC URL via the `--fork-url` flag:

```bash
forge test --fork-url <your_rpc_url>
Expand Down
18 changes: 18 additions & 0 deletions src/reference/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ memory_limit = 33554432
names = false
# Print the sizes of the compiled contracts
sizes = false
# Contains alias -> URL|Env pairs for RPC endpoints that can be accessed during testing
rpc_endpoints = { optimism = "https://optimism.alchemyapi.io/v2/...", mainnet = "${RPC_MAINNET}" }
```

### Configuration keys
Expand Down Expand Up @@ -699,3 +701,19 @@ Print compiled contract names.
- Environment: `FOUNDRY_SIZES` or `DAPP_SIZES`

Print compiled contract sizes.

##### `rpc_endpoints`

- Type: table or list of alias -> URL|Env pairs

Container type for RPC endpoints that can be accessed during testing via cheatcodes.

Environment variables need to be wrapped in `${}`

```toml
rpc_endpoints = { optimism = "https://optimism.alchemyapi.io/v2/...", mainnet = "${RPC_MAINNET}" }
```

See also

[`RPC`](./cheatcodes/rpc.md)