Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Make possible to test and log dynamic datasources #406

Merged
merged 7 commits into from
Sep 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
121 changes: 113 additions & 8 deletions src/context/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand All @@ -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...
Expand Down Expand Up @@ -96,6 +100,16 @@ impl ToEntity for StoreEntity {
}
}

type TemplateStore = HashMap<String, HashMap<String, TemplateInfo>>;

#[derive(Debug, serde::Serialize)]
pub(crate) struct TemplateInfo {
kind: String,
name: String,
address: String,
context: Option<DataSourceContext>,
}

/// The Matchstick Instance Context wraps WASM Instance Context and
/// implements the external functions.
pub struct MatchstickInstanceContext<C: Blockchain> {
Expand Down Expand Up @@ -138,6 +152,7 @@ pub struct MatchstickInstanceContext<C: Blockchain> {
/// 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<String, String>,
templates: TemplateStore,
}

/// Implementation of non-external functions.
Expand All @@ -154,6 +169,7 @@ impl<C: Blockchain> MatchstickInstanceContext<C> {
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
Expand All @@ -168,6 +184,7 @@ impl<C: Blockchain> MatchstickInstanceContext<C> {
});

derive_schema(&mut context);
populate_templates(&mut context);
context
}

Expand Down Expand Up @@ -207,10 +224,34 @@ impl<C: Blockchain> MatchstickInstanceContext<C> {

/// 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<AscString>,
) -> 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(())
}

Expand Down Expand Up @@ -455,6 +496,61 @@ impl<C: Blockchain> MatchstickInstanceContext<C> {
Ok(true)
}

pub fn assert_data_source_count(
&mut self,
_gas: &GasCounter,
template_name_ptr: AscPtr<AscString>,
expected_count: u32,
) -> Result<bool, HostExportError> {
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<AscString>,
address_ptr: AscPtr<AscString>,
) -> Result<bool, HostExportError> {
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,
Expand Down Expand Up @@ -865,7 +961,10 @@ impl<C: Blockchain> MatchstickInstanceContext<C> {
_name_ptr: AscPtr<AscString>,
_params_ptr: AscPtr<Array<AscPtr<AscString>>>,
) -> Result<(), HostExportError> {
Ok(())
let name: String = asc_get(&self.wasm_ctx, _name_ptr, &GasCounter::new(), 0)?;
let params: Vec<String> = asc_get(&self.wasm_ctx, _params_ptr, &GasCounter::new(), 0)?;

data_source_create(name, params, None, &mut self.templates)
}

/// function dataSource.createWithContext(
Expand All @@ -879,7 +978,13 @@ impl<C: Blockchain> MatchstickInstanceContext<C> {
_params_ptr: AscPtr<Array<AscPtr<AscString>>>,
_context_ptr: AscPtr<AscEntity>,
) -> Result<(), HostExportError> {
Ok(())
let name: String = asc_get(&self.wasm_ctx, _name_ptr, &GasCounter::new(), 0)?;
let params: Vec<String> = asc_get(&self.wasm_ctx, _params_ptr, &GasCounter::new(), 0)?;
let context: HashMap<Word, Value> =
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
Expand Down
59 changes: 59 additions & 0 deletions src/context/template.rs
Original file line number Diff line number Diff line change
@@ -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<C: graph::blockchain::Blockchain>(
context: &mut MatchstickInstanceContext<C>,
) {
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<String>,
context: Option<DataSourceContext>,
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::<Vec<_>>()
.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(())
}
18 changes: 16 additions & 2 deletions src/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<C: Blockchain> {
Expand Down Expand Up @@ -307,6 +306,9 @@ impl<C: Blockchain> MatchstickInstance<C> {
link!("clearStore", clear_store,);
link!("clearInBlockStore", clear_cache_store,);
link!("logStore", log_store,);

link!("logDataSources", log_data_sources, template_ptr);

link!(
"logEntity",
log_entity,
Expand Down Expand Up @@ -507,6 +509,18 @@ impl<C: Blockchain> MatchstickInstance<C> {
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);

Expand Down
8 changes: 7 additions & 1 deletion src/integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 = <MatchstickInstance<Chain>>::new("mocks/wasm/gravity.wasm");
let test_suite = TestGroup::from(&module);
let mut failed_tests = Box::new(0);
Expand All @@ -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 = <MatchstickInstance<Chain>>::new("mocks/wasm/token-lock-wallet.wasm");
let test_suite = TestGroup::from(&module);
let mut failed_tests = Box::new(0);
Expand Down
17 changes: 17 additions & 0 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,23 @@ pub fn collect_handlers(path: &str) -> HashMap<String, Vec<String>> {
.collect()
}

pub fn collect_template_names(path: &str) -> Vec<String> {
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 {
Expand Down
14 changes: 6 additions & 8 deletions src/unit_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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<Chain> {
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 = <MatchstickInstance<Chain>>::new("./mocks/wasm/gravity.wasm");

module
.instance_ctx
.take()
.take()
.expect("Couldn't get context from module.")
}

Expand Down