Skip to content
Open
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
3 changes: 2 additions & 1 deletion .config/forest.dic
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
294
295
ABI
Algorand/M
API's
API/SM
Expand Down
7 changes: 4 additions & 3 deletions .github/workflows/forest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -491,13 +491,14 @@ jobs:
run: |
chmod +x "$GITHUB_WORKSPACE"/forest*
echo "$GITHUB_WORKSPACE" >> "$GITHUB_PATH"
- name: Devnet wallet tests
- name: Devnet tests
run: |
set -euo pipefail
source ./scripts/devnet/wallet_harness.sh
devnet_wallet_env_init
source ./scripts/devnet/test_harness.sh
devnet_test_env_init
forest-dev tests mpool
forest-dev tests wallet
forest-dev devnet eth-gas
timeout-minutes: ${{ fromJSON(env.SCRIPT_TIMEOUT_MINUTES) }}
- name: Dump docker logs
if: always()
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@

### Fixed

- [#7473](https://github.com/ChainSafe/forest/pull/7473): Fixed `eth_estimateGas` under-estimating gas for nested contract calls (EIP-150's 63/64 rule), which could make transactions fail on chain with `SYS_OUT_OF_GAS`; the estimate is now raised until the message succeeds. Genuine reverts return an `execution reverted` error (JSON-RPC code `3`) with the decoded reason and data, matching Lotus.

- [#7412](https://github.com/ChainSafe/forest/issues/7412): Fixes quicknet "unchained" logic to fetch the `max_beacon_round` for all covered epochs

- [#7446](https://github.com/ChainSafe/forest/pull/7446): Fixed a panic condition on `ChainNotify` when a client closes a connection just after subscription.
Expand Down
9 changes: 5 additions & 4 deletions mise.toml
Original file line number Diff line number Diff line change
Expand Up @@ -225,19 +225,20 @@ forest-dev tests mpool
forest-dev tests wallet
'''

[tasks."test:wallet-devnet"]
description = "Run wallet integration tests against a local docker devnet."
[tasks."test:devnet"]
description = "Run the integration test suites against a local docker devnet."
shell = "bash -c"
run = '''
set -euo pipefail
pushd scripts/devnet
./setup.sh
./check.sh
popd
source ./scripts/devnet/wallet_harness.sh
devnet_wallet_env_init
source ./scripts/devnet/test_harness.sh
devnet_test_env_init
forest-dev tests mpool
forest-dev tests wallet
forest-dev devnet eth-gas
'''

[tasks."codecov:nextest"]
Expand Down
19 changes: 12 additions & 7 deletions scripts/devnet/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,20 +56,25 @@ export FULLNODE_API_INFO=$TOKEN:/dns/forest/tcp/3456/http
forest-cli net peers
```

## Running the wallet integration tests
## Running the integration tests

The same wallet/mpool integration suite that runs against calibnet can be run
against the local devnet. This brings up the devnet, waits for it to sync, wires
up the host environment, and runs the tests:
The wallet/mpool suites that run against calibnet, plus the `eth_estimateGas`
parity suite, can be run against the local devnet. This brings up the devnet,
waits for it to sync, wires up the host environment, and runs the tests:

```shell
mise run test:wallet-devnet
mise run test:devnet
```

Under the hood this sources `wallet_harness.sh`, which reads the admin token and
Under the hood this sources `test_harness.sh`, which reads the admin token and
the funded genesis key from the running `forest` container, exports
`FULLNODE_API_INFO` (Forest RPC on port 3456) and `FOREST_TEST_PRELOADED_ADDRESS`,
then runs `forest-dev tests mpool` and `forest-dev tests wallet`.
then runs `forest-dev tests mpool`, `forest-dev tests wallet` and
`forest-dev devnet eth-gas`.

The `devnet` suites (unlike `tests`) need both nodes reachable: `eth-gas`
compares Forest against Lotus, so it runs pre-flight checks and fails early if
the devnet or harness environment is missing.

## Local devnet development

Expand Down
2 changes: 2 additions & 0 deletions scripts/devnet/lotus.env
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ LOTUS_MINER_PATH=/lotus_data/lotus-miner-local-net
LOTUS_SKIP_GENESIS_CHECK=_yes_
LOTUS_API_LISTENADDRESS=/dns/lotus/tcp/1234/http
LOTUS_LIBP2P_LISTENADDRESSES=/ip4/0.0.0.0/tcp/1235
LOTUS_FEVM_ENABLEETHRPC=1
LOTUS_CHAININDEXER_ENABLEINDEXER=1
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
#!/bin/bash
# Sourced (not executed) helpers for the wallet/mpool suite on the docker
# devnet. Run after the devnet is up (`setup.sh`) and synced (`check.sh`).
# Sourced (not executed) helpers for the devnet test suites (wallet, mpool,
# eth-gas) on the docker devnet. Run after the devnet is up (`setup.sh`) and synced (`check.sh`).
#
# The genesis key is the Lotus miner's default wallet, so using it as the test
# sender causes nonce contention. We fund a dedicated wallet instead.

WALLET_HARNESS_PARENT_PATH=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P )
source "${WALLET_HARNESS_PARENT_PATH}/.env"
TEST_HARNESS_PARENT_PATH=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P )
# Export everything in `.env` so the test binaries inherit the devnet config (ports, data dirs)
# as environment variables; `source` on its own would only set shell variables.
set -a
source "${TEST_HARNESS_PARENT_PATH}/.env"
set +a

export FOREST_CLI_PATH="${FOREST_CLI_PATH:-forest-cli}"
export FOREST_WALLET_PATH="${FOREST_WALLET_PATH:-forest-wallet}"
export DEVNET_TEST_FUND_AMT="${DEVNET_TEST_FUND_AMT:-100 FIL}"

function devnet_wallet_env_init {
function devnet_test_env_init {
local token
token=$(docker exec forest cat "${FOREST_DATA_DIR}/token.jwt")
export FULLNODE_API_INFO="${token}:/ip4/127.0.0.1/tcp/${FOREST_RPC_PORT}/http"
Expand All @@ -32,7 +36,7 @@ function devnet_wallet_env_init {
${FOREST_WALLET_PATH} --remote-wallet import "${test_key_path}"
export FOREST_TEST_PRELOADED_ADDRESS="${test_addr}"

echo "Devnet wallet env initialised:"
echo "Devnet test env initialised:"
echo " FULLNODE_API_INFO=<token>:/ip4/127.0.0.1/tcp/${FOREST_RPC_PORT}/http"
echo " FOREST_TEST_PRELOADED_ADDRESS=${FOREST_TEST_PRELOADED_ADDRESS}"
echo " Funding ${test_addr} with ${DEVNET_TEST_FUND_AMT} from ${genesis_addr}..."
Expand Down
61 changes: 61 additions & 0 deletions src/dev/subcommands/devnet_cmd.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright 2019-2026 ChainSafe Systems
// SPDX-License-Identifier: Apache-2.0, MIT

//! Integration suites that run against the local docker devnet. Unlike the unit
//! suite, these need a running devnet with both a Forest and a Lotus node reachable and the
//! test harness environment wired up. [`preflight`] fails early with actionable errors when
//! that environment is missing, rather than letting a suite surface it as an opaque mid-run error.

mod eth_gas;

use crate::dev::subcommands::tests_cmd::helpers::{docker, forest_client, lotus_client};
use crate::rpc::prelude::*;
use anyhow::{Context as _, ensure};

/// Integration tests that require the docker devnet
#[derive(Debug, clap::Subcommand)]
pub enum DevnetCommand {
EthGas(eth_gas::EthGasTestCommand),
}

impl DevnetCommand {
pub async fn run(self) -> anyhow::Result<()> {
preflight().await.context("devnet pre-flight failed")?;
match self {
Self::EthGas(cmd) => cmd.run().await,
}
}
}

async fn preflight() -> anyhow::Result<()> {
for container in ["forest", "lotus"] {
let running = docker(&["inspect", "-f", "{{.State.Running}}", container]).with_context(
|| format!("could not query container `{container}`; is docker running and the local docker devnet up?"),
)?;
ensure!(
running.trim() == "true",
"devnet container `{container}` is not running; bring the local docker devnet up first"
);
}

for var in [
"FULLNODE_API_INFO",
"FOREST_TEST_PRELOADED_ADDRESS",
"LOTUS_RPC_PORT",
] {
ensure!(
std::env::var_os(var).is_some(),
"{var} is not set; source the devnet test harness and run `devnet_test_env_init` first"
);
}

// Probe eth RPC specifically, not just `ChainHead`: the suites need it, and a devnet with eth
// RPC disabled would otherwise pass here and fail opaquely mid-suite.
for (node, client) in [("forest", forest_client()?), ("lotus", lotus_client()?)] {
EthBlockNumber::call(&client, ()).await.with_context(|| {
format!("{node} eth RPC is not reachable; is the local docker devnet up (with eth RPC enabled) and synced?")
})?;
}

Ok(())
}
19 changes: 19 additions & 0 deletions src/dev/subcommands/devnet_cmd/contracts/compile.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/usr/bin/env bash
# Regenerates the `.hex` bytecode beside every `.sol` fixture here. The tests consume only the
# `.hex`, which is checked in so neither CI nor a developer needs a Solidity toolchain.
#
# The compiler is pinned and each source pins the same version in its `pragma`: bytecode has to
# be reproducible, and a different compiler shifts the gas profile these tests measure.
set -euo pipefail

SOLC_VERSION=0.8.30
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"

for sol in "${DIR}"/*/*.sol; do
contract_dir="$(dirname "${sol}")"
name="$(basename "${sol}" .sol)"
echo "compiling ${name} with solc ${SOLC_VERSION}"
docker run --rm --volume "${contract_dir}:/src:ro" "ethereum/solc:${SOLC_VERSION}" \
--bin "/src/${name}.sol" |
awk '/^Binary:/ { getline; print }' > "${contract_dir}/${name}.hex"
done
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
6080604052348015600e575f5ffd5b506104d98061001c5f395ff3fe608060405234801561000f575f5ffd5b506004361061003f575f3560e01c80637da1365e14610043578063a7f8d9fc14610061578063b2041d211461006b575b5f5ffd5b61004b610087565b6040516100589190610256565b60405180910390f35b61006961008c565b005b6100856004803603810190610080919061029d565b6100ec565b005b5f5481565b6302faf0805a116100d2576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100c990610322565b60405180910390fd5b60015f5f8282546100e3919061036d565b92505081905550565b5f81036101105760015f5f828254610104919061036d565b9250508190555061023b565b5f3073ffffffffffffffffffffffffffffffffffffffff1663b2041d2160e01b60018461013d91906103a0565b60405160240161014d9190610256565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040516101b79190610425565b5f604051808303815f865af19150503d805f81146101f0576040519150601f19603f3d011682016040523d82523d5f602084013e6101f5565b606091505b5050905080610239576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161023090610485565b60405180910390fd5b505b50565b5f819050919050565b6102508161023e565b82525050565b5f6020820190506102695f830184610247565b92915050565b5f5ffd5b61027c8161023e565b8114610286575f5ffd5b50565b5f8135905061029781610273565b92915050565b5f602082840312156102b2576102b161026f565b5b5f6102bf84828501610289565b91505092915050565b5f82825260208201905092915050565b7f676173206c696d697420746f6f206c6f770000000000000000000000000000005f82015250565b5f61030c6011836102c8565b9150610317826102d8565b602082019050919050565b5f6020820190508181035f83015261033981610300565b9050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f6103778261023e565b91506103828361023e565b925082820190508082111561039a57610399610340565b5b92915050565b5f6103aa8261023e565b91506103b58361023e565b92508282039050818111156103cd576103cc610340565b5b92915050565b5f81519050919050565b5f81905092915050565b8281835e5f83830152505050565b5f6103ff826103d3565b61040981856103dd565b93506104198185602086016103e7565b80840191505092915050565b5f61043082846103f5565b915081905092915050565b7f73756263616c6c206f7574206f662067617300000000000000000000000000005f82015250565b5f61046f6012836102c8565b915061047a8261043b565b602082019050919050565b5f6020820190508181035f83015261049c81610463565b905091905056fea2646970667358221220ee3eeb4b133ad268135c21b8298e2c4c79dbda3759a7da207271b8b0839ce53364736f6c634300081e0033
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// SPDX-License-Identifier: MIT
pragma solidity =0.8.30;

/// Each `recurse` level forwards at most 63/64 of the remaining gas (EIP-150:
/// https://github.com/ethereum/EIPs/blob/15f61ed0fda82ec86d8d6a872f6b874816f03d96/EIPS/eip-150.md#L32-L33),
/// so the gas *limit* the top-level call needs grows as (64/63)^depth above the
/// gas it actually *uses*. That gap is what `eth_estimateGas`'s search exists to close.
contract NestedGas {
uint256 public acc;

/// Succeeds only when handed a large gas limit, and otherwise reverts explicitly rather than
/// running out of gas. Raising the limit would in fact fix it, but the estimator has no way to
/// know that, so this is the failure it must report instead of searching around.
function requiresHighGasLimit() external {
require(gasleft() > 50_000_000, "gas limit too low");
acc += 1;
}

function recurse(uint256 depth) external {
if (depth == 0) {
acc += 1;
return;
}
(bool ok, ) = address(this).call(
abi.encodeWithSelector(this.recurse.selector, depth - 1)
);
require(ok, "subcall out of gas");
}
}
Loading
Loading