From c27aac4cbad7bd1ada09b1cfd5893b2ba1d75b97 Mon Sep 17 00:00:00 2001 From: Anton Rusev <103100324+arrusev@users.noreply.github.com> Date: Wed, 13 Sep 2023 13:10:08 +0300 Subject: [PATCH] Add ipfs data source (#409) * feat: add ipfs data source Signed-off-by: Anton Rusev * chore: fix fmt and clippy Signed-off-by: Anton Rusev --------- Signed-off-by: Anton Rusev --- src/context/mod.rs | 60 +++++++++++++++++++++++++++++++---------- src/context/template.rs | 37 ++++++++++++++++--------- src/instance.rs | 1 + src/parser.rs | 7 ++--- 4 files changed, 76 insertions(+), 29 deletions(-) diff --git a/src/context/mod.rs b/src/context/mod.rs index e54788d..cdbccbf 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -153,6 +153,7 @@ pub struct MatchstickInstanceContext { /// path to the file that matchstick should read and parse pub(crate) ipfs: HashMap, templates: TemplateStore, + template_kinds: HashMap, } /// Implementation of non-external functions. @@ -170,6 +171,7 @@ impl MatchstickInstanceContext { data_source_return_value: (None, None, None), ipfs: HashMap::new(), templates: HashMap::new(), + template_kinds: HashMap::new(), }; // reads the graphql schema file and extracts all entities and their object types @@ -1155,7 +1157,7 @@ impl MatchstickInstanceContext { 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) + data_source_create(name, params, None, self) } /// function dataSource.createWithContext( @@ -1175,7 +1177,7 @@ impl MatchstickInstanceContext { 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) + data_source_create(name, params, Some(context), self) } /// function dataSource.address(): Address @@ -1184,19 +1186,33 @@ impl MatchstickInstanceContext { _gas: &GasCounter, ) -> Result, HostExportError> { let default_address_val = "0x0000000000000000000000000000000000000000"; + let result = match &self.data_source_return_value.0 { - Some(value) => asc_new( - &mut self.wasm_ctx, - &Address::from_str(value).expect("Couldn't create Address."), - &GasCounter::new(), - ) - .expect("Couldn't create pointer."), - None => asc_new( - &mut self.wasm_ctx, - &Address::from_str(default_address_val).expect("Couldn't create Address."), - &GasCounter::new(), - ) - .expect("Couldn't create pointer."), + Some(value) => { + let address = Address::from_str(value).unwrap_or_default(); + // checks whether the value is a valid ethereum address and parses it + // otherwise it is considered as ipfs cid + // Zero address is considered as valid only if matches the mocked value + if !address.is_zero() || value.eq(default_address_val) { + asc_new(&mut self.wasm_ctx, &address, &GasCounter::new()) + .expect("Couldn't create pointer.") + } else { + asc_new(&mut self.wasm_ctx, value.as_bytes(), &GasCounter::new()) + .expect("Couldn't create pointer.") + } + } + None => { + logging::error!( + "No mocked Eth address or Ipfs CID found, so fallback to Eth Zero address" + ); + + asc_new( + &mut self.wasm_ctx, + &Address::from_str(default_address_val).expect("Couldn't create address"), + &GasCounter::new(), + ) + .expect("Couldn't create pointer.") + } }; Ok(result) @@ -1281,6 +1297,22 @@ impl MatchstickInstanceContext { } } + /// function readFile(path: string): Bytes + pub fn read_file( + &mut self, + _gas: &GasCounter, + file_path_ptr: AscPtr, + ) -> Result, HostExportError> { + let file_path: String = asc_get(&self.wasm_ctx, file_path_ptr, &GasCounter::new(), 0)?; + + let string = std::fs::read_to_string(&file_path).unwrap_or_else(|err| { + logging::critical!("Failed to read file `{}` with error: {}", &file_path, err) + }); + let result = asc_new(&mut self.wasm_ctx, string.as_bytes(), &GasCounter::new())?; + + Ok(result) + } + /// function mockIpfsFile(hash: string, file_path: string): void pub fn mock_ipfs_file( &mut self, diff --git a/src/context/template.rs b/src/context/template.rs index 3cea4b9..765e5d3 100644 --- a/src/context/template.rs +++ b/src/context/template.rs @@ -1,33 +1,37 @@ -use super::{MatchstickInstanceContext, TemplateInfo, TemplateStore}; +use std::collections::HashMap; + +use super::{MatchstickInstanceContext, TemplateInfo}; 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( + let templates = crate::parser::collect_templates( path.borrow().to_str().expect("Cannot convert to string."), ); - names.iter().for_each(|name| { - context.templates.insert(name.to_string(), HashMap::new()); + templates.iter().for_each(|(name, kind)| { + context + .template_kinds + .insert(name.to_string(), kind.to_string()); }); }); } -pub(crate) fn data_source_create( +pub(crate) fn data_source_create( name: String, params: Vec, context: Option, - templates: &mut TemplateStore, + instance_ctx: &mut MatchstickInstanceContext, ) -> Result<(), HostExportError> { // Resolve the name into the right template - templates + instance_ctx + .template_kinds .iter() .find(|template| template.0 == &name) .with_context(|| { @@ -36,7 +40,8 @@ pub(crate) fn data_source_create( No template with this name available. \ Available names: {}.", name, - templates + instance_ctx + .template_kinds .iter() .map(|template| template.0.to_owned()) .collect::>() @@ -45,15 +50,23 @@ pub(crate) fn data_source_create( }) .map_err(DeterministicHostError::from)?; + let kind = instance_ctx.template_kinds.get(&name).unwrap(); + let template_info = TemplateInfo { - kind: "ethereum/contract".to_string(), + kind: kind.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); + if instance_ctx.templates.contains_key(&name) { + let template = instance_ctx.templates.get_mut(&name).unwrap(); + template.insert(params[0].clone(), template_info); + } else { + let mut template: HashMap = HashMap::new(); + template.insert(params[0].clone(), template_info); + instance_ctx.templates.insert(name.clone(), template); + } Ok(()) } diff --git a/src/instance.rs b/src/instance.rs index 8052ac7..6d4943b 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -359,6 +359,7 @@ impl MatchstickInstance { ); link!("mockIpfsFile", mock_ipfs_file, hash, file_path); + link!("readFile", read_file, file_path); link!("ipfs.cat", mock_ipfs_cat, "host_export_ipfs_cat", hash_ptr); link!( diff --git a/src/parser.rs b/src/parser.rs index e4cdf8b..d3b946b 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -93,16 +93,17 @@ pub fn collect_handlers(path: &str) -> HashMap> { .collect() } -pub fn collect_template_names(path: &str) -> Vec { +pub fn collect_templates(path: &str) -> Vec<(String, 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" { + + if kind == "ethereum/contract" || kind == "file/ipfs" { let name = template.get("name").unwrap().as_str().unwrap().to_owned(); - Some(name) + Some((name, kind)) } else { None }