Skip to content

Commit

Permalink
Add ipfs data source (#409)
Browse files Browse the repository at this point in the history
* feat: add ipfs data source

Signed-off-by: Anton Rusev <[email protected]>

* chore: fix fmt and clippy

Signed-off-by: Anton Rusev <[email protected]>

---------

Signed-off-by: Anton Rusev <[email protected]>
  • Loading branch information
arrusev authored Sep 13, 2023
1 parent b3af1bd commit 5ea8548
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 29 deletions.
60 changes: 46 additions & 14 deletions src/context/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ pub struct MatchstickInstanceContext<C: Blockchain> {
/// path to the file that matchstick should read and parse
pub(crate) ipfs: HashMap<String, String>,
templates: TemplateStore,
template_kinds: HashMap<String, String>,
}

/// Implementation of non-external functions.
Expand All @@ -170,6 +171,7 @@ impl<C: Blockchain> MatchstickInstanceContext<C> {
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
Expand Down Expand Up @@ -1155,7 +1157,7 @@ impl<C: Blockchain> MatchstickInstanceContext<C> {
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)
data_source_create(name, params, None, self)
}

/// function dataSource.createWithContext(
Expand All @@ -1175,7 +1177,7 @@ impl<C: Blockchain> MatchstickInstanceContext<C> {
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
Expand All @@ -1184,19 +1186,33 @@ impl<C: Blockchain> MatchstickInstanceContext<C> {
_gas: &GasCounter,
) -> Result<AscPtr<Uint8Array>, 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)
Expand Down Expand Up @@ -1281,6 +1297,22 @@ impl<C: Blockchain> MatchstickInstanceContext<C> {
}
}

/// function readFile(path: string): Bytes
pub fn read_file(
&mut self,
_gas: &GasCounter,
file_path_ptr: AscPtr<AscString>,
) -> Result<AscPtr<Uint8Array>, 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,
Expand Down
37 changes: 25 additions & 12 deletions src/context/template.rs
Original file line number Diff line number Diff line change
@@ -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<C: graph::blockchain::Blockchain>(
context: &mut MatchstickInstanceContext<C>,
) {
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<C: graph::blockchain::Blockchain>(
name: String,
params: Vec<String>,
context: Option<DataSourceContext>,
templates: &mut TemplateStore,
instance_ctx: &mut MatchstickInstanceContext<C>,
) -> Result<(), HostExportError> {
// Resolve the name into the right template
templates
instance_ctx
.template_kinds
.iter()
.find(|template| template.0 == &name)
.with_context(|| {
Expand All @@ -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::<Vec<_>>()
Expand All @@ -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<String, TemplateInfo> = HashMap::new();
template.insert(params[0].clone(), template_info);
instance_ctx.templates.insert(name.clone(), template);
}

Ok(())
}
1 change: 1 addition & 0 deletions src/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,7 @@ impl<C: Blockchain> MatchstickInstance<C> {
);

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!(
Expand Down
7 changes: 4 additions & 3 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,16 +93,17 @@ pub fn collect_handlers(path: &str) -> HashMap<String, Vec<String>> {
.collect()
}

pub fn collect_template_names(path: &str) -> Vec<String> {
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
}
Expand Down

0 comments on commit 5ea8548

Please sign in to comment.