diff --git a/chain/ethereum/src/runtime/mod.rs b/chain/ethereum/src/runtime/mod.rs index 3f279473a6e..42f0a9dd10e 100644 --- a/chain/ethereum/src/runtime/mod.rs +++ b/chain/ethereum/src/runtime/mod.rs @@ -1,4 +1,4 @@ pub use runtime_adapter::RuntimeAdapter; pub mod abi; -mod runtime_adapter; +pub mod runtime_adapter; diff --git a/chain/ethereum/src/runtime/runtime_adapter.rs b/chain/ethereum/src/runtime/runtime_adapter.rs index 74af17cbc33..05b622afe90 100644 --- a/chain/ethereum/src/runtime/runtime_adapter.rs +++ b/chain/ethereum/src/runtime/runtime_adapter.rs @@ -197,7 +197,7 @@ fn eth_call( } #[derive(Clone, Debug)] -pub(crate) struct UnresolvedContractCall { +pub struct UnresolvedContractCall { pub contract_name: String, pub contract_address: Address, pub function_name: String, diff --git a/runtime/test/Cargo.toml b/runtime/test/Cargo.toml index 4cea65b8f16..0654a61498c 100644 --- a/runtime/test/Cargo.toml +++ b/runtime/test/Cargo.toml @@ -3,7 +3,7 @@ name = "graph-runtime-test" version = "0.23.1" edition = "2018" -[dev-dependencies] +[dependencies] semver = "1.0" wasmtime = "0.27.0" graph = { path = "../../graph" } diff --git a/runtime/test/src/common.rs b/runtime/test/src/common.rs new file mode 100644 index 00000000000..c9ffa7b0415 --- /dev/null +++ b/runtime/test/src/common.rs @@ -0,0 +1,127 @@ +use ethabi::Contract; +use graph::components::store::DeploymentLocator; +use graph::data::subgraph::*; +use graph::ipfs_client::IpfsClient; +use graph::prelude::*; +use graph_chain_ethereum::{Chain, DataSource, DataSourceTemplate}; +use graph_runtime_wasm::{HostExports, MappingContext}; +use semver::Version; +use std::str::FromStr; +use web3::types::Address; + +fn mock_host_exports( + subgraph_id: DeploymentHash, + data_source: DataSource, + store: Arc, + api_version: Version, +) -> HostExports { + let templates = vec![DataSourceTemplate { + kind: String::from("ethereum/contract"), + name: String::from("example template"), + network: Some(String::from("mainnet")), + source: TemplateSource { + abi: String::from("foo"), + }, + mapping: Mapping { + kind: String::from("ethereum/events"), + api_version, + language: String::from("wasm/assemblyscript"), + entities: vec![], + abis: vec![], + event_handlers: vec![], + call_handlers: vec![], + block_handlers: vec![], + link: Link { + link: "link".to_owned(), + }, + runtime: Arc::new(vec![]), + }, + }]; + + let network = data_source.network.clone().unwrap(); + HostExports::new( + subgraph_id, + &data_source, + network, + Arc::new(templates), + Arc::new(graph_core::LinkResolver::from(IpfsClient::localhost())), + store, + ) +} + +fn mock_abi() -> MappingABI { + MappingABI { + name: "mock_abi".to_string(), + contract: Contract::load( + r#"[ + { + "inputs": [ + { + "name": "a", + "type": "address" + } + ], + "type": "constructor" + } + ]"# + .as_bytes(), + ) + .unwrap(), + } +} + +pub fn mock_context( + deployment: DeploymentLocator, + data_source: DataSource, + store: Arc, + api_version: Version, +) -> MappingContext { + MappingContext { + logger: Logger::root(slog::Discard, o!()), + block_ptr: BlockPtr { + hash: Default::default(), + number: 0, + }, + host_exports: Arc::new(mock_host_exports( + deployment.hash.clone(), + data_source, + store.clone(), + api_version, + )), + state: BlockState::new(store.writable(&deployment).unwrap(), Default::default()), + proof_of_indexing: None, + host_fns: Arc::new(Vec::new()), + } +} + +pub fn mock_data_source(path: &str, api_version: Version) -> DataSource { + let runtime = std::fs::read(path).unwrap(); + + DataSource { + kind: String::from("ethereum/contract"), + name: String::from("example data source"), + network: Some(String::from("mainnet")), + source: Source { + address: Some(Address::from_str("0123123123012312312301231231230123123123").unwrap()), + abi: String::from("123123"), + start_block: 0, + }, + mapping: Mapping { + kind: String::from("ethereum/events"), + api_version, + language: String::from("wasm/assemblyscript"), + entities: vec![], + abis: vec![], + event_handlers: vec![], + call_handlers: vec![], + block_handlers: vec![], + link: Link { + link: "link".to_owned(), + }, + runtime: Arc::new(runtime.clone()), + }, + context: Default::default(), + creation_block: None, + contract_abi: Arc::new(mock_abi()), + } +} diff --git a/runtime/test/src/lib.rs b/runtime/test/src/lib.rs index 5710f755856..0853bb05ef6 100644 --- a/runtime/test/src/lib.rs +++ b/runtime/test/src/lib.rs @@ -1,4 +1,3 @@ -//! This crate is for tests only. - +pub mod common; #[cfg(test)] mod test; diff --git a/runtime/test/src/test.rs b/runtime/test/src/test.rs index 5083eb52f17..95692f13517 100644 --- a/runtime/test/src/test.rs +++ b/runtime/test/src/test.rs @@ -1,4 +1,3 @@ -use ethabi::Contract; use graph::data::store::scalar; use graph::data::subgraph::*; use graph::prelude::web3::types::U256; @@ -6,25 +5,31 @@ use graph::prelude::*; use graph::runtime::AscPtr; use graph::runtime::{asc_get, asc_new, try_asc_get}; use graph::{components::store::*, ipfs_client::IpfsClient}; -use graph_chain_ethereum::{Chain, DataSource, DataSourceTemplate}; -use graph_core; +use graph_chain_ethereum::{Chain, DataSource}; use graph_mock::MockMetricsRegistry; use graph_runtime_wasm::asc_abi::class::{Array, AscBigInt, AscEntity, AscString, Uint8Array}; -use graph_runtime_wasm::{ - ExperimentalFeatures, HostExports, MappingContext, ValidModule, WasmInstance, -}; +use graph_runtime_wasm::{ExperimentalFeatures, ValidModule, WasmInstance}; use hex; use semver::Version; use std::collections::{BTreeMap, HashMap}; use std::str::FromStr; use test_store::STORE; -use web3::types::{Address, H160}; +use web3::types::H160; + +use crate::common::{mock_context, mock_data_source}; mod abi; const API_VERSION_0_0_4: Version = Version::new(0, 0, 4); const API_VERSION_0_0_5: Version = Version::new(0, 0, 5); +fn wasm_file_path(wasm_file: &str, api_version: Version) -> String { + format!( + "wasm_test/api_version_{}_{}_{}/{}", + api_version.major, api_version.minor, api_version.patch, wasm_file + ) +} + fn subgraph_id_with_api_version(subgraph_id: &str, api_version: Version) -> String { format!( "{}_{}_{}_{}", @@ -113,127 +118,6 @@ fn test_module( test_valid_module_and_store(subgraph_id, data_source, api_version).0 } -fn mock_data_source(wasm_file: &str, api_version: Version) -> DataSource { - let path = format!( - "wasm_test/api_version_{}_{}_{}/{}", - api_version.major, api_version.minor, api_version.patch, wasm_file - ); - let runtime = std::fs::read(path).unwrap(); - - DataSource { - kind: String::from("ethereum/contract"), - name: String::from("example data source"), - network: Some(String::from("mainnet")), - source: Source { - address: Some(Address::from_str("0123123123012312312301231231230123123123").unwrap()), - abi: String::from("123123"), - start_block: 0, - }, - mapping: Mapping { - kind: String::from("ethereum/events"), - api_version, - language: String::from("wasm/assemblyscript"), - entities: vec![], - abis: vec![], - event_handlers: vec![], - call_handlers: vec![], - block_handlers: vec![], - link: Link { - link: "link".to_owned(), - }, - runtime: Arc::new(runtime.clone()), - }, - context: Default::default(), - creation_block: None, - contract_abi: Arc::new(mock_abi()), - } -} - -fn mock_abi() -> MappingABI { - MappingABI { - name: "mock_abi".to_string(), - contract: Contract::load( - r#"[ - { - "inputs": [ - { - "name": "a", - "type": "address" - } - ], - "type": "constructor" - } - ]"# - .as_bytes(), - ) - .unwrap(), - } -} - -fn mock_host_exports( - subgraph_id: DeploymentHash, - data_source: DataSource, - store: Arc, - api_version: Version, -) -> HostExports { - let templates = vec![DataSourceTemplate { - kind: String::from("ethereum/contract"), - name: String::from("example template"), - network: Some(String::from("mainnet")), - source: TemplateSource { - abi: String::from("foo"), - }, - mapping: Mapping { - kind: String::from("ethereum/events"), - api_version, - language: String::from("wasm/assemblyscript"), - entities: vec![], - abis: vec![], - event_handlers: vec![], - call_handlers: vec![], - block_handlers: vec![], - link: Link { - link: "link".to_owned(), - }, - runtime: Arc::new(vec![]), - }, - }]; - - let network = data_source.network.clone().unwrap(); - HostExports::new( - subgraph_id, - &data_source, - network, - Arc::new(templates), - Arc::new(graph_core::LinkResolver::from(IpfsClient::localhost())), - store, - ) -} - -fn mock_context( - deployment: DeploymentLocator, - data_source: DataSource, - store: Arc, - api_version: Version, -) -> MappingContext { - MappingContext { - logger: test_store::LOGGER.clone(), - block_ptr: BlockPtr { - hash: Default::default(), - number: 0, - }, - host_exports: Arc::new(mock_host_exports( - deployment.hash.clone(), - data_source, - store.clone(), - api_version, - )), - state: BlockState::new(store.writable(&deployment).unwrap(), Default::default()), - proof_of_indexing: None, - host_fns: Arc::new(Vec::new()), - } -} - trait WasmInstanceExt { fn invoke_export0(&self, f: &str); fn invoke_export(&self, f: &str, arg: AscPtr) -> AscPtr; @@ -291,7 +175,10 @@ impl WasmInstanceExt for WasmInstance { fn test_json_conversions(api_version: Version) { let mut module = test_module( "jsonConversions", - mock_data_source("string_to_number.wasm", api_version.clone()), + mock_data_source( + &wasm_file_path("string_to_number.wasm", api_version.clone()), + api_version.clone(), + ), api_version, ); @@ -337,7 +224,10 @@ async fn json_conversions_v0_0_5() { fn test_json_parsing(api_version: Version) { let mut module = test_module( "jsonParsing", - mock_data_source("json_parsing.wasm", api_version.clone()), + mock_data_source( + &wasm_file_path("json_parsing.wasm", api_version.clone()), + api_version.clone(), + ), api_version, ); @@ -379,7 +269,10 @@ async fn test_ipfs_cat(api_version: Version) { runtime.enter(|| { let mut module = test_module( "ipfsCat", - mock_data_source("ipfs_cat.wasm", api_version.clone()), + mock_data_source( + &wasm_file_path("ipfs_cat.wasm", api_version.clone()), + api_version.clone(), + ), api_version, ); let arg = asc_new(&mut module, &hash).unwrap(); @@ -449,7 +342,10 @@ async fn run_ipfs_map( runtime.enter(|| { let (mut module, _, _) = test_valid_module_and_store( &subgraph_id, - mock_data_source("ipfs_map.wasm", api_version.clone()), + mock_data_source( + &wasm_file_path("ipfs_map.wasm", api_version.clone()), + api_version.clone(), + ), api_version, ); let value = asc_new(&mut module, &hash).unwrap(); @@ -583,13 +479,16 @@ async fn test_ipfs_fail(api_version: Version) { runtime.enter(|| { let mut module = test_module( "ipfsFail", - mock_data_source("ipfs_cat.wasm", api_version.clone()), + mock_data_source( + &wasm_file_path("ipfs_cat.wasm", api_version.clone()), + api_version.clone(), + ), api_version, ); let hash = asc_new(&mut module, "invalid hash").unwrap(); assert!(module - .invoke_export::<_, AscString>("ipfsCat", hash,) + .invoke_export::<_, AscString>("ipfsCat", hash) .is_null()); }) }) @@ -610,7 +509,10 @@ async fn ipfs_fail_v0_0_5() { fn test_crypto_keccak256(api_version: Version) { let mut module = test_module( "cryptoKeccak256", - mock_data_source("crypto.wasm", api_version.clone()), + mock_data_source( + &wasm_file_path("crypto.wasm", api_version.clone()), + api_version.clone(), + ), api_version, ); let input: &[u8] = "eth".as_ref(); @@ -637,7 +539,10 @@ async fn crypto_keccak256_v0_0_5() { fn test_big_int_to_hex(api_version: Version) { let mut module = test_module( "BigIntToHex", - mock_data_source("big_int_to_hex.wasm", api_version.clone()), + mock_data_source( + &wasm_file_path("big_int_to_hex.wasm", api_version.clone()), + api_version.clone(), + ), api_version, ); @@ -679,7 +584,10 @@ async fn big_int_to_hex_v0_0_5() { fn test_big_int_arithmetic(api_version: Version) { let mut module = test_module( "BigIntArithmetic", - mock_data_source("big_int_arithmetic.wasm", api_version.clone()), + mock_data_source( + &wasm_file_path("big_int_arithmetic.wasm", api_version.clone()), + api_version.clone(), + ), api_version, ); @@ -751,7 +659,10 @@ async fn big_int_arithmetic_v0_0_5() { fn test_abort(api_version: Version, error_msg: &str) { let module = test_module( "abort", - mock_data_source("abort.wasm", api_version.clone()), + mock_data_source( + &wasm_file_path("abort.wasm", api_version.clone()), + api_version.clone(), + ), api_version, ); let res: Result<(), _> = module.get_func("abort").typed().unwrap().call(()); @@ -777,7 +688,10 @@ async fn abort_v0_0_5() { fn test_bytes_to_base58(api_version: Version) { let mut module = test_module( "bytesToBase58", - mock_data_source("bytes_to_base58.wasm", api_version.clone()), + mock_data_source( + &wasm_file_path("bytes_to_base58.wasm", api_version.clone()), + api_version.clone(), + ), api_version, ); let bytes = hex::decode("12207D5A99F603F231D53A4F39D1521F98D2E8BB279CF29BEBFD0687DC98458E7F89") @@ -805,7 +719,10 @@ fn test_data_source_create(api_version: Version) { -> Result>, wasmtime::Trap> { let mut module = test_module( "DataSourceCreate", - mock_data_source("data_source_create.wasm", api_version.clone()), + mock_data_source( + &wasm_file_path("data_source_create.wasm", api_version.clone()), + api_version.clone(), + ), api_version.clone(), ); @@ -851,7 +768,10 @@ async fn data_source_create_v0_0_5() { fn test_ens_name_by_hash(api_version: Version) { let mut module = test_module( "EnsNameByHash", - mock_data_source("ens_name_by_hash.wasm", api_version.clone()), + mock_data_source( + &wasm_file_path("ens_name_by_hash.wasm", api_version.clone()), + api_version.clone(), + ), api_version, ); @@ -882,7 +802,10 @@ async fn ens_name_by_hash_v0_0_5() { fn test_entity_store(api_version: Version) { let (mut module, store, deployment) = test_valid_module_and_store( "entityStore", - mock_data_source("store.wasm", api_version.clone()), + mock_data_source( + &wasm_file_path("store.wasm", api_version.clone()), + api_version.clone(), + ), api_version, ); @@ -979,10 +902,16 @@ async fn entity_store_v0_0_5() { } fn test_detect_contract_calls(api_version: Version) { - let data_source_without_calls = mock_data_source("abi_store_value.wasm", api_version.clone()); + let data_source_without_calls = mock_data_source( + &wasm_file_path("abi_store_value.wasm", api_version.clone()), + api_version.clone(), + ); assert_eq!(data_source_without_calls.mapping.requires_archive(), false); - let data_source_with_calls = mock_data_source("contract_calls.wasm", api_version); + let data_source_with_calls = mock_data_source( + &wasm_file_path("contract_calls.wasm", api_version.clone()), + api_version, + ); assert_eq!(data_source_with_calls.mapping.requires_archive(), true); } @@ -999,7 +928,10 @@ async fn detect_contract_calls_v0_0_5() { fn test_allocate_global(api_version: Version) { let module = test_module( "AllocateGlobal", - mock_data_source("allocate_global.wasm", api_version.clone()), + mock_data_source( + &wasm_file_path("allocate_global.wasm", api_version.clone()), + api_version.clone(), + ), api_version, ); diff --git a/runtime/test/src/test/abi.rs b/runtime/test/src/test/abi.rs index 6619e4bbdf7..4b3b7da7352 100644 --- a/runtime/test/src/test/abi.rs +++ b/runtime/test/src/test/abi.rs @@ -13,7 +13,10 @@ fn test_unbounded_loop(api_version: Version) { // Set handler timeout to 3 seconds. let module = test_valid_module_and_store_with_timeout( "unboundedLoop", - mock_data_source("non_terminating.wasm", api_version.clone()), + mock_data_source( + &wasm_file_path("non_terminating.wasm", api_version.clone()), + api_version.clone(), + ), api_version, Some(Duration::from_secs(3)), ) @@ -35,7 +38,10 @@ async fn unbounded_loop_v0_0_5() { fn test_unbounded_recursion(api_version: Version) { let module = test_module( "unboundedRecursion", - mock_data_source("non_terminating.wasm", api_version.clone()), + mock_data_source( + &wasm_file_path("non_terminating.wasm", api_version.clone()), + api_version.clone(), + ), api_version, ); let res: Result<(), _> = module.get_func("rabbit_hole").typed().unwrap().call(()); @@ -56,7 +62,10 @@ async fn unbounded_recursion_v0_0_5() { fn test_abi_array(api_version: Version) { let mut module = test_module( "abiArray", - mock_data_source("abi_classes.wasm", api_version.clone()), + mock_data_source( + &wasm_file_path("abi_classes.wasm", api_version.clone()), + api_version.clone(), + ), api_version, ); @@ -96,7 +105,10 @@ async fn abi_array_v0_0_5() { fn test_abi_subarray(api_version: Version) { let mut module = test_module( "abiSubarray", - mock_data_source("abi_classes.wasm", api_version.clone()), + mock_data_source( + &wasm_file_path("abi_classes.wasm", api_version.clone()), + api_version.clone(), + ), api_version, ); @@ -123,7 +135,10 @@ async fn abi_subarray_v0_0_5() { fn test_abi_bytes_and_fixed_bytes(api_version: Version) { let mut module = test_module( "abiBytesAndFixedBytes", - mock_data_source("abi_classes.wasm", api_version.clone()), + mock_data_source( + &wasm_file_path("abi_classes.wasm", api_version.clone()), + api_version.clone(), + ), api_version, ); let bytes1: Vec = vec![42, 45, 7, 245, 45]; @@ -154,7 +169,10 @@ async fn abi_bytes_and_fixed_bytes_v0_0_5() { fn test_abi_ethabi_token_identity(api_version: Version) { let mut module = test_module( "abiEthabiTokenIdentity", - mock_data_source("abi_token.wasm", api_version.clone()), + mock_data_source( + &wasm_file_path("abi_token.wasm", api_version.clone()), + api_version.clone(), + ), api_version, ); @@ -261,7 +279,10 @@ async fn abi_ethabi_token_identity_v0_0_5() { fn test_abi_store_value(api_version: Version) { let mut module = test_module( "abiStoreValue", - mock_data_source("abi_store_value.wasm", api_version.clone()), + mock_data_source( + &wasm_file_path("abi_store_value.wasm", api_version.clone()), + api_version.clone(), + ), api_version, ); @@ -367,7 +388,10 @@ async fn abi_store_value_v0_0_5() { fn test_abi_h160(api_version: Version) { let mut module = test_module( "abiH160", - mock_data_source("abi_classes.wasm", api_version.clone()), + mock_data_source( + &wasm_file_path("abi_classes.wasm", api_version.clone()), + api_version.clone(), + ), api_version, ); let address = H160::zero(); @@ -398,7 +422,10 @@ async fn abi_h160_v0_0_5() { fn test_string(api_version: Version) { let mut module = test_module( "string", - mock_data_source("abi_classes.wasm", api_version.clone()), + mock_data_source( + &wasm_file_path("abi_classes.wasm", api_version.clone()), + api_version.clone(), + ), api_version, ); let string = " 漢字Double_Me🇧🇷 "; @@ -422,7 +449,10 @@ async fn string_v0_0_5() { fn test_abi_big_int(api_version: Version) { let mut module = test_module( "abiBigInt", - mock_data_source("abi_classes.wasm", api_version.clone()), + mock_data_source( + &wasm_file_path("abi_classes.wasm", api_version.clone()), + api_version.clone(), + ), api_version, ); @@ -459,7 +489,10 @@ async fn abi_big_int_v0_0_5() { fn test_big_int_to_string(api_version: Version) { let mut module = test_module( "bigIntToString", - mock_data_source("big_int_to_string.wasm", api_version.clone()), + mock_data_source( + &wasm_file_path("big_int_to_string.wasm", api_version.clone()), + api_version.clone(), + ), api_version, ); @@ -484,7 +517,10 @@ async fn big_int_to_string_v0_0_5() { fn test_invalid_discriminant(api_version: Version) { let module = test_module( "invalidDiscriminant", - mock_data_source("abi_store_value.wasm", api_version.clone()), + mock_data_source( + &wasm_file_path("abi_store_value.wasm", api_version.clone()), + api_version.clone(), + ), api_version, ); diff --git a/runtime/wasm/src/host_exports.rs b/runtime/wasm/src/host_exports.rs index 9b069e4b497..1dc7d95148a 100644 --- a/runtime/wasm/src/host_exports.rs +++ b/runtime/wasm/src/host_exports.rs @@ -43,7 +43,7 @@ impl IntoTrap for HostExportError { pub struct HostExports { pub(crate) subgraph_id: DeploymentHash, - pub(crate) api_version: Version, + pub api_version: Version, data_source_name: String, data_source_address: Vec, data_source_network: String, diff --git a/runtime/wasm/src/module/mod.rs b/runtime/wasm/src/module/mod.rs index 91bd5c9f4c2..d6126366379 100644 --- a/runtime/wasm/src/module/mod.rs +++ b/runtime/wasm/src/module/mod.rs @@ -42,14 +42,14 @@ pub trait IntoTrap { /// Handle to a WASM instance, which is terminated if and only if this is dropped. pub struct WasmInstance { - instance: wasmtime::Instance, + pub instance: wasmtime::Instance, // This is the only reference to `WasmInstanceContext` that's not within the instance itself, so // we can always borrow the `RefCell` with no concern for race conditions. // // Also this is the only strong reference, so the instance will be dropped once this is dropped. // The weak references are circulary held by instance itself through host exports. - instance_ctx: Rc>>>, + pub instance_ctx: Rc>>>, } impl Drop for WasmInstance {