From ec9aaaf49ca21da31ab01107e9ce0d81e62861d1 Mon Sep 17 00:00:00 2001 From: David Lutterkort Date: Fri, 23 Jan 2026 09:41:09 -0800 Subject: [PATCH 1/6] tests: Use foundry v1.4.0 in docker-compose That makes it use the same as what CI uses --- tests/docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/docker-compose.yml b/tests/docker-compose.yml index 19dfa1f86c5..c05228f8418 100644 --- a/tests/docker-compose.yml +++ b/tests/docker-compose.yml @@ -22,7 +22,7 @@ services: anvil: # Pinned to specific version since newer versions do not produce # deterministic block hashes. Unpin once that's fixed upstream - image: ghcr.io/foundry-rs/foundry:v1.2.3 + image: ghcr.io/foundry-rs/foundry:v1.4.0 ports: - '3021:8545' command: "'anvil --host 0.0.0.0 --gas-limit 100000000000 --base-fee 1 --block-time 2 --timestamp 1743944919 --mnemonic \"test test test test test test test test test test test junk\"'" From de6792591a42efb862444b262c2c6c6a36d65494 Mon Sep 17 00:00:00 2001 From: David Lutterkort Date: Fri, 23 Jan 2026 09:41:43 -0800 Subject: [PATCH 2/6] tests: Change grafted test to not use hardcoded POIs We want to make the test independent of the IPFS hash of the subgraphs involved. Instead of using hardcoded POIs, we calculate them during the test --- pnpm-lock.yaml | 52 ---- tests/integration-tests/grafted/subgraph.yaml | 4 +- tests/src/subgraph.rs | 37 ++- tests/tests/integration_tests.rs | 267 ++++++++++++++---- 4 files changed, 255 insertions(+), 105 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9276137fd13..4fab0a09157 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -308,12 +308,6 @@ importers: specifier: 0.31.0 version: 0.31.0 - tests/runner-tests/substreams: - devDependencies: - '@graphprotocol/graph-cli': - specifier: 0.61.0 - version: 0.61.0(@types/node@24.3.0)(bufferutil@4.0.9)(encoding@0.1.13)(node-fetch@2.7.0(encoding@0.1.13))(typescript@5.9.2)(utf-8-validate@5.0.10) - tests/runner-tests/typename: devDependencies: '@graphprotocol/graph-cli': @@ -419,11 +413,6 @@ packages: engines: {node: '>=14'} hasBin: true - '@graphprotocol/graph-cli@0.61.0': - resolution: {integrity: sha512-gc3+DioZ/K40sQCt6DsNvbqfPTc9ZysuSz3I9MJ++bD6SftaSSweWwfpPysDMzDuxvUAhLAsJ6QjBACPngT2Kw==} - engines: {node: '>=14'} - hasBin: true - '@graphprotocol/graph-cli@0.69.0': resolution: {integrity: sha512-DoneR0TRkZYumsygdi/RST+OB55TgwmhziredI21lYzfj0QNXGEHZOagTOKeFKDFEpP3KR6BAq6rQIrkprJ1IQ==} engines: {node: '>=18'} @@ -3450,47 +3439,6 @@ snapshots: - typescript - utf-8-validate - '@graphprotocol/graph-cli@0.61.0(@types/node@24.3.0)(bufferutil@4.0.9)(encoding@0.1.13)(node-fetch@2.7.0(encoding@0.1.13))(typescript@5.9.2)(utf-8-validate@5.0.10)': - dependencies: - '@float-capital/float-subgraph-uncrashable': 0.0.0-internal-testing.5 - '@oclif/core': 2.8.6(@types/node@24.3.0)(typescript@5.9.2) - '@oclif/plugin-autocomplete': 2.3.10(@types/node@24.3.0)(typescript@5.9.2) - '@oclif/plugin-not-found': 2.4.3(@types/node@24.3.0)(typescript@5.9.2) - '@whatwg-node/fetch': 0.8.8 - assemblyscript: 0.19.23 - binary-install-raw: 0.0.13(debug@4.3.4) - chalk: 3.0.0 - chokidar: 3.5.3 - debug: 4.3.4(supports-color@8.1.1) - docker-compose: 0.23.19 - dockerode: 2.5.8 - fs-extra: 9.1.0 - glob: 9.3.5 - gluegun: 5.1.2(debug@4.3.4) - graphql: 15.5.0 - immutable: 4.2.1 - ipfs-http-client: 55.0.0(encoding@0.1.13)(node-fetch@2.7.0(encoding@0.1.13)) - jayson: 4.0.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) - js-yaml: 3.14.1 - prettier: 1.19.1 - request: 2.88.2 - semver: 7.4.0 - sync-request: 6.1.0 - tmp-promise: 3.0.3 - web3-eth-abi: 1.7.0 - which: 2.0.2 - yaml: 1.10.2 - transitivePeerDependencies: - - '@swc/core' - - '@swc/wasm' - - '@types/node' - - bufferutil - - encoding - - node-fetch - - supports-color - - typescript - - utf-8-validate - '@graphprotocol/graph-cli@0.69.0(@types/node@24.3.0)(bufferutil@4.0.9)(encoding@0.1.13)(node-fetch@2.7.0(encoding@0.1.13))(typescript@5.9.2)(utf-8-validate@5.0.10)': dependencies: '@float-capital/float-subgraph-uncrashable': 0.0.0-internal-testing.5 diff --git a/tests/integration-tests/grafted/subgraph.yaml b/tests/integration-tests/grafted/subgraph.yaml index c0435df9c11..6cd33f93006 100644 --- a/tests/integration-tests/grafted/subgraph.yaml +++ b/tests/integration-tests/grafted/subgraph.yaml @@ -26,5 +26,5 @@ dataSources: features: - grafting graft: - base: QmTQbJ234d2Po7xKZS5wKPiYuMYsCAqqY4df5czESjEXn4 - block: 2 \ No newline at end of file + base: '@base@' + block: 2 diff --git a/tests/src/subgraph.rs b/tests/src/subgraph.rs index e1057fccdcb..67513f49ca0 100644 --- a/tests/src/subgraph.rs +++ b/tests/src/subgraph.rs @@ -47,16 +47,41 @@ impl Subgraph { Ok(()) } + /// Patch source subgraph placeholders in the manifest with their deployment hashes. + /// This must be called after `patch()` since it reads from `subgraph.yaml.patched`. + pub fn patch_sources(dir: &TestFile, sources: &[(String, String)]) -> anyhow::Result<()> { + if sources.is_empty() { + return Ok(()); + } + + let patched_path = dir.path.join("subgraph.yaml.patched"); + let mut content = fs::read_to_string(&patched_path)?; + + for (placeholder, deployment_hash) in sources { + let repl = format!("@{}@", placeholder); + content = content.replace(&repl, deployment_hash); + } + + fs::write(&patched_path, content)?; + Ok(()) + } + /// Prepare the subgraph for deployment by patching contracts and checking for subgraph datasources pub async fn prepare( name: &str, contracts: &[Contract], + sources: Option<&[(String, String)]>, ) -> anyhow::Result<(TestFile, String, bool)> { let dir = Self::dir(name); let name = format!("test/{name}"); Self::patch(&dir, contracts).await?; + // Patch source subgraph placeholders if provided + if let Some(sources) = sources { + Self::patch_sources(&dir, sources)?; + } + // Check if subgraph has subgraph datasources let yaml_content = fs::read_to_string(dir.path.join("subgraph.yaml.patched"))?; let yaml: serde_yaml::Value = serde_yaml::from_str(&yaml_content)?; @@ -68,9 +93,15 @@ impl Subgraph { Ok((dir, name, has_subgraph_datasource)) } - /// Deploy the subgraph by running the required `graph` commands - pub async fn deploy(name: &str, contracts: &[Contract]) -> anyhow::Result { - let (dir, name, has_subgraph_datasource) = Self::prepare(name, contracts).await?; + /// Deploy the subgraph by running the required `graph` commands. + /// If `sources` is provided, the deployment hashes will be used to patch + /// source subgraph placeholders (e.g., `@source-subgraph@`) in the manifest. + pub async fn deploy( + name: &str, + contracts: &[Contract], + sources: Option<&[(String, String)]>, + ) -> anyhow::Result { + let (dir, name, has_subgraph_datasource) = Self::prepare(name, contracts, sources).await?; // graph codegen subgraph.yaml let mut prog = Command::new(&CONFIG.graph_cli); diff --git a/tests/tests/integration_tests.rs b/tests/tests/integration_tests.rs index b5c7d3405ca..e47f043bd67 100644 --- a/tests/tests/integration_tests.rs +++ b/tests/tests/integration_tests.rs @@ -9,14 +9,23 @@ //! tasks are really worth parallelizing, and applying this trick //! indiscriminately will only result in messy code and diminishing returns. +use std::collections::HashMap; use std::future::Future; use std::pin::Pin; use std::time::{self, Duration, Instant}; use anyhow::{anyhow, bail, Context, Result}; +use graph::components::subgraph::{ + ProofOfIndexing, ProofOfIndexingEvent, ProofOfIndexingFinisher, ProofOfIndexingVersion, +}; +use graph::data::store::Id; +use graph::entity; use graph::futures03::StreamExt; use graph::itertools::Itertools; use graph::prelude::serde_json::{json, Value}; +use graph::prelude::{alloy::primitives::Address, hex, BlockPtr, DeploymentHash}; +use graph::schema::InputSchema; +use graph::slog::{o, Discard, Logger}; use graph_tests::contract::Contract; use graph_tests::subgraph::Subgraph; use graph_tests::{error, status, CONFIG}; @@ -172,7 +181,7 @@ impl TestCase { contracts: &[Contract], ) -> Result { status!(&self.name, "Deploying subgraph"); - let subgraph_name = match Subgraph::deploy(subgraph_name, contracts).await { + let subgraph_name = match Subgraph::deploy(subgraph_name, contracts, None).await { Ok(name) => name, Err(e) => { error!(&self.name, "Deploy failed"); @@ -199,22 +208,28 @@ impl TestCase { } pub async fn prepare(&self, contracts: &[Contract]) -> anyhow::Result { - // If a subgraph has subgraph datasources, prepare them first - if let Some(_subgraphs) = &self.source_subgraph { - if let Err(e) = self.prepare_multiple_sources(contracts).await { - error!(&self.name, "source subgraph deployment failed: {:?}", e); - return Err(e); + // If a subgraph has subgraph datasources, prepare them first and collect their deployment hashes + let source_mappings = if let Some(_subgraphs) = &self.source_subgraph { + match self.prepare_multiple_sources(contracts).await { + Ok(mappings) => Some(mappings), + Err(e) => { + error!(&self.name, "source subgraph deployment failed: {:?}", e); + return Err(e); + } } - } + } else { + None + }; status!(&self.name, "Preparing subgraph"); - let (_, subgraph_name, _) = match Subgraph::prepare(&self.name, contracts).await { - Ok(name) => name, - Err(e) => { - error!(&self.name, "Prepare failed: {:?}", e); - return Err(e); - } - }; + let (_, subgraph_name, _) = + match Subgraph::prepare(&self.name, contracts, source_mappings.as_deref()).await { + Ok(name) => name, + Err(e) => { + error!(&self.name, "Prepare failed: {:?}", e); + return Err(e); + } + }; Ok(subgraph_name) } @@ -276,45 +291,64 @@ impl TestCase { } } - async fn run(self, contracts: &[Contract]) -> TestResult { - // If a subgraph has subgraph datasources, deploy them first - if let Some(_subgraphs) = &self.source_subgraph { - if let Err(e) = self.deploy_multiple_sources(contracts).await { - error!(&self.name, "source subgraph deployment failed"); - return TestResult { - name: self.name.clone(), - subgraph: None, - status: TestStatus::Err(e), - }; + pub async fn run(self, contracts: &[Contract]) -> TestResult { + // If a subgraph has subgraph datasources, deploy them first and collect their deployment hashes + let source_mappings = if let Some(_subgraphs) = &self.source_subgraph { + match self.deploy_multiple_sources(contracts).await { + Ok(mappings) => Some(mappings), + Err(e) => { + error!(&self.name, "source subgraph deployment failed"); + return TestResult { + name: self.name.clone(), + subgraph: None, + status: TestStatus::Err(e), + }; + } } - } + } else { + None + }; status!(&self.name, "Deploying subgraph"); - let subgraph_name = match Subgraph::deploy(&self.name, contracts).await { - Ok(name) => name, - Err(e) => { - error!(&self.name, "Deploy failed"); - return TestResult { - name: self.name.clone(), - subgraph: None, - status: TestStatus::Err(e.context("Deploy failed")), - }; - } - }; + let subgraph_name = + match Subgraph::deploy(&self.name, contracts, source_mappings.as_deref()).await { + Ok(name) => name, + Err(e) => { + error!(&self.name, "Deploy failed"); + return TestResult { + name: self.name.clone(), + subgraph: None, + status: TestStatus::Err(e.context("Deploy failed")), + }; + } + }; self.check_health_and_test(contracts, subgraph_name).await } - async fn prepare_multiple_sources(&self, contracts: &[Contract]) -> Result<()> { + async fn prepare_multiple_sources( + &self, + contracts: &[Contract], + ) -> Result> { + let mut mappings = Vec::new(); if let Some(sources) = &self.source_subgraph { for source in sources { - let _ = Subgraph::prepare(source.test_name(), contracts).await?; + // Source subgraphs don't have their own sources, so pass None + let _ = Subgraph::prepare(source.test_name(), contracts, None).await?; + // If the source has an alias (pre-known IPFS hash), use it for the mapping + if let Some(alias) = source.alias() { + mappings.push((source.test_name().to_string(), alias.to_string())); + } } } - Ok(()) + Ok(mappings) } - async fn deploy_multiple_sources(&self, contracts: &[Contract]) -> Result<()> { + async fn deploy_multiple_sources( + &self, + contracts: &[Contract], + ) -> Result> { + let mut mappings = Vec::new(); if let Some(sources) = &self.source_subgraph { for source in sources { let subgraph = self.deploy_and_wait(source.test_name(), contracts).await?; @@ -323,9 +357,11 @@ impl TestCase { "Source subgraph deployed with hash {}", subgraph.deployment ); + // Use the test_name as the placeholder key + mappings.push((source.test_name().to_string(), subgraph.deployment.clone())); } } - Ok(()) + Ok(mappings) } } @@ -892,21 +928,22 @@ async fn test_subgraph_grafting(ctx: TestContext) -> anyhow::Result<()> { assert!(subgraph.healthy); + // Fixed block hashes from deterministic Anvil config let block_hashes: Vec<&str> = vec![ "e26fccbd24dcc76074b432becf29cad3bcba11a8467a7b770fad109c2b5d14c2", "249dbcbee975c22f8c9cc937536945ca463568c42d8933a3f54129dec352e46b", "408675f81c409dede08d0eeb2b3420a73b067c4fa8c5f0fc49ce369289467c33", ]; - let pois: Vec<&str> = vec![ - "0x606c1ed77564ef9ab077e0438da9f3c6af79a991603aecf74650971a88d05b65", - "0xbb21d5cf5fd62892159f95211da4a02f0dfa1b43d68aeb64baa52cc67fbb6c8e", - "0x5a01b371017c924e8cedd62a76cf8dcf05987f80d2b91aaf3fb57872ab75887f", - ]; + // The deployment hash is dynamic (depends on the base subgraph's hash) + let deployment_hash = DeploymentHash::new(&subgraph.deployment).unwrap(); + + // Compute the expected POI values dynamically + let expected_pois = compute_expected_pois(&deployment_hash, &block_hashes); for i in 1..4 { let block_hash = get_block_hash(i).await.unwrap(); - // We need to make sure that the preconditions for POI are fulfiled + // We need to make sure that the preconditions for POI are fulfilled // namely that the blockchain produced the proper block hashes for the // blocks of which we will check the POI. assert_eq!(block_hash, block_hashes[(i - 1) as usize]); @@ -938,12 +975,146 @@ async fn test_subgraph_grafting(ctx: TestContext) -> anyhow::Result<()> { // Change on the block #2 would mean a change in the transitioning // from the old to the new algorithm hence would be reflected only // subgraphs that are grafting from pre 0.0.5 to 0.0.6 or newer. - assert_eq!(poi, pois[(i - 1) as usize]); + assert_eq!( + poi, + expected_pois[(i - 1) as usize], + "POI mismatch for block {}", + i + ); } Ok(()) } +/// Compute the expected POI values for the grafted subgraph. +/// +/// The grafted subgraph: +/// - Spec version 0.0.6 (uses Fast POI algorithm) +/// - Grafts from base subgraph at block 2 +/// - Creates GraftedData entities starting from block 3 +/// +/// The base subgraph: +/// - Spec version 0.0.5 (uses Legacy POI algorithm) +/// - Creates BaseData entities for each block +/// +/// POI algorithm transition: +/// - Blocks 0-2: Legacy POI digests (from base subgraph) +/// - Block 3+: Fast POI algorithm with transition from Legacy +fn compute_expected_pois(deployment_hash: &DeploymentHash, block_hashes: &[&str]) -> Vec { + let logger = Logger::root(Discard, o!()); + let causality_region = "ethereum/test"; + + // Create schemas for the entity types + let base_schema = InputSchema::parse_latest( + "type BaseData @entity(immutable: true) { id: ID!, data: String!, blockNumber: BigInt! }", + deployment_hash.clone(), + ) + .unwrap(); + + let grafted_schema = InputSchema::parse_latest( + "type GraftedData @entity(immutable: true) { id: ID!, data: String!, blockNumber: BigInt! }", + deployment_hash.clone(), + ) + .unwrap(); + + // Compute POI digests at each block checkpoint + // Store the accumulated state after each block so we can compute POI at any block + let mut db_at_block: HashMap>> = HashMap::new(); + let mut db: HashMap> = HashMap::new(); + + // Process blocks 0-3: + // - Blocks 0-2: Legacy POI (from base subgraph, creates BaseData entities) + // - Block 3: Fast POI (grafted subgraph starts here, creates GraftedData entity) + // + // The base subgraph starts from block 0 (genesis block triggers handlers in Anvil). + // + // The grafted subgraph: + // - spec version 0.0.6 → uses Fast POI algorithm + // - grafts from base subgraph at block 2 + // - inherits POI digests from base for blocks 0-2 + // - transitions from Legacy to Fast at block 3 + for block_i in 0..=3i32 { + let version = if block_i <= 2 { + ProofOfIndexingVersion::Legacy + } else { + ProofOfIndexingVersion::Fast + }; + + let mut stream = ProofOfIndexing::new(block_i, version); + + if block_i <= 2 { + // Base subgraph creates BaseData + let id_str = block_i.to_string(); + let entity = entity! { + base_schema => + id: &id_str, + data: "from base", + blockNumber: graph::prelude::Value::BigInt(block_i.into()), + }; + + let event = ProofOfIndexingEvent::SetEntity { + entity_type: "BaseData", + id: &id_str, + data: &entity, + }; + stream.write(&logger, causality_region, &event); + } else { + // Grafted subgraph creates GraftedData + let id_str = block_i.to_string(); + let entity = entity! { + grafted_schema => + id: &id_str, + data: "to grafted", + blockNumber: graph::prelude::Value::BigInt(block_i.into()), + }; + + let event = ProofOfIndexingEvent::SetEntity { + entity_type: "GraftedData", + id: &id_str, + data: &entity, + }; + stream.write(&logger, causality_region, &event); + } + + for (name, region) in stream.take() { + let prev = db.get(&name); + let update = region.pause(prev.map(|v| &v[..])); + db.insert(name, update); + } + + db_at_block.insert(block_i, db.clone()); + } + + // Compute POI for blocks 1, 2, 3 + let mut pois = Vec::new(); + for (block_idx, block_hash_hex) in block_hashes.iter().enumerate() { + let block_number = (block_idx + 1) as i32; + + // Get the POI version for this block - grafted subgraph uses Fast (spec 0.0.6) + let version = ProofOfIndexingVersion::Fast; + + let block_hash_bytes = hex::decode(block_hash_hex).unwrap(); + let block_ptr = BlockPtr::from((block_hash_bytes, block_number as u64)); + + // Use zero address to match the test query's indexer parameter + let indexer = Some(Address::ZERO); + let mut finisher = + ProofOfIndexingFinisher::new(&block_ptr, deployment_hash, &indexer, version); + + if let Some(db_state) = db_at_block.get(&block_number) { + for (name, region) in db_state.iter() { + finisher.add_causality_region(name, region); + } + } + + let poi_bytes = finisher.finish(); + let poi = format!("0x{}", hex::encode(poi_bytes)); + pois.push(poi); + } + + pois +} + async fn test_poi_for_failed_subgraph(ctx: TestContext) -> anyhow::Result<()> { let subgraph = ctx.subgraph; const INDEXING_STATUS: &str = r#" From ddab0b0f19faebe13a65b160447bf94632cd2c2c Mon Sep 17 00:00:00 2001 From: David Lutterkort Date: Fri, 23 Jan 2026 10:13:28 -0800 Subject: [PATCH 3/6] tests: Remove staleness check for compiled contracts Git doesn't preserve mtimes, and this check just checks whether the source or the compiled contract was checked out first. --- tests/src/contract.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/tests/src/contract.rs b/tests/src/contract.rs index 80a9ba57031..5ef59bc7eed 100644 --- a/tests/src/contract.rs +++ b/tests/src/contract.rs @@ -75,14 +75,7 @@ impl Contract { } fn code_and_abi(name: &str) -> anyhow::Result<(String, Vec)> { - let src = TestFile::new(&format!("contracts/src/{}.sol", name)); let bin = TestFile::new(&format!("contracts/out/{}.sol/{}.json", name, name)); - if src.newer(&bin) { - println!( - "The source {} is newer than the compiled contract {}. Please recompile.", - src, bin - ); - } let json: Value = serde_json::from_reader(bin.reader()?).unwrap(); let abi = serde_json::to_string(&json["abi"]).unwrap(); From 94aa120120eb2edb682db7915471a67d84a974ee Mon Sep 17 00:00:00 2001 From: David Lutterkort Date: Wed, 28 Jan 2026 15:43:45 -0800 Subject: [PATCH 4/6] tests: Make topic-filter test more predictable Use insertion order instead of transaction hash as the id so that sorting of entities is independent of hashes, and always returns entities in the order in which they were created --- tests/integration-tests/topic-filter/schema.graphql | 2 +- tests/integration-tests/topic-filter/src/mapping.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration-tests/topic-filter/schema.graphql b/tests/integration-tests/topic-filter/schema.graphql index 1ff0f94adab..01dfa0433dd 100644 --- a/tests/integration-tests/topic-filter/schema.graphql +++ b/tests/integration-tests/topic-filter/schema.graphql @@ -1,5 +1,5 @@ type AnotherTriggerEntity @entity { - id: ID! + id: Int8! a: BigInt b: BigInt c: BigInt diff --git a/tests/integration-tests/topic-filter/src/mapping.ts b/tests/integration-tests/topic-filter/src/mapping.ts index 9a5ab36b221..53a38f42117 100644 --- a/tests/integration-tests/topic-filter/src/mapping.ts +++ b/tests/integration-tests/topic-filter/src/mapping.ts @@ -3,7 +3,7 @@ import { AnotherTrigger } from "../generated/Contract/Contract"; import { AnotherTriggerEntity } from "../generated/schema"; export function handleAnotherTrigger(event: AnotherTrigger): void { - let entity = new AnotherTriggerEntity(event.transaction.hash.toHex()); + let entity = new AnotherTriggerEntity("auto"); entity.a = event.params.a; entity.b = event.params.b; entity.c = event.params.c; From 6bd7e217bc3b95dfb2413d5ac539ede5b8601c77 Mon Sep 17 00:00:00 2001 From: David Lutterkort Date: Wed, 28 Jan 2026 15:45:00 -0800 Subject: [PATCH 5/6] tests: Fix running integration tests multiple times When running integration tests multiple times, we need to make sure that we emit events only once, not once per run. Change the topic-filter test to do just that --- tests/src/contract.rs | 46 ++++++++++++++++++--- tests/tests/gnd_tests.rs | 2 +- tests/tests/integration_tests.rs | 71 ++------------------------------ 3 files changed, 45 insertions(+), 74 deletions(-) diff --git a/tests/src/contract.rs b/tests/src/contract.rs index 5ef59bc7eed..82daa1b00f5 100644 --- a/tests/src/contract.rs +++ b/tests/src/contract.rs @@ -9,7 +9,7 @@ use web3::{ api::{Eth, Namespace}, contract::{tokens::Tokenize, Contract as Web3Contract, Options}, transports::Http, - types::{Address, Block, BlockId, BlockNumber, Bytes, TransactionReceipt, H256}, + types::{Address, Block, BlockId, BlockNumber, Bytes, TransactionReceipt, H256, U256}, }; // web3 version 0.18 does not expose this; once the graph crate updates to // version 0.19, we can use web3::signing::SecretKey from the graph crate @@ -34,19 +34,19 @@ lazy_static! { }, Contract { name: "LimitedContract".to_string(), - address: Address::from_str("0xb7f8bc63bbcad18155201308c8f3540b07f84f5e").unwrap(), + address: Address::from_str("0x0b306bf915c4d645ff596e518faf3f9669b97016").unwrap(), }, Contract { name: "RevertingContract".to_string(), - address: Address::from_str("0xa51c1fc2f0d1a1b8494ed1fe312d7c3a78ed91c0").unwrap(), + address: Address::from_str("0x959922be3caee4b8cd9a407cc3ac1c251c2007b1").unwrap(), }, Contract { name: "OverloadedContract".to_string(), - address: Address::from_str("0x0dcd1bf9a1b36ce34237eeafef220932846bcd82").unwrap(), + address: Address::from_str("0x9a9f2ccfde556a7e9ff0848998aa4a0cfd8863ae").unwrap(), }, Contract { name: "DeclaredCallsContract".to_string(), - address: Address::from_str("0x9a676e781a523b5d0c0e43731313a708cb607508").unwrap(), + address: Address::from_str("0x68b1d87f95878fe05b998f19b66f4baba5de1aed").unwrap(), }, ] }; @@ -195,6 +195,42 @@ impl Contract { .await .unwrap(); } + // The topic-filter test needs some events emitted + if contract.name == "SimpleContract" { + status!("contracts", "Emitting anotherTrigger from SimpleContract"); + let args = &[ + ( + U256::from(1), + U256::from(2), + U256::from(3), + "abc".to_string(), + ), + ( + U256::from(1), + U256::from(1), + U256::from(1), + "abc".to_string(), + ), + ( + U256::from(4), + U256::from(2), + U256::from(3), + "abc".to_string(), + ), + ( + U256::from(4), + U256::from(4), + U256::from(3), + "abc".to_string(), + ), + ]; + for arg in args { + contract + .call("emitAnotherTrigger", arg.clone()) + .await + .unwrap(); + } + } } else { status!( "contracts", diff --git a/tests/tests/gnd_tests.rs b/tests/tests/gnd_tests.rs index 3e3a2e0f448..0ed1ac5ec8a 100644 --- a/tests/tests/gnd_tests.rs +++ b/tests/tests/gnd_tests.rs @@ -104,7 +104,7 @@ async fn gnd_tests() -> anyhow::Result<()> { .enumerate() .map(|(index, case)| { let subgraph_name = format!("subgraph-{}", num_sources + index); - case.check_health_and_test(&contracts, subgraph_name) + case.check_health_and_test(subgraph_name) }) .buffered(CONFIG.num_parallel_tests); diff --git a/tests/tests/integration_tests.rs b/tests/tests/integration_tests.rs index e47f043bd67..e455a0b9d8f 100644 --- a/tests/tests/integration_tests.rs +++ b/tests/tests/integration_tests.rs @@ -32,7 +32,6 @@ use graph_tests::{error, status, CONFIG}; use tokio::process::Child; use tokio::task::JoinError; use tokio::time::sleep; -use web3::types::U256; const SUBGRAPH_LAST_GRAFTING_BLOCK: i32 = 3; @@ -44,7 +43,6 @@ type TestFn = Box< pub struct TestContext { pub subgraph: Subgraph, - pub contracts: Vec, } pub enum TestStatus { @@ -234,11 +232,7 @@ impl TestCase { Ok(subgraph_name) } - pub async fn check_health_and_test( - self, - contracts: &[Contract], - subgraph_name: String, - ) -> TestResult { + pub async fn check_health_and_test(self, subgraph_name: String) -> TestResult { status!( &self.name, "Waiting for subgraph ({}) to become ready", @@ -264,7 +258,6 @@ impl TestCase { let ctx = TestContext { subgraph: subgraph.clone(), - contracts: contracts.to_vec(), }; status!(&self.name, "Starting test"); @@ -323,7 +316,7 @@ impl TestCase { } }; - self.check_health_and_test(contracts, subgraph_name).await + self.check_health_and_test(subgraph_name).await } async fn prepare_multiple_sources( @@ -671,63 +664,7 @@ async fn test_topic_filters(ctx: TestContext) -> anyhow::Result<()> { let subgraph = ctx.subgraph; assert!(subgraph.healthy); - let contract = ctx - .contracts - .iter() - .find(|x| x.name == "SimpleContract") - .unwrap(); - - contract - .call( - "emitAnotherTrigger", - ( - U256::from(1), - U256::from(2), - U256::from(3), - "abc".to_string(), - ), - ) - .await - .unwrap(); - - contract - .call( - "emitAnotherTrigger", - ( - U256::from(1), - U256::from(1), - U256::from(1), - "abc".to_string(), - ), - ) - .await - .unwrap(); - - contract - .call( - "emitAnotherTrigger", - ( - U256::from(4), - U256::from(2), - U256::from(3), - "abc".to_string(), - ), - ) - .await - .unwrap(); - - contract - .call( - "emitAnotherTrigger", - ( - U256::from(4), - U256::from(4), - U256::from(3), - "abc".to_string(), - ), - ) - .await - .unwrap(); + // Events we need are already emitted from Contract::deploy_all let exp = json!({ "anotherTriggerEntities": [ @@ -1211,8 +1148,6 @@ pub async fn test_multiple_subgraph_datasources(ctx: TestContext) -> anyhow::Res let subgraph = ctx.subgraph; assert!(subgraph.healthy); - println!("subgraph: {:?}", subgraph); - // Test querying data aggregated from multiple sources let exp = json!({ "aggregatedDatas": [ From 3d51352f10291ddb48513b68c32bf866621c0d6b Mon Sep 17 00:00:00 2001 From: David Lutterkort Date: Wed, 28 Jan 2026 14:35:06 -0800 Subject: [PATCH 6/6] tests: Replace web3 with alloy for contract interactions Remove the web3 fork dependency (graphprotocol/rust-web3) and secp256k1 in favor of alloy, which is already used throughout the production code. This simplifies the dependency tree and uses modern Ethereum tooling: - Replace web3 Eth transport with alloy ProviderBuilder - Replace SecretKey with PrivateKeySigner - Replace Web3Contract with alloy ContractInstance - Use DynSolValue for encoding function parameters - Update Block type access patterns for alloy's API Fixes https://github.com/graphprotocol/graph-node/issues/5859 --- Cargo.lock | 257 ++++--------------------------- tests/Cargo.toml | 8 - tests/src/contract.rs | 236 +++++++++++++++------------- tests/tests/integration_tests.rs | 7 +- 4 files changed, 165 insertions(+), 343 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a0c04723ed1..921ffa9eb28 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -110,12 +110,12 @@ dependencies = [ "auto_impl", "borsh", "c-kzg", - "derive_more 2.1.1", + "derive_more", "either", "k256", "once_cell", "rand 0.8.5", - "secp256k1 0.30.0", + "secp256k1", "serde", "serde_json", "serde_with", @@ -252,7 +252,7 @@ dependencies = [ "auto_impl", "borsh", "c-kzg", - "derive_more 2.1.1", + "derive_more", "either", "serde", "serde_with", @@ -321,7 +321,7 @@ dependencies = [ "alloy-sol-types", "async-trait", "auto_impl", - "derive_more 2.1.1", + "derive_more", "futures-utils-wasm", "serde", "serde_json", @@ -352,7 +352,7 @@ dependencies = [ "bytes", "cfg-if 1.0.0", "const-hex", - "derive_more 2.1.1", + "derive_more", "foldhash 0.2.0", "hashbrown 0.16.1", "indexmap 2.11.4", @@ -534,7 +534,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bab1ebed118b701c497e6541d2d11dfa6f3c6ae31a3c52999daa802fcdcc16b7" dependencies = [ "alloy-primitives", - "derive_more 2.1.1", + "derive_more", "serde", "serde_with", ] @@ -551,7 +551,7 @@ dependencies = [ "alloy-rlp", "alloy-serde", "arbitrary", - "derive_more 2.1.1", + "derive_more", "rand 0.8.5", "serde", "strum", @@ -730,7 +730,7 @@ dependencies = [ "alloy-json-rpc", "auto_impl", "base64 0.22.1", - "derive_more 2.1.1", + "derive_more", "futures 0.3.31", "futures-utils-wasm", "parking_lot", @@ -775,7 +775,7 @@ dependencies = [ "serde", "serde_json", "tokio", - "tokio-util 0.7.18", + "tokio-util", "tracing", ] @@ -807,7 +807,7 @@ dependencies = [ "arbitrary", "arrayvec 0.7.4", "derive_arbitrary", - "derive_more 2.1.1", + "derive_more", "nybbles", "proptest", "proptest-derive 0.5.1", @@ -1429,7 +1429,7 @@ dependencies = [ "serde_json", "tokio", "tokio-stream", - "tokio-util 0.7.18", + "tokio-util", "tower-service 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1682,12 +1682,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" -[[package]] -name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - [[package]] name = "base64" version = "0.21.7" @@ -1827,15 +1821,6 @@ dependencies = [ "constant_time_eq 0.3.1", ] -[[package]] -name = "block-buffer" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" -dependencies = [ - "generic-array", -] - [[package]] name = "block-buffer" version = "0.10.4" @@ -2071,7 +2056,7 @@ dependencies = [ "memchr", "pin-project-lite", "tokio", - "tokio-util 0.7.18", + "tokio-util", ] [[package]] @@ -2146,12 +2131,6 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - [[package]] name = "convert_case" version = "0.10.0" @@ -2745,19 +2724,6 @@ dependencies = [ "syn 2.0.114", ] -[[package]] -name = "derive_more" -version = "0.99.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3da29a38df43d6f156149c9b43ded5e018ddff2a855cf2cfd62e8cd7d079c69f" -dependencies = [ - "convert_case 0.4.0", - "proc-macro2", - "quote", - "rustc_version 0.4.0", - "syn 2.0.114", -] - [[package]] name = "derive_more" version = "2.1.1" @@ -2773,7 +2739,7 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" dependencies = [ - "convert_case 0.10.0", + "convert_case", "proc-macro2", "quote", "rustc_version 0.4.0", @@ -2898,7 +2864,7 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer 0.10.4", + "block-buffer", "const-oid", "crypto-common", "subtle", @@ -3485,12 +3451,6 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" -[[package]] -name = "futures-timer" -version = "3.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" - [[package]] name = "futures-util" version = "0.3.31" @@ -3641,7 +3601,7 @@ dependencies = [ "pgtemp", "pq-sys", "tokio", - "tokio-util 0.7.18", + "tokio-util", ] [[package]] @@ -3727,7 +3687,7 @@ dependencies = [ "tokio", "tokio-retry", "tokio-stream", - "tokio-util 0.7.18", + "tokio-util", "toml 0.9.11+spec-1.1.0", "tonic", "tonic-prost", @@ -3815,7 +3775,7 @@ dependencies = [ "thiserror 2.0.18", "tokio", "tokio-retry", - "tokio-util 0.7.18", + "tokio-util", "tower 0.5.3", "tower-test", "wiremock", @@ -3867,7 +3827,7 @@ dependencies = [ "shellexpand", "termcolor", "tokio", - "tokio-util 0.7.18", + "tokio-util", "url", ] @@ -3962,7 +3922,7 @@ dependencies = [ "chrono", "clap", "deadpool", - "derive_more 2.1.1", + "derive_more", "diesel", "diesel-async", "diesel-derive-enum", @@ -4008,14 +3968,12 @@ dependencies = [ "graph-runtime-wasm", "graph-server-index-node", "graph-store-postgres", - "secp256k1 0.21.3", "serde", "serde_yaml", "slog", "tokio", "tokio-stream", - "tokio-util 0.7.18", - "web3", + "tokio-util", ] [[package]] @@ -4122,7 +4080,7 @@ dependencies = [ "indexmap 2.11.4", "slab", "tokio", - "tokio-util 0.7.18", + "tokio-util", "tracing", ] @@ -4141,7 +4099,7 @@ dependencies = [ "indexmap 2.11.4", "slab", "tokio", - "tokio-util 0.7.18", + "tokio-util", "tracing", ] @@ -4218,30 +4176,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "headers" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322106e6bd0cba2d5ead589ddb8150a13d7c4217cf80d7c4f682ca994ccc6aa9" -dependencies = [ - "base64 0.21.7", - "bytes", - "headers-core", - "http 1.4.0", - "httpdate", - "mime", - "sha1", -] - -[[package]] -name = "headers-core" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" -dependencies = [ - "http 1.4.0", -] - [[package]] name = "heck" version = "0.4.1" @@ -4674,17 +4608,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" -[[package]] -name = "idna" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" -dependencies = [ - "matches", - "unicode-bidi", - "unicode-normalization", -] - [[package]] name = "idna" version = "1.1.0" @@ -5286,12 +5209,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" -[[package]] -name = "matches" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" - [[package]] name = "matchit" version = "0.8.4" @@ -5643,12 +5560,6 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" -[[package]] -name = "opaque-debug" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" - [[package]] name = "openssl" version = "0.10.75" @@ -6599,7 +6510,7 @@ dependencies = [ "sha1_smol", "socket2 0.6.0", "tokio", - "tokio-util 0.7.18", + "tokio-util", "url", "xxhash-rust", ] @@ -6714,7 +6625,7 @@ dependencies = [ "tokio", "tokio-native-tls", "tokio-rustls", - "tokio-util 0.7.18", + "tokio-util", "tower 0.5.2", "tower-http", "tower-service 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -7011,15 +6922,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "secp256k1" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c42e6f1735c5f00f51e43e28d6634141f2bcad10931b2609ddd74a86d751260" -dependencies = [ - "secp256k1-sys 0.4.2", -] - [[package]] name = "secp256k1" version = "0.30.0" @@ -7028,19 +6930,10 @@ checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252" dependencies = [ "bitcoin_hashes", "rand 0.8.5", - "secp256k1-sys 0.10.1", + "secp256k1-sys", "serde", ] -[[package]] -name = "secp256k1-sys" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957da2573cde917463ece3570eab4a0b3f19de6f1646cde62e6fd3868f566036" -dependencies = [ - "cc", -] - [[package]] name = "secp256k1-sys" version = "0.10.1" @@ -7273,19 +7166,6 @@ dependencies = [ "serde", ] -[[package]] -name = "sha-1" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" -dependencies = [ - "block-buffer 0.9.0", - "cfg-if 1.0.0", - "cpufeatures", - "digest 0.9.0", - "opaque-debug", -] - [[package]] name = "sha1" version = "0.10.6" @@ -7493,21 +7373,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "soketto" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d1c5305e39e09653383c2c7244f2f78b3bcae37cf50c64cb4789c9f5096ec2" -dependencies = [ - "base64 0.13.1", - "bytes", - "futures 0.3.31", - "httparse", - "log", - "rand 0.8.5", - "sha-1", -] - [[package]] name = "spin" version = "0.9.8" @@ -8032,7 +7897,7 @@ dependencies = [ "rand 0.9.2", "socket2 0.6.0", "tokio", - "tokio-util 0.7.18", + "tokio-util", "whoami", ] @@ -8066,7 +7931,7 @@ dependencies = [ "futures-core", "pin-project-lite", "tokio", - "tokio-util 0.7.18", + "tokio-util", ] [[package]] @@ -8110,21 +7975,6 @@ dependencies = [ "tungstenite 0.28.0", ] -[[package]] -name = "tokio-util" -version = "0.6.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" -dependencies = [ - "bytes", - "futures-core", - "futures-io", - "futures-sink", - "log", - "pin-project-lite", - "tokio", -] - [[package]] name = "tokio-util" version = "0.7.18" @@ -8308,7 +8158,7 @@ dependencies = [ "slab", "sync_wrapper", "tokio", - "tokio-util 0.7.18", + "tokio-util", "tower-layer 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "tower-service 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "tracing", @@ -8327,7 +8177,7 @@ dependencies = [ "slab", "sync_wrapper", "tokio", - "tokio-util 0.7.18", + "tokio-util", "tower-layer 0.3.3 (git+https://github.com/tower-rs/tower.git)", "tower-service 0.3.3 (git+https://github.com/tower-rs/tower.git)", "tracing", @@ -8596,7 +8446,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", - "idna 1.1.0", + "idna", "percent-encoding", "serde", ] @@ -9179,53 +9029,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "web3" -version = "0.19.0-graph" -source = "git+https://github.com/graphprotocol/rust-web3?branch=graph-patches-onto-0.18#f9f27f45ce23bf489d8bd010b50b2b207eb316cb" -dependencies = [ - "arrayvec 0.7.4", - "base64 0.13.1", - "bytes", - "derive_more 0.99.19", - "ethabi", - "ethereum-types", - "futures 0.3.31", - "futures-timer", - "headers", - "hex", - "idna 0.2.3", - "jsonrpc-core", - "log", - "once_cell", - "parking_lot", - "pin-project", - "reqwest", - "rlp", - "secp256k1 0.21.3", - "serde", - "serde_json", - "soketto", - "tiny-keccak 2.0.2", - "tokio", - "tokio-stream", - "tokio-util 0.6.10", - "url", - "web3-async-native-tls", -] - -[[package]] -name = "web3-async-native-tls" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f6d8d1636b2627fe63518d5a9b38a569405d9c9bc665c43c9c341de57227ebb" -dependencies = [ - "native-tls", - "thiserror 1.0.61", - "tokio", - "url", -] - [[package]] name = "webpki-roots" version = "0.26.11" diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 955c8d80449..6de710f6561 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -20,15 +20,7 @@ serde = { workspace = true } serde_yaml = { workspace = true } slog = { workspace = true } tokio = { version = "1.49.0", features = ["rt", "macros", "process"] } -# Once graph upgrades to web3 0.19, we don't need this anymore. The version -# here needs to be kept in sync with the web3 version that the graph crate -# uses until then -secp256k1 = { version = "0.21", features = ["recovery"] } tokio-util.workspace = true -web3 = { git = "https://github.com/graphprotocol/rust-web3", branch = "graph-patches-onto-0.18", features = [ - "arbitrary_precision", - "test", -] } [dev-dependencies] anyhow = "1.0.100" diff --git a/tests/src/contract.rs b/tests/src/contract.rs index 82daa1b00f5..ffd92fe5e34 100644 --- a/tests/src/contract.rs +++ b/tests/src/contract.rs @@ -1,57 +1,81 @@ -use std::str::FromStr; - use graph::prelude::{ - lazy_static, + alloy::{ + contract::{ContractInstance, Interface}, + dyn_abi::DynSolValue, + json_abi::JsonAbi, + network::{Ethereum, TransactionBuilder}, + primitives::{Address, Bytes, U256}, + providers::{Provider, ProviderBuilder, WalletProvider}, + rpc::types::{Block, TransactionReceipt, TransactionRequest}, + signers::local::PrivateKeySigner, + }, + hex, lazy_static, serde_json::{self, Value}, }; -use web3::{ - api::{Eth, Namespace}, - contract::{tokens::Tokenize, Contract as Web3Contract, Options}, - transports::Http, - types::{Address, Block, BlockId, BlockNumber, Bytes, TransactionReceipt, H256, U256}, -}; -// web3 version 0.18 does not expose this; once the graph crate updates to -// version 0.19, we can use web3::signing::SecretKey from the graph crate -use secp256k1::SecretKey; - use crate::{error, helpers::TestFile, status, CONFIG}; // `FROM` and `FROM_KEY` are the address and private key of the first // account that anvil prints on startup lazy_static! { - static ref FROM: Address = - Address::from_str("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266").unwrap(); - static ref FROM_KEY: SecretKey = { - SecretKey::from_str("ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80") - .unwrap() - }; + static ref FROM: Address = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + .parse() + .unwrap(); + static ref FROM_KEY: PrivateKeySigner = + "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" + .parse() + .unwrap(); static ref CONTRACTS: Vec = { vec![ Contract { name: "SimpleContract".to_string(), - address: Address::from_str("0x5fbdb2315678afecb367f032d93f642f64180aa3").unwrap(), + address: "0x5fbdb2315678afecb367f032d93f642f64180aa3" + .parse() + .unwrap(), }, Contract { name: "LimitedContract".to_string(), - address: Address::from_str("0x0b306bf915c4d645ff596e518faf3f9669b97016").unwrap(), + address: "0x0b306bf915c4d645ff596e518faf3f9669b97016" + .parse() + .unwrap(), }, Contract { name: "RevertingContract".to_string(), - address: Address::from_str("0x959922be3caee4b8cd9a407cc3ac1c251c2007b1").unwrap(), + address: "0x959922be3caee4b8cd9a407cc3ac1c251c2007b1" + .parse() + .unwrap(), }, Contract { name: "OverloadedContract".to_string(), - address: Address::from_str("0x9a9f2ccfde556a7e9ff0848998aa4a0cfd8863ae").unwrap(), + address: "0x9a9f2ccfde556a7e9ff0848998aa4a0cfd8863ae" + .parse() + .unwrap(), }, Contract { name: "DeclaredCallsContract".to_string(), - address: Address::from_str("0x68b1d87f95878fe05b998f19b66f4baba5de1aed").unwrap(), + address: "0x68b1d87f95878fe05b998f19b66f4baba5de1aed" + .parse() + .unwrap(), }, ] }; } +/// Helper function to create a DynSolValue::Uint from a u64 value +pub fn uint(val: u64, bits: usize) -> DynSolValue { + DynSolValue::Uint(U256::from(val), bits) +} + +/// Helper function to create a DynSolValue::Address +pub fn addr(a: Address) -> DynSolValue { + DynSolValue::Address(a) +} + +/// Helper function to create a DynSolValue::String +pub fn string(s: &str) -> DynSolValue { + DynSolValue::String(s.to_string()) +} + #[derive(Debug, Clone)] pub struct Contract { pub name: String, @@ -59,59 +83,74 @@ pub struct Contract { } impl Contract { - fn eth() -> Eth { - let web3 = Http::new(&CONFIG.eth.url()).unwrap(); - Eth::new(web3) + fn provider() -> impl Provider + WalletProvider + Clone { + ProviderBuilder::new() + .wallet(FROM_KEY.clone()) + .connect_http(CONFIG.eth.url().parse().unwrap()) } async fn exists(&self) -> bool { let bytes = self.code().await; - !bytes.0.is_empty() + !bytes.is_empty() } pub async fn code(&self) -> Bytes { - let eth = Self::eth(); - eth.code(self.address, None).await.unwrap() + let provider = Self::provider(); + provider.get_code_at(self.address).await.unwrap() } - fn code_and_abi(name: &str) -> anyhow::Result<(String, Vec)> { + fn code_and_abi(name: &str) -> anyhow::Result<(Bytes, JsonAbi)> { let bin = TestFile::new(&format!("contracts/out/{}.sol/{}.json", name, name)); let json: Value = serde_json::from_reader(bin.reader()?).unwrap(); - let abi = serde_json::to_string(&json["abi"]).unwrap(); - let code = json["bytecode"]["object"].as_str().unwrap(); - Ok((code.to_string(), abi.as_bytes().to_vec())) + let abi: JsonAbi = serde_json::from_value(json["abi"].clone())?; + let code_hex = json["bytecode"]["object"].as_str().unwrap(); + let code = Bytes::from(hex::decode(code_hex.trim_start_matches("0x"))?); + Ok((code, abi)) } pub async fn deploy(name: &str) -> anyhow::Result { - let eth = Self::eth(); - let (code, abi) = Self::code_and_abi(name)?; - let contract = Web3Contract::deploy(eth, &abi) - .unwrap() - .confirmations(1) - .execute(code, (), FROM.to_owned()) - .await - .unwrap(); + let provider = Self::provider(); + let (code, _abi) = Self::code_and_abi(name)?; + + let deploy_tx = TransactionRequest::default().with_deploy_code(code); + + let receipt = provider + .send_transaction(deploy_tx) + .await? + .with_required_confirmations(1) + .get_receipt() + .await?; + + let address = receipt + .contract_address + .ok_or_else(|| anyhow::anyhow!("No contract address in receipt"))?; Ok(Self { name: name.to_string(), - address: contract.address(), + address, }) } pub async fn call( &self, func: &str, - params: impl Tokenize, + params: &[DynSolValue], ) -> anyhow::Result { - let eth = Self::eth(); + let provider = Self::provider(); let (_, abi) = Self::code_and_abi(&self.name)?; - let contract = Web3Contract::from_json(eth, self.address, &abi)?; - let options = Options::default(); + + let interface = Interface::new(abi); + let contract = ContractInstance::new(self.address, provider, interface); + let receipt = contract - .signed_call_with_confirmations(func, params, options, 1, &*FROM_KEY) - .await - .unwrap(); + .function(func, params)? + .send() + .await? + .with_required_confirmations(1) + .get_receipt() + .await?; + Ok(receipt) } @@ -147,50 +186,62 @@ impl Contract { // Some tests want 10 calls to `emitTrigger` in place if contract.name == "SimpleContract" { status!("contracts", "Calling SimpleContract.emitTrigger 10 times"); - for i in 1..=10 { - contract.call("emitTrigger", (i as u16,)).await.unwrap(); + for i in 1u64..=10 { + contract.call("emitTrigger", &[uint(i, 16)]).await.unwrap(); } } // Declared calls tests need a Transfer if contract.name == "DeclaredCallsContract" { status!("contracts", "Emitting transfers from DeclaredCallsContract"); - let addr1 = "0x1111111111111111111111111111111111111111" - .parse::() + let addr1: Address = "0x1111111111111111111111111111111111111111" + .parse() .unwrap(); - let addr2 = "0x2222222222222222222222222222222222222222" - .parse::() + let addr2: Address = "0x2222222222222222222222222222222222222222" + .parse() .unwrap(); - let addr3 = "0x3333333333333333333333333333333333333333" - .parse::() + let addr3: Address = "0x3333333333333333333333333333333333333333" + .parse() .unwrap(); - let addr4 = "0x4444444444444444444444444444444444444444" - .parse::() + let addr4: Address = "0x4444444444444444444444444444444444444444" + .parse() .unwrap(); contract - .call("emitTransfer", (addr1, addr2, 100u64)) + .call( + "emitTransfer", + &[addr(addr1), addr(addr2), uint(100u64, 256)], + ) .await .unwrap(); // Emit an asset transfer event to trigger struct field declared calls contract - .call("emitAssetTransfer", (addr1, 150u64, true, addr3)) + .call( + "emitAssetTransfer", + &[ + addr(addr1), + uint(150u64, 256), + DynSolValue::Bool(true), + addr(addr3), + ], + ) .await .unwrap(); // Also emit a complex asset event for nested struct testing - let values = vec![1u64, 2u64, 3u64]; + let values = + DynSolValue::Array(vec![uint(1u64, 256), uint(2u64, 256), uint(3u64, 256)]); contract .call( "emitComplexAssetCreated", - ( - addr4, - 250u64, - true, - "Complex Asset Metadata".to_string(), + &[ + addr(addr4), + uint(250u64, 256), + DynSolValue::Bool(true), + string("Complex Asset Metadata"), values, - 99u64, - ), + uint(99u64, 256), + ], ) .await .unwrap(); @@ -199,36 +250,13 @@ impl Contract { if contract.name == "SimpleContract" { status!("contracts", "Emitting anotherTrigger from SimpleContract"); let args = &[ - ( - U256::from(1), - U256::from(2), - U256::from(3), - "abc".to_string(), - ), - ( - U256::from(1), - U256::from(1), - U256::from(1), - "abc".to_string(), - ), - ( - U256::from(4), - U256::from(2), - U256::from(3), - "abc".to_string(), - ), - ( - U256::from(4), - U256::from(4), - U256::from(3), - "abc".to_string(), - ), + [uint(1, 256), uint(2, 256), uint(3, 256), string("abc")], + [uint(1, 256), uint(1, 256), uint(1, 256), string("abc")], + [uint(4, 256), uint(2, 256), uint(3, 256), string("abc")], + [uint(4, 256), uint(4, 256), uint(3, 256), string("abc")], ]; for arg in args { - contract - .call("emitAnotherTrigger", arg.clone()) - .await - .unwrap(); + contract.call("emitAnotherTrigger", arg).await.unwrap(); } } } else { @@ -244,12 +272,12 @@ impl Contract { Ok(contracts) } - pub async fn latest_block() -> Option> { - let eth = Self::eth(); - let block = eth - .block(BlockId::Number(BlockNumber::Latest)) + pub async fn latest_block() -> Option { + let provider = Self::provider(); + provider + .get_block_by_number(Default::default()) .await - .unwrap_or_default(); - block + .ok() + .flatten() } } diff --git a/tests/tests/integration_tests.rs b/tests/tests/integration_tests.rs index e455a0b9d8f..adaa1732024 100644 --- a/tests/tests/integration_tests.rs +++ b/tests/tests/integration_tests.rs @@ -1398,10 +1398,9 @@ async fn wait_for_blockchain_block(block_number: i32) -> bool { while start.elapsed() < STATUS_WAIT { let latest_block = Contract::latest_block().await; if let Some(latest_block) = latest_block { - if let Some(number) = latest_block.number { - if number >= block_number.into() { - return true; - } + let number = latest_block.header.number; + if number >= block_number as u64 { + return true; } } tokio::time::sleep(REQUEST_REPEATING).await;