diff --git a/Cargo.toml b/Cargo.toml index ab25e23..2cb3c48 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,12 +17,12 @@ lazy_static = "1.4.0" async-trait = "0.1.50" colored = "2" clap = "2.33.3" -serde_json = "1.0.59" run_script = "0.9" regex = "1.5.4" +serde = "1.0.188" +serde_json = "1.0.59" serde_yaml = "0.8.21" graphql-parser = "0.4.0" -serde = "1.0.139" [dev-dependencies] serial_test = "0.5.1" diff --git a/src/context/mod.rs b/src/context/mod.rs index 2f52a4d..fda2bb9 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -9,7 +9,10 @@ use graph::{ store::{Attribute, Value}, value::Word, }, - prelude::ethabi::{Address, Token}, + prelude::{ + ethabi::{Address, Token}, + DataSourceContext, + }, runtime::{asc_get, asc_new, gas::GasCounter, AscPtr, HostExportError}, semver::Version, }; @@ -34,9 +37,10 @@ use crate::SCHEMA_LOCATION; mod conversion; mod derived_schema; +mod template; use conversion::{collect_types, get_kind, get_token_value}; - use derived_schema::derive_schema; +use template::{data_source_create, populate_templates}; lazy_static! { /// Special tokens... @@ -96,6 +100,16 @@ impl ToEntity for StoreEntity { } } +type TemplateStore = HashMap>; + +#[derive(Debug, serde::Serialize)] +pub(crate) struct TemplateInfo { + kind: String, + name: String, + address: String, + context: Option, +} + /// The Matchstick Instance Context wraps WASM Instance Context and /// implements the external functions. pub struct MatchstickInstanceContext { @@ -138,6 +152,7 @@ pub struct MatchstickInstanceContext { /// Holds the mocked ipfs files in a HashMap, where key is the file hash, and the value is the /// path to the file that matchstick should read and parse pub(crate) ipfs: HashMap, + templates: TemplateStore, } /// Implementation of non-external functions. @@ -154,6 +169,7 @@ impl MatchstickInstanceContext { store_updated: true, data_source_return_value: (None, None, None), ipfs: HashMap::new(), + templates: HashMap::new(), }; // reads the graphql schema file and extracts all entities and their object types @@ -168,6 +184,7 @@ impl MatchstickInstanceContext { }); derive_schema(&mut context); + populate_templates(&mut context); context } @@ -207,10 +224,34 @@ impl MatchstickInstanceContext { /// function logStore(): void pub fn log_store(&mut self, _gas: &GasCounter) -> Result<(), HostExportError> { - logging::debug!( - "{}", - to_string_pretty(&self.store).unwrap_or_else(|err| logging::critical!(err)), - ); + let string_pretty = + to_string_pretty(&self.store).unwrap_or_else(|err| logging::critical!(err)); + logging::debug!(string_pretty); + + Ok(()) + } + + /// function logDataSources(template: string): void + pub fn log_data_sources( + &mut self, + _gas: &GasCounter, + template_ptr: AscPtr, + ) -> Result<(), HostExportError> { + let template: String = asc_get(&self.wasm_ctx, template_ptr, &GasCounter::new(), 0)?; + let data_sources = self + .templates + .get(&template) + .unwrap_or_else(|| panic!("No template with name '{}' found.", template)); + + let string_pretty = to_string_pretty(&data_sources).unwrap_or_else(|err| { + logging::critical!( + "Something went wrong when trying to convert data sources to string: {}", + err + ) + }); + + logging::debug!(string_pretty); + Ok(()) } @@ -455,6 +496,61 @@ impl MatchstickInstanceContext { Ok(true) } + pub fn assert_data_source_count( + &mut self, + _gas: &GasCounter, + template_name_ptr: AscPtr, + expected_count: u32, + ) -> Result { + let template_name: String = + asc_get(&self.wasm_ctx, template_name_ptr, &GasCounter::new(), 0)?; + + let actual_count = self + .templates + .get(&template_name) + .unwrap_or_else(|| panic!("No template with name '{}' found.", template_name)) + .len() as u32; + + if actual_count != expected_count { + logging::error!( + "(assert.dataSourceCount) Expected dataSource count for template `{}` to be '{}' but was '{}'", + template_name, + expected_count, + actual_count + ); + return Ok(false); + } + + Ok(true) + } + + pub fn assert_data_source_exists( + &mut self, + _gas: &GasCounter, + template_name_ptr: AscPtr, + address_ptr: AscPtr, + ) -> Result { + let template_name: String = + asc_get(&self.wasm_ctx, template_name_ptr, &GasCounter::new(), 0)?; + let address: String = asc_get(&self.wasm_ctx, address_ptr, &GasCounter::new(), 0)?; + + let template = self + .templates + .get(&template_name) + .unwrap_or_else(|| panic!("No template with name '{}' found.", template_name)); + + if !template.contains_key(&address) { + logging::error!( + "(assert.dataSourceExists) No dataSource with address '{}' found for template '{}'", + address, + template_name + ); + return Ok(false); + } + + Ok(true) + } + fn get_store_entity( &mut self, scope: StoreScope, @@ -865,7 +961,10 @@ impl MatchstickInstanceContext { _name_ptr: AscPtr, _params_ptr: AscPtr>>, ) -> Result<(), HostExportError> { - Ok(()) + let name: String = asc_get(&self.wasm_ctx, _name_ptr, &GasCounter::new(), 0)?; + let params: Vec = asc_get(&self.wasm_ctx, _params_ptr, &GasCounter::new(), 0)?; + + data_source_create(name, params, None, &mut self.templates) } /// function dataSource.createWithContext( @@ -879,7 +978,13 @@ impl MatchstickInstanceContext { _params_ptr: AscPtr>>, _context_ptr: AscPtr, ) -> Result<(), HostExportError> { - Ok(()) + let name: String = asc_get(&self.wasm_ctx, _name_ptr, &GasCounter::new(), 0)?; + let params: Vec = asc_get(&self.wasm_ctx, _params_ptr, &GasCounter::new(), 0)?; + let context: HashMap = + asc_get(&self.wasm_ctx, _context_ptr, &GasCounter::new(), 0)?; + let context = DataSourceContext::from(context); + + data_source_create(name, params, Some(context), &mut self.templates) } /// function dataSource.address(): Address diff --git a/src/context/template.rs b/src/context/template.rs new file mode 100644 index 0000000..3cea4b9 --- /dev/null +++ b/src/context/template.rs @@ -0,0 +1,59 @@ +use super::{MatchstickInstanceContext, TemplateInfo, TemplateStore}; +use anyhow::{Context as _, Result}; +use graph::{ + prelude::DataSourceContext, + runtime::{DeterministicHostError, HostExportError}, +}; +use std::collections::HashMap; + +pub(crate) fn populate_templates( + context: &mut MatchstickInstanceContext, +) { + crate::MANIFEST_LOCATION.with(|path| { + let names = crate::parser::collect_template_names( + path.borrow().to_str().expect("Cannot convert to string."), + ); + + names.iter().for_each(|name| { + context.templates.insert(name.to_string(), HashMap::new()); + }); + }); +} + +pub(crate) fn data_source_create( + name: String, + params: Vec, + context: Option, + templates: &mut TemplateStore, +) -> Result<(), HostExportError> { + // Resolve the name into the right template + templates + .iter() + .find(|template| template.0 == &name) + .with_context(|| { + format!( + "Failed to create data source from name `{}`: \ + No template with this name available. \ + Available names: {}.", + name, + templates + .iter() + .map(|template| template.0.to_owned()) + .collect::>() + .join(", ") + ) + }) + .map_err(DeterministicHostError::from)?; + + let template_info = TemplateInfo { + kind: "ethereum/contract".to_string(), + name: name.clone(), + address: params[0].clone(), + context, + }; + + let template = templates.get_mut(&name).expect("Template not found."); + template.insert(params[0].clone(), template_info); + + Ok(()) +} diff --git a/src/instance.rs b/src/instance.rs index e12275d..140e8c4 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -22,8 +22,7 @@ use graph_runtime_wasm::{ }; use wasmtime::Trap; -use crate::subgraph_store::MockSubgraphStore; -use crate::{context::MatchstickInstanceContext, logging}; +use crate::{context::MatchstickInstanceContext, logging, subgraph_store::MockSubgraphStore}; /// The Matchstick Instance is simply a wrapper around WASM Instance and pub struct MatchstickInstance { @@ -307,6 +306,9 @@ impl MatchstickInstance { link!("clearStore", clear_store,); link!("clearInBlockStore", clear_cache_store,); link!("logStore", log_store,); + + link!("logDataSources", log_data_sources, template_ptr); + link!( "logEntity", log_entity, @@ -507,6 +509,18 @@ impl MatchstickInstance { entity_type_ptr, id_ptr ); + link!( + "_assert.dataSourceCount", + assert_data_source_count, + template_ptr, + expected_count_ptr + ); + link!( + "_assert.dataSourceExists", + assert_data_source_exists, + template_ptr, + address_ptr + ); link!("countEntities", count_entities, entity_type); diff --git a/src/integration_tests.rs b/src/integration_tests.rs index 50c1218..331abcf 100644 --- a/src/integration_tests.rs +++ b/src/integration_tests.rs @@ -5,12 +5,15 @@ mod tests { use std::path::PathBuf; use crate::test_suite::{Test, TestGroup, Testable}; - use crate::{MatchstickInstance, SCHEMA_LOCATION}; + use crate::{MatchstickInstance, MANIFEST_LOCATION, SCHEMA_LOCATION}; #[test] #[serial] fn run_all_gravity_demo_subgraph_tests() { SCHEMA_LOCATION.with(|path| *path.borrow_mut() = PathBuf::from("./mocks/schema.graphql")); + MANIFEST_LOCATION + .with(|path| *path.borrow_mut() = PathBuf::from("./mocks/yamls/subgraph.yaml")); + let module = >::new("mocks/wasm/gravity.wasm"); let test_suite = TestGroup::from(&module); let mut failed_tests = Box::new(0); @@ -28,6 +31,9 @@ mod tests { #[serial] fn run_all_token_lock_wallet_demo_subgraph_tests() { SCHEMA_LOCATION.with(|path| *path.borrow_mut() = PathBuf::from("./mocks/schema.graphql")); + MANIFEST_LOCATION + .with(|path| *path.borrow_mut() = PathBuf::from("./mocks/yamls/subgraph.yaml")); + let module = >::new("mocks/wasm/token-lock-wallet.wasm"); let test_suite = TestGroup::from(&module); let mut failed_tests = Box::new(0); diff --git a/src/parser.rs b/src/parser.rs index 04109c1..e4cdf8b 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -93,6 +93,23 @@ pub fn collect_handlers(path: &str) -> HashMap> { .collect() } +pub fn collect_template_names(path: &str) -> Vec { + let subgraph_yaml = parse_yaml(path); + + extract_vec(&subgraph_yaml, "templates") + .iter() + .filter_map(|template| { + let kind = template.get("kind").unwrap().as_str().unwrap().to_owned(); + if kind == "ethereum/contract" { + let name = template.get("name").unwrap().as_str().unwrap().to_owned(); + Some(name) + } else { + None + } + }) + .collect() +} + /// Extracts the schema location from subraph.yaml /// Will panic if the `schema` or `file` key is missing pub fn get_schema_location(path: &str) -> String { diff --git a/src/unit_tests.rs b/src/unit_tests.rs index f89662c..516a513 100644 --- a/src/unit_tests.rs +++ b/src/unit_tests.rs @@ -3,7 +3,6 @@ mod tests { use std::collections::HashMap; use std::path::PathBuf; use std::str::FromStr; - use std::sync::Once; use graph::{ data::store::Value, @@ -20,22 +19,21 @@ mod tests { use crate::{ context::{asc_string_from_str, MatchstickInstanceContext, REVERTS_IDENTIFIER}, logging::{accum, flush, LOGS}, - {MatchstickInstance, SCHEMA_LOCATION}, + {MatchstickInstance, MANIFEST_LOCATION, SCHEMA_LOCATION}, }; - static GET_SCHEMA: Once = Once::new(); - fn get_context() -> MatchstickInstanceContext { - GET_SCHEMA.call_once(|| { - SCHEMA_LOCATION - .with(|path| *path.borrow_mut() = PathBuf::from("./mocks/schema.graphql")); + SCHEMA_LOCATION.with(|path| *path.borrow_mut() = PathBuf::from("./mocks/schema.graphql")); + + MANIFEST_LOCATION.with(|path| { + *path.borrow_mut() = PathBuf::from("./mocks/yamls/subgraph.yaml"); }); + let module = >::new("./mocks/wasm/gravity.wasm"); module .instance_ctx .take() - .take() .expect("Couldn't get context from module.") }