From 1adc3c9bce5de496483264712213407b3a9df674 Mon Sep 17 00:00:00 2001 From: Francis Li Date: Tue, 6 Oct 2026 04:10:01 +0000 Subject: [PATCH 1/2] Repair CI dependencies and pinned test fixtures Amp-Thread-ID: https://ampcode.com/threads/T-01a10d69-00b6-7378-855f-af2b1a148692 Co-authored-by: Amp --- Cargo.lock | 4 +-- crates/forge/tests/cli/install.rs | 7 ++--- crates/forge/tests/cli/script.rs | 2 +- crates/forge/tests/cli/verify.rs | 47 +++++++++++++++++++++++++++---- crates/test-utils/src/ext.rs | 13 +++++++++ crates/test-utils/src/util.rs | 17 +++++------ 6 files changed, 67 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f92e33126..e2be339f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9987,9 +9987,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.43" +version = "0.23.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" +checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634" dependencies = [ "aws-lc-rs", "log", diff --git a/crates/forge/tests/cli/install.rs b/crates/forge/tests/cli/install.rs index f95f865f9..b91a2fb72 100644 --- a/crates/forge/tests/cli/install.rs +++ b/crates/forge/tests/cli/install.rs @@ -607,9 +607,9 @@ forgetest_init!(sync_on_forge_update, |prj, cmd| { let forge_std_path = prj.root().join("lib/forge-std"); let git = Git::new(&forge_std_path); - // Ensure we're on the release tag first (known starting point) + // Ensure we're on the pinned revision first (known starting point) git.checkout(false, forge_std.name()).unwrap(); - assert_eq!(git.head().unwrap(), forge_std.rev(), "Forge std should be at the release tag"); + assert_eq!(git.head().unwrap(), forge_std.rev(), "Forge std should be at the pinned revision"); // Make sure origin/master is up to date, then resolve its commit hash deterministically. git.fetch(false, "origin", Some("master")).unwrap(); @@ -617,8 +617,7 @@ forgetest_init!(sync_on_forge_update, |prj, cmd| { // Run update and assert the output matches the dynamically resolved hash. let expected_output = format!( - "Updated dep at 'lib/forge-std', (from: tag={}@{}, to: branch=master@{})\n", - forge_std.name(), + "Updated dep at 'lib/forge-std', (from: rev={}, to: branch=master@{})\n", forge_std.rev(), origin_master_head ); diff --git a/crates/forge/tests/cli/script.rs b/crates/forge/tests/cli/script.rs index 0ded70d6b..eebdc3d42 100644 --- a/crates/forge/tests/cli/script.rs +++ b/crates/forge/tests/cli/script.rs @@ -1945,7 +1945,7 @@ contract SimpleScript is Script { ]) .assert_success() .stdout_eq(str![[r#" -{"logs":[],"returns":{"success":{"internal_type":"bool","value":"true"}},"success":true,"raw_logs":[],"traces":[["Deployment",{"arena":[{"parent":null,"children":[],"idx":0,"trace":{"depth":0,"success":true,"caller":"0x1804c8ab1f12e6bbf3894d4083f33e07309d1f38","address":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","maybe_precompile":false,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CREATE","value":"0x0","data":"[..]","output":"[..]","gas_used":"{...}","gas_limit":"{...}","gas_refund_counter":0,"status":"Return","steps":[],"decoded":{"label":"SimpleScript","return_data":null,"call_data":null}},"logs":[],"ordering":[]}]}],["Execution",{"arena":[{"parent":null,"children":[1,2],"idx":0,"trace":{"depth":0,"success":true,"caller":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","address":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","maybe_precompile":null,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0xc0406226","output":"0x0000000000000000000000000000000000000000000000000000000000000001","gas_used":"{...}","gas_limit":1073720760,"gas_refund_counter":0,"status":"Return","steps":[],"decoded":{"label":"SimpleScript","return_data":"true","call_data":{"signature":"run()","args":[]}}},"logs":[],"ordering":[{"Call":0},{"Call":1}]},{"parent":0,"children":[],"idx":1,"trace":{"depth":1,"success":true,"caller":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","address":"0x7109709ecfa91a80626ff3989d68f67f5b1dd12d","maybe_precompile":null,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0x7fb5297f","output":"0x","gas_used":"{...}","gas_limit":1056940999,"gas_refund_counter":0,"status":"Return","steps":[],"decoded":{"label":"VM","return_data":null,"call_data":{"signature":"startBroadcast()","args":[]}}},"logs":[],"ordering":[]},{"parent":0,"children":[],"idx":2,"trace":{"depth":1,"success":true,"caller":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","address":"0x0000000000000000000000000000000000000000","maybe_precompile":null,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0x","output":"0x","gas_used":"{...}","gas_limit":1056940650,"gas_refund_counter":0,"status":"Stop","steps":[],"decoded":{"label":"","return_data":null,"call_data":null}},"logs":[],"ordering":[]}]}]],"gas_used":"{...}","labeled_addresses":{},"returned":"0x0000000000000000000000000000000000000000000000000000000000000001","address":null} +{"logs":[],"returns":{"success":{"internal_type":"bool","value":"true"}},"success":true,"raw_logs":[],"traces":[["Deployment",{"arena":[{"parent":null,"children":[],"idx":0,"trace":{"depth":0,"success":true,"caller":"0x1804c8ab1f12e6bbf3894d4083f33e07309d1f38","address":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","maybe_precompile":false,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CREATE","value":"0x0","data":"[..]","output":"[..]","gas_used":"{...}","gas_limit":"{...}","gas_refund_counter":0,"status":"Return","steps":[],"decoded":{"label":"SimpleScript","return_data":null,"call_data":null}},"logs":[],"ordering":[]}]}],["Execution",{"arena":[{"parent":null,"children":[1,2],"idx":0,"trace":{"depth":0,"success":true,"caller":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","address":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","maybe_precompile":null,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0xc0406226","output":"0x0000000000000000000000000000000000000000000000000000000000000001","gas_used":"{...}","gas_limit":1073720760,"gas_refund_counter":0,"status":"Return","steps":[],"decoded":{"label":"SimpleScript","return_data":"true","call_data":{"signature":"run()","args":[]}}},"logs":[],"ordering":[{"Call":0},{"Call":1}]},{"parent":0,"children":[],"idx":1,"trace":{"depth":1,"success":true,"caller":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","address":"0x7109709ecfa91a80626ff3989d68f67f5b1dd12d","maybe_precompile":null,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0x7fb5297f","output":"0x","gas_used":"{...}","gas_limit":1056940999,"gas_refund_counter":0,"status":"Return","steps":[],"decoded":{"label":"VM","return_data":null,"call_data":{"signature":"startBroadcast()","args":[]}}},"logs":[],"ordering":[]},{"parent":0,"children":[],"idx":2,"trace":{"depth":1,"success":true,"caller":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","address":"0x0000000000000000000000000000000000000000","maybe_precompile":null,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0x","output":"0x","gas_used":"{...}","gas_limit":1056940650,"gas_refund_counter":0,"status":"Stop","steps":[],"decoded":{"label":null,"return_data":null,"call_data":null}},"logs":[],"ordering":[]}]}]],"gas_used":"{...}","labeled_addresses":{},"returned":"0x0000000000000000000000000000000000000000000000000000000000000001","address":null} {"chain":31337,"estimated_gas_price":"{...}","estimated_total_gas_used":"{...}","estimated_amount_required":"{...}","token_symbol":"ETH"} {"chain":"anvil-hardhat","status":"success","tx_hash":"0x4f78afe915fceb282c7625a68eb350bc0bf78acb59ad893e5c62b710a37f3156","contract_address":null,"block_number":1,"gas_used":"{...}","gas_price":"{...}"} {"status":"success","transactions":"[..]/broadcast/Foo.sol/31337/run-latest.json","sensitive":"[..]/cache/Foo.sol/31337/run-latest.json"} diff --git a/crates/forge/tests/cli/verify.rs b/crates/forge/tests/cli/verify.rs index 2e5a5b4ef..7fab8b2f5 100644 --- a/crates/forge/tests/cli/verify.rs +++ b/crates/forge/tests/cli/verify.rs @@ -313,16 +313,24 @@ forgetest!(can_verify_random_contract_sepolia_default_sourcify, |prj, cmd| { // Tests that verify properly validates verifier arguments. // -forgetest_init!(can_validate_verifier_settings, |prj, cmd| { +forgetest_async!(can_validate_verifier_settings, |prj, cmd| { + foundry_test_utils::util::initialize(prj.root()); prj.initialize_default_contracts(); + // A local solc reports full build metadata; with an explicit compilation profile (which skips + // the cache lookup that drops it), verify never needs the remote solc release list. + prj.update_config(|config| { + config.solc = Some(foundry_config::SolcReq::Local(svm::version_binary( + foundry_test_utils::util::SOLC_VERSION, + ))); + }); // Build the project to create the cache. cmd.forge_fuse().arg("build").assert_success(); // No verifier URL. cmd.forge_fuse() .args([ "verify-contract", - "--rpc-url", - "https://rpc.sepolia-api.lisk.com", + "--chain", + "4202", "--verifier", "blockscout", "0x19b248616E4964f43F611b5871CE1250f360E9d3", @@ -338,8 +346,8 @@ Error: No verifier URL specified for verifier blockscout cmd.forge_fuse() .args([ "verify-contract", - "--rpc-url", - "https://rpc.sepolia-api.lisk.com", + "--chain", + "4202", "--verifier", "etherscan", "0x19b248616E4964f43F611b5871CE1250f360E9d3", @@ -353,7 +361,34 @@ Error: No known Etherscan API URL for chain `4202`. To fix this, please: "#]]); - cmd.forge_fuse().args(["verify-contract", "--rpc-url", "https://rpc.sepolia-api.lisk.com", "--verifier", "blockscout", "--verifier-url", "https://sepolia-blockscout.lisk.com/api", "0x19b248616E4964f43F611b5871CE1250f360E9d3", "src/Counter.sol:Counter"]).assert_success().stdout_eq(str![[r#" + let app = axum::Router::new().route( + "/api", + axum::routing::get( + |axum::extract::Query(q): axum::extract::Query< + std::collections::HashMap, + >| async move { + let expected = q.get("module").is_some_and(|m| m == "contract") + && q.get("action").is_some_and(|a| a == "getabi") + && q.get("address").is_some_and(|a| { + a.eq_ignore_ascii_case("0x19b248616E4964f43F611b5871CE1250f360E9d3") + }); + if expected { + Ok(axum::Json( + serde_json::json!({"status": "1", "message": "OK", "result": "[]"}), + )) + } else { + Err((axum::http::StatusCode::BAD_REQUEST, format!("unexpected query: {q:?}"))) + } + }, + ), + ); + let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); + let verifier_url = format!("http://{}/api", listener.local_addr().unwrap()); + tokio::spawn(async move { + axum::serve(listener, app).await.unwrap(); + }); + + cmd.forge_fuse().args(["verify-contract", "--chain", "4202", "--verifier", "blockscout", "--verifier-url", &verifier_url, "--compilation-profile", "default", "0x19b248616E4964f43F611b5871CE1250f360E9d3", "src/Counter.sol:Counter"]).assert_success().stdout_eq(str![[r#" Start verifying contract `0x19b248616E4964f43F611b5871CE1250f360E9d3` deployed on 4202 Contract [src/Counter.sol:Counter] "0x19b248616E4964f43F611b5871CE1250f360E9d3" is already verified. Skipping verification. diff --git a/crates/test-utils/src/ext.rs b/crates/test-utils/src/ext.rs index 18d6bfc4c..0bb15e08d 100644 --- a/crates/test-utils/src/ext.rs +++ b/crates/test-utils/src/ext.rs @@ -138,6 +138,19 @@ impl ExtTester { if !status.success() { panic!("git checkout failed: {status}"); } + + // Check out the submodules pinned by the selected revision. + if recursive { + for args in [&["sync", "--recursive"][..], &["update", "--init", "--recursive"]] { + let mut git = Command::new("git"); + git.current_dir(root).arg("submodule").args(args); + test_debug!("$ {git:?}"); + let status = git.status().unwrap(); + if !status.success() { + panic!("git submodule {} failed: {status}", args[0]); + } + } + } } (prj, test_cmd) diff --git a/crates/test-utils/src/util.rs b/crates/test-utils/src/util.rs index aa36ce02d..2fa791b82 100644 --- a/crates/test-utils/src/util.rs +++ b/crates/test-utils/src/util.rs @@ -7,7 +7,6 @@ use std::{ fs::{self, File}, io::{IsTerminal, Read, Seek, Write}, path::{Path, PathBuf}, - process::Command, sync::LazyLock, }; @@ -81,20 +80,18 @@ pub fn initialize(target: &Path) { let (prj, mut cmd) = setup_forge("template", foundry_compilers::PathStyle::Dapptools); test_debug!("- initializing template dir in {}", prj.root().display()); - cmd.args(["init", "--force", "--empty"]).assert_success(); + cmd.args(["init", "--force", "--empty", "--offline"]).assert_success(); + + // Install the pinned forge-std so `foundry.lock` records the checked-out revision. + cmd.forge_fuse() + .args(["install", &format!("foundry-rs/forge-std@{FORGE_STD_REVISION}")]) + .assert_success(); + prj.write_config(Config { solc: Some(foundry_config::SolcReq::Version(SOLC_VERSION.parse().unwrap())), ..Default::default() }); - // Checkout forge-std. - let output = Command::new("git") - .current_dir(prj.root().join("lib/forge-std")) - .args(["checkout", FORGE_STD_REVISION]) - .output() - .expect("failed to checkout forge-std"); - assert!(output.status.success(), "{output:#?}"); - // Build the project. cmd.forge_fuse().arg("build").assert_success(); From 48a3e17ac32d44fabf7eecb81f3fa27b254d77c7 Mon Sep 17 00:00:00 2001 From: Francis Li Date: Tue, 6 Oct 2026 04:25:49 +0000 Subject: [PATCH 2/2] Remove timing races from Anvil CI assertions Amp-Thread-ID: https://ampcode.com/threads/T-01a10d69-00b6-7378-855f-af2b1a148692 Co-authored-by: Amp --- crates/anvil/tests/it/anvil_api.rs | 14 ++++++++++---- crates/anvil/tests/it/gas.rs | 18 +++++++++++++++++- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/crates/anvil/tests/it/anvil_api.rs b/crates/anvil/tests/it/anvil_api.rs index 34cf2abee..3c15b7aca 100644 --- a/crates/anvil/tests/it/anvil_api.rs +++ b/crates/anvil/tests/it/anvil_api.rs @@ -881,19 +881,25 @@ async fn test_mine_blk_with_prev_timestamp() { assert!(third_blk_timestamp > next_blk_timestamp); } -// increase time by 0 seconds i.e next_block_timestamp = prev_block_timestamp -// api.evm_increase_time(0).unwrap(); +// increase time by 0 seconds leaves the existing offset unchanged; the next block timestamp is +// pinned explicitly because the default timestamp still tracks wall-clock time. #[tokio::test(flavor = "multi_thread")] async fn test_increase_time_by_zero() { - let (api, handle) = spawn(NodeConfig::test()).await; + let (api, handle) = spawn(NodeConfig::test().with_genesis_timestamp(Some(1_000u64))).await; let provider = handle.http_provider(); let init_blk = provider.get_block(BlockId::latest()).await.unwrap().unwrap(); let init_number = init_blk.header.number; let init_timestamp = init_blk.header.timestamp; + let init_offset = api.backend.time().offset(); + assert_ne!(init_offset, 0); + + api.evm_set_next_block_timestamp(init_timestamp).unwrap(); - let _ = api.evm_increase_time(U256::ZERO).await; + let offset = api.evm_increase_time(U256::ZERO).await.unwrap(); + assert_eq!(i128::from(offset), init_offset); + assert_eq!(api.backend.time().offset(), init_offset); api.mine_one().await; diff --git a/crates/anvil/tests/it/gas.rs b/crates/anvil/tests/it/gas.rs index 2becfb386..cd2e6bb08 100644 --- a/crates/anvil/tests/it/gas.rs +++ b/crates/anvil/tests/it/gas.rs @@ -208,7 +208,23 @@ async fn test_can_use_fee_history() { provider.send_transaction(tx.clone()).await.unwrap().get_receipt().await.unwrap(); assert!(receipt.inner.inner.is_success()); - let fee_history_after = provider.get_fee_history(1, Default::default(), &[]).await.unwrap(); + // The receipt is available before the fee history service caches the mined block, so poll + // until the history covers the receipt's block plus the next base fee. + let block_number = receipt.block_number.unwrap(); + let fee_history_after = tokio::time::timeout(std::time::Duration::from_secs(5), async { + loop { + let fee_history = + provider.get_fee_history(1, Default::default(), &[]).await.unwrap(); + if fee_history.oldest_block == block_number + && fee_history.base_fee_per_gas.len() == 2 + { + break fee_history; + } + tokio::time::sleep(std::time::Duration::from_millis(10)).await; + } + }) + .await + .expect("fee history did not include the mined block"); let latest_fee_history_fee = *fee_history_after.base_fee_per_gas.first().unwrap() as u64; let latest_block = provider.get_block(BlockId::latest()).await.unwrap().unwrap();