diff --git a/Cargo.lock b/Cargo.lock index 994064a4726..c7dd2e149c5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1648,6 +1648,7 @@ dependencies = [ "lazy_static", "mockall 0.9.1", "pretty_assertions", + "semver 1.0.3", "serde", "state_machine_future", "test-store", diff --git a/chain/ethereum/Cargo.toml b/chain/ethereum/Cargo.toml index fa3a7021e0a..9fac3b107d0 100644 --- a/chain/ethereum/Cargo.toml +++ b/chain/ethereum/Cargo.toml @@ -17,6 +17,7 @@ dirs-next = "2.0" anyhow = "1.0" tiny-keccak = "1.5.0" hex = "0.4.3" +semver = "1.0.3" # master contains changes such as # https://github.com/paritytech/ethabi/pull/140, which upstream does not want diff --git a/chain/ethereum/src/runtime/abi.rs b/chain/ethereum/src/runtime/abi.rs index d26cd2d2735..204012cd7bb 100644 --- a/chain/ethereum/src/runtime/abi.rs +++ b/chain/ethereum/src/runtime/abi.rs @@ -1,10 +1,12 @@ use graph::prelude::BigInt; use graph::runtime::{asc_get, asc_new, AscPtr, DeterministicHostError, FromAscObj, ToAscObj}; -use graph::runtime::{AscHeap, AscType}; +use graph::runtime::{AscHeap, AscIndexId, AscType, IndexForAscTypeId}; use graph_runtime_derive::AscType; use graph_runtime_wasm::asc_abi::class::{ Array, AscAddress, AscBigInt, AscEnum, AscH160, AscString, EthereumValueKind, Uint8Array, }; +use semver::Version; +use std::mem::size_of; use crate::trigger::{ EthereumBlockData, EthereumCallData, EthereumEventData, EthereumTransactionData, @@ -13,7 +15,35 @@ use crate::trigger::{ use super::runtime_adapter::UnresolvedContractCall; type AscH256 = Uint8Array; -type AscLogParamArray = Array>; + +pub struct AscLogParamArray(Array>); + +impl AscType for AscLogParamArray { + fn to_asc_bytes(&self) -> Result, DeterministicHostError> { + self.0.to_asc_bytes() + } + fn from_asc_bytes( + asc_obj: &[u8], + api_version: Version, + ) -> Result { + Ok(Self(Array::from_asc_bytes(asc_obj, api_version)?)) + } +} + +impl ToAscObj for Vec { + fn to_asc_obj( + &self, + heap: &mut H, + ) -> Result { + let content: Result, _> = self.iter().map(|x| asc_new(heap, x)).collect(); + let content = content?; + Ok(AscLogParamArray(Array::new(&*content, heap)?)) + } +} + +impl AscIndexId for AscLogParamArray { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::ArrayEventParam; +} #[repr(C)] #[derive(AscType)] @@ -25,6 +55,10 @@ pub struct AscUnresolvedContractCall_0_0_4 { pub function_args: AscPtr>>>, } +impl AscIndexId for AscUnresolvedContractCall_0_0_4 { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::SmartContractCall; +} + impl FromAscObj for UnresolvedContractCall { fn from_asc_obj( asc_call: AscUnresolvedContractCall_0_0_4, @@ -83,6 +117,10 @@ pub(crate) struct AscEthereumBlock { pub size: AscPtr, } +impl AscIndexId for AscEthereumBlock { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::EthereumBlock; +} + #[repr(C)] #[derive(AscType)] pub(crate) struct AscEthereumTransaction_0_0_1 { @@ -95,6 +133,10 @@ pub(crate) struct AscEthereumTransaction_0_0_1 { pub gas_price: AscPtr, } +impl AscIndexId for AscEthereumTransaction_0_0_1 { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::EthereumTransaction; +} + #[repr(C)] #[derive(AscType)] pub(crate) struct AscEthereumTransaction_0_0_2 { @@ -108,6 +150,10 @@ pub(crate) struct AscEthereumTransaction_0_0_2 { pub input: AscPtr, } +impl AscIndexId for AscEthereumTransaction_0_0_2 { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::EthereumTransaction; +} + #[repr(C)] #[derive(AscType)] pub(crate) struct AscEthereumEvent @@ -123,6 +169,14 @@ where pub params: AscPtr, } +impl AscIndexId for AscEthereumEvent { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::EthereumEvent; +} + +impl AscIndexId for AscEthereumEvent { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::EthereumEvent; +} + #[repr(C)] #[derive(AscType)] pub(crate) struct AscLogParam { @@ -130,6 +184,10 @@ pub(crate) struct AscLogParam { pub value: AscPtr>, } +impl AscIndexId for AscLogParam { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::EventParam; +} + #[repr(C)] #[derive(AscType)] pub(crate) struct AscEthereumCall { @@ -140,6 +198,10 @@ pub(crate) struct AscEthereumCall { pub outputs: AscPtr, } +impl AscIndexId for AscEthereumCall { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::EthereumCall; +} + #[repr(C)] #[derive(AscType)] pub(crate) struct AscEthereumCall_0_0_3 { @@ -151,6 +213,10 @@ pub(crate) struct AscEthereumCall_0_0_3 { pub outputs: AscPtr, } +impl AscIndexId for AscEthereumCall_0_0_3 { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::EthereumCall; +} + impl ToAscObj for EthereumBlockData { fn to_asc_obj( &self, @@ -219,7 +285,7 @@ impl ToAscObj for EthereumTransactionData { } } -impl ToAscObj> for EthereumEventData +impl ToAscObj> for EthereumEventData where EthereumTransactionData: ToAscObj, { @@ -241,7 +307,7 @@ where .unwrap_or(Ok(AscPtr::null()))?, block: asc_new(heap, &self.block)?, transaction: asc_new::(heap, &self.transaction)?, - params: asc_new(heap, self.params.as_slice())?, + params: asc_new(heap, &self.params)?, }) } } @@ -255,8 +321,8 @@ impl ToAscObj for EthereumCallData { address: asc_new(heap, &self.to)?, block: asc_new(heap, &self.block)?, transaction: asc_new(heap, &self.transaction)?, - inputs: asc_new(heap, self.inputs.as_slice())?, - outputs: asc_new(heap, self.outputs.as_slice())?, + inputs: asc_new(heap, &self.inputs)?, + outputs: asc_new(heap, &self.outputs)?, }) } } @@ -271,8 +337,8 @@ impl ToAscObj for EthereumCallData { from: asc_new(heap, &self.from)?, block: asc_new(heap, &self.block)?, transaction: asc_new(heap, &self.transaction)?, - inputs: asc_new(heap, self.inputs.as_slice())?, - outputs: asc_new(heap, self.outputs.as_slice())?, + inputs: asc_new(heap, &self.inputs)?, + outputs: asc_new(heap, &self.outputs)?, }) } } diff --git a/chain/ethereum/src/runtime/runtime_adapter.rs b/chain/ethereum/src/runtime/runtime_adapter.rs index ca7cb58d0c6..ab669c41f33 100644 --- a/chain/ethereum/src/runtime/runtime_adapter.rs +++ b/chain/ethereum/src/runtime/runtime_adapter.rs @@ -7,6 +7,7 @@ use crate::{ use anyhow::{Context, Error}; use blockchain::HostFn; use ethabi::{Address, Token}; +use graph::runtime::{AscIndexId, IndexForAscTypeId}; use graph::{ blockchain::{self, BlockPtr, DataSource as _, HostFnCtx}, cheap_clone::CheapClone, @@ -204,3 +205,7 @@ pub(crate) struct UnresolvedContractCall { pub function_signature: Option, pub function_args: Vec, } + +impl AscIndexId for AscUnresolvedContractCall { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::SmartContractCall; +} diff --git a/graph/src/data/subgraph/mod.rs b/graph/src/data/subgraph/mod.rs index 051df6f6248..2a60db9781d 100644 --- a/graph/src/data/subgraph/mod.rs +++ b/graph/src/data/subgraph/mod.rs @@ -57,6 +57,11 @@ lazy_static! { // doesn't exist. In the future we should not use 0.0.3 as version // and skip to 0.0.4 to avoid ambiguity. static ref MAX_SPEC_VERSION: Version = Version::new(0, 0, 3); + + static ref MAX_API_VERSION: Version = std::env::var("GRAPH_MAX_API_VERSION") + .ok() + .and_then(|api_version_str| Version::parse(&api_version_str).ok()) + .unwrap_or(Version::new(0, 0, 4)); } /// Rust representation of the GraphQL schema for a `SubgraphManifest`. @@ -604,8 +609,12 @@ impl UnresolvedMapping { let api_version = Version::parse(&api_version)?; - ensure!(VersionReq::parse("<= 0.0.4").unwrap().matches(&api_version), - "The maximum supported mapping API version of this indexer is 0.0.4, but `{}` was found", + ensure!( + VersionReq::parse(&format!("<= {}", *MAX_API_VERSION)) + .unwrap() + .matches(&api_version), + "The maximum supported mapping API version of this indexer is {}, but `{}` was found", + *MAX_API_VERSION, api_version ); diff --git a/graph/src/runtime/asc_heap.rs b/graph/src/runtime/asc_heap.rs index 74e8c48bf87..1941b72fb23 100644 --- a/graph/src/runtime/asc_heap.rs +++ b/graph/src/runtime/asc_heap.rs @@ -1,6 +1,6 @@ use semver::Version; -use super::{AscPtr, AscType, DeterministicHostError}; +use super::{AscIndexId, AscPtr, AscType, DeterministicHostError, IndexForAscTypeId}; /// A type that can read and write to the Asc heap. Call `asc_new` and `asc_get` /// for reading and writing Rust structs from and to Asc. /// @@ -12,6 +12,11 @@ pub trait AscHeap { fn get(&self, offset: u32, size: u32) -> Result, DeterministicHostError>; fn api_version(&self) -> Version; + + fn asc_type_id( + &mut self, + type_id_index: IndexForAscTypeId, + ) -> Result; } /// Instantiate `rust_obj` as an Asc object of class `C`. @@ -24,7 +29,7 @@ pub fn asc_new( rust_obj: &T, ) -> Result, DeterministicHostError> where - C: AscType, + C: AscType + AscIndexId, T: ToAscObj, { let obj = rust_obj.to_asc_obj(heap)?; @@ -40,7 +45,7 @@ pub fn asc_get( asc_ptr: AscPtr, ) -> Result where - C: AscType, + C: AscType + AscIndexId, T: FromAscObj, { T::from_asc_obj(asc_ptr.read_ptr(heap)?, heap) @@ -51,7 +56,7 @@ pub fn try_asc_get( asc_ptr: AscPtr, ) -> Result where - C: AscType, + C: AscType + AscIndexId, T: TryFromAscObj, { T::try_from_asc_obj(asc_ptr.read_ptr(heap)?, heap) diff --git a/graph/src/runtime/asc_ptr.rs b/graph/src/runtime/asc_ptr.rs index 9b6ec9b0ede..354cbfa6624 100644 --- a/graph/src/runtime/asc_ptr.rs +++ b/graph/src/runtime/asc_ptr.rs @@ -1,6 +1,7 @@ -use super::DeterministicHostError; +use super::{padding_to_16, DeterministicHostError}; -use super::{AscHeap, AscType}; +use super::{AscHeap, AscIndexId, AscType, IndexForAscTypeId}; +use semver::Version; use std::fmt; use std::marker::PhantomData; use std::mem::size_of; @@ -49,20 +50,53 @@ impl AscPtr { /// Read from `self` into the Rust struct `C`. pub fn read_ptr(self, heap: &H) -> Result { - let bytes = heap.get(self.0, C::asc_size(self, heap)?)?; - C::from_asc_bytes(&bytes) + let len = match heap.api_version() { + version if version <= Version::new(0, 0, 4) => C::asc_size(self, heap), + _ => self.read_len(heap), + }?; + let bytes = heap.get(self.0, len)?; + C::from_asc_bytes(&bytes, heap.api_version()) } /// Allocate `asc_obj` as an Asc object of class `C`. pub fn alloc_obj( asc_obj: C, heap: &mut H, - ) -> Result, DeterministicHostError> { - let heap_ptr = heap.raw_new(&asc_obj.to_asc_bytes()?)?; - Ok(AscPtr::new(heap_ptr)) + ) -> Result, DeterministicHostError> + where + C: AscIndexId, + { + match heap.api_version() { + version if version <= Version::new(0, 0, 4) => { + let heap_ptr = heap.raw_new(&asc_obj.to_asc_bytes()?)?; + Ok(AscPtr::new(heap_ptr)) + } + _ => { + let mut bytes = asc_obj.to_asc_bytes()?; + + let aligned_len = padding_to_16(bytes.len()); + // Since AssemblyScript keeps all allocated objects with a 16 byte alignment, + // we need to do the same when we allocate ourselves. + bytes.extend(std::iter::repeat(0).take(aligned_len)); + + let header = Self::generate_header( + heap, + C::INDEX_ASC_TYPE_ID, + asc_obj.content_len(&bytes), + bytes.len(), + )?; + let header_len = header.len() as u32; + + let heap_ptr = heap.raw_new(&[header, bytes].concat())?; + + // Use header length as offset. so the AscPtr points directly at the content. + Ok(AscPtr::new(heap_ptr + header_len)) + } + } } /// Helper used by arrays and strings to read their length. + /// Only used for version <= 0.0.4. pub fn read_u32(&self, heap: &H) -> Result { // Read the bytes pointed to by `self` as the bytes of a `u32`. let raw_bytes = heap.get(self.0, size_of::() as u32)?; @@ -71,6 +105,64 @@ impl AscPtr { Ok(u32::from_le_bytes(u32_bytes)) } + /// Helper that generates an AssemblyScript header. + /// An AssemblyScript header has 20 bytes and it is composed of 5 values. + /// - mm_info: usize -> size of all header contents + payload contents + padding + /// - gc_info: usize -> first GC info (we don't free memory so it's irrelevant) + /// - gc_info2: usize -> second GC info (we don't free memory so it's irrelevant) + /// - rt_id: u32 -> identifier for the class being allocated + /// - rt_size: u32 -> content size + /// Only used for version >= 0.0.5. + fn generate_header( + heap: &mut H, + type_id_index: IndexForAscTypeId, + content_length: usize, + full_length: usize, + ) -> Result, DeterministicHostError> { + let mut header: Vec = Vec::with_capacity(20); + + let gc_info: [u8; 4] = (0u32).to_le_bytes(); + let gc_info2: [u8; 4] = (0u32).to_le_bytes(); + let asc_type_id = heap.asc_type_id(type_id_index)?; + let rt_id: [u8; 4] = asc_type_id.to_le_bytes(); + let rt_size: [u8; 4] = (content_length as u32).to_le_bytes(); + + let mm_info: [u8; 4] = + ((gc_info.len() + gc_info2.len() + rt_id.len() + rt_size.len() + full_length) as u32) + .to_le_bytes(); + + header.extend(&mm_info); + header.extend(&gc_info); + header.extend(&gc_info2); + header.extend(&rt_id); + header.extend(&rt_size); + + Ok(header) + } + + /// Helper to read the length from the header. + /// An AssemblyScript header has 20 bytes, and it's right before the content, and composed by: + /// - mm_info: usize + /// - gc_info: usize + /// - gc_info2: usize + /// - rt_id: u32 + /// - rt_size: u32 + /// This function returns the `rt_size`. + /// Only used for version >= 0.0.5. + pub fn read_len(&self, heap: &H) -> Result { + let size_of_rt_size = 4; + let start_of_rt_size = self.0.checked_sub(size_of_rt_size).ok_or_else(|| { + DeterministicHostError(anyhow::anyhow!( + "Subtract overflow on pointer: {}", // Usually when pointer is zero because of null in AssemblyScript + self.0 + )) + })?; + let raw_bytes = heap.get(start_of_rt_size, size_of::() as u32)?; + let mut u32_bytes: [u8; size_of::()] = [0; size_of::()]; + u32_bytes.copy_from_slice(&raw_bytes); + Ok(u32::from_le_bytes(u32_bytes)) + } + /// Conversion to `u64` for use with `AscEnum`. pub fn to_payload(&self) -> u64 { self.0 as u64 @@ -98,8 +190,11 @@ impl AscType for AscPtr { self.0.to_asc_bytes() } - fn from_asc_bytes(asc_obj: &[u8]) -> Result { - let bytes = u32::from_asc_bytes(asc_obj)?; + fn from_asc_bytes( + asc_obj: &[u8], + api_version: Version, + ) -> Result { + let bytes = u32::from_asc_bytes(asc_obj, api_version)?; Ok(AscPtr::new(bytes)) } } diff --git a/graph/src/runtime/mod.rs b/graph/src/runtime/mod.rs index be5b8f794c5..0f172ea940b 100644 --- a/graph/src/runtime/mod.rs +++ b/graph/src/runtime/mod.rs @@ -10,10 +10,23 @@ pub use asc_heap::{asc_get, asc_new, try_asc_get, AscHeap, FromAscObj, ToAscObj, pub use asc_ptr::AscPtr; use anyhow::Error; +use semver::Version; use std::convert::TryInto; use std::fmt; use std::mem::size_of; +/// Marker trait for AssemblyScript types that the id should +/// be in the header. +pub trait AscIndexId { + /// Constant string with the name of the type in AssemblyScript. + /// This is used to get the identifier for the type in memory layout. + /// Info about memory layout: + /// https://www.assemblyscript.org/memory.html#common-header-layout. + /// Info about identifier (`idof`): + /// https://www.assemblyscript.org/garbage-collection.html#runtime-interface + const INDEX_ASC_TYPE_ID: IndexForAscTypeId; +} + /// A type that has a direct correspondence to an Asc type. /// /// This can be derived for structs that are `#[repr(C)]`, contain no padding @@ -28,9 +41,15 @@ pub trait AscType: Sized { fn to_asc_bytes(&self) -> Result, DeterministicHostError>; /// The Rust representation of an Asc object as layed out in Asc memory. - fn from_asc_bytes(asc_obj: &[u8]) -> Result; + fn from_asc_bytes(asc_obj: &[u8], api_version: Version) + -> Result; + + fn content_len(&self, asc_bytes: &[u8]) -> usize { + asc_bytes.len() + } /// Size of the corresponding Asc instance in bytes. + /// Only used for version <= 0.0.3. fn asc_size( _ptr: AscPtr, _heap: &H, @@ -46,7 +65,10 @@ impl AscType for std::marker::PhantomData { Ok(vec![]) } - fn from_asc_bytes(asc_obj: &[u8]) -> Result { + fn from_asc_bytes( + asc_obj: &[u8], + _api_version: Version, + ) -> Result { assert!(asc_obj.len() == 0); Ok(Self) @@ -63,7 +85,10 @@ impl AscType for bool { Ok(vec![*self as u8]) } - fn from_asc_bytes(asc_obj: &[u8]) -> Result { + fn from_asc_bytes( + asc_obj: &[u8], + _api_version: Version, + ) -> Result { if asc_obj.len() != 1 { Err(DeterministicHostError(anyhow::anyhow!( "Incorrect size for bool. Expected 1, got {},", @@ -86,7 +111,7 @@ macro_rules! impl_asc_type { Ok(self.to_le_bytes().to_vec()) } - fn from_asc_bytes(asc_obj: &[u8]) -> Result { + fn from_asc_bytes(asc_obj: &[u8], _api_version: Version) -> Result { let bytes = asc_obj.try_into().map_err(|_| { DeterministicHostError(anyhow::anyhow!( "Incorrect size for {}. Expected {}, got {},", @@ -107,6 +132,75 @@ macro_rules! impl_asc_type { impl_asc_type!(u8, u16, u32, u64, i8, i32, i64, f32, f64); +// The numbers on each variant could just be comments hence the +// `#[repr(u32)]`, however having them in code enforces each value +// to be the same as the docs. +#[repr(u32)] +#[derive(Copy, Clone, Debug)] +pub enum IndexForAscTypeId { + String = 0, + ArrayBuffer = 1, + Int8Array = 2, + Int16Array = 3, + Int32Array = 4, + Int64Array = 5, + Uint8Array = 6, + Uint16Array = 7, + Uint32Array = 8, + Uint64Array = 9, + Float32Array = 10, + Float64Array = 11, + BigDecimal = 12, + ArrayBool = 13, + ArrayUint8Array = 14, + ArrayEthereumValue = 15, + ArrayStoreValue = 16, + ArrayJsonValue = 17, + ArrayString = 18, + ArrayEventParam = 19, + ArrayTypedMapEntryStringJsonValue = 20, + ArrayTypedMapEntryStringStoreValue = 21, + SmartContractCall = 22, + EventParam = 23, + EthereumTransaction = 24, + EthereumBlock = 25, + EthereumCall = 26, + WrappedTypedMapStringJsonValue = 27, + WrappedBool = 28, + WrappedJsonValue = 29, + EthereumValue = 30, + StoreValue = 31, + JsonValue = 32, + EthereumEvent = 33, + TypedMapEntryStringStoreValue = 34, + TypedMapEntryStringJsonValue = 35, + TypedMapStringStoreValue = 36, + TypedMapStringJsonValue = 37, + TypedMapStringTypedMapStringJsonValue = 38, + ResultTypedMapStringJsonValueBool = 39, + ResultJsonValueBool = 40, + ArrayU8 = 41, + ArrayU16 = 42, + ArrayU32 = 43, + ArrayU64 = 44, + ArrayI8 = 45, + ArrayI16 = 46, + ArrayI32 = 47, + ArrayI64 = 48, + ArrayF32 = 49, + ArrayF64 = 50, + ArrayBigDecimal = 51, +} + +impl ToAscObj for IndexForAscTypeId { + fn to_asc_obj( + &self, + _heap: &mut H, + ) -> Result { + Ok(*self as u32) + } +} + #[derive(Debug)] pub struct DeterministicHostError(pub Error); @@ -141,3 +235,9 @@ impl From for HostExportError { HostExportError::Deterministic(value.0) } } + +pub const HEADER_SIZE: usize = 20; + +pub fn padding_to_16(content_length: usize) -> usize { + (16 - (HEADER_SIZE + content_length) % 16) % 16 +} diff --git a/runtime/derive/src/lib.rs b/runtime/derive/src/lib.rs index e8b558bd907..d3b8d5309fc 100644 --- a/runtime/derive/src/lib.rs +++ b/runtime/derive/src/lib.rs @@ -35,14 +35,15 @@ pub fn asc_type_derive(input: TokenStream) -> TokenStream { // } // #[allow(unused_variables)] -// fn from_asc_bytes(asc_obj: &[u8]) -> Self { +// fn from_asc_bytes(asc_obj: &[u8], api_version: semver::Version) -> Self { // assert_eq!(&asc_obj.len(), &size_of::()); // let mut offset = 0; // let field_size = std::mem::size_of::>(); -// let key = AscType::from_asc_bytes(&asc_obj[offset..(offset + field_size)]); +// let key = AscType::from_asc_bytes(&asc_obj[offset..(offset + field_size)], +// api_version.clone()); // offset += field_size; // let field_size = std::mem::size_of::>(); -// let value = AscType::from_asc_bytes(&asc_obj[offset..(offset + field_size)]); +// let value = AscType::from_asc_bytes(&asc_obj[offset..(offset + field_size)], api_version); // offset += field_size; // Self { key, value } // } @@ -77,10 +78,30 @@ fn asc_type_derive_struct(item_struct: ItemStruct) -> TokenStream { } #[allow(unused_variables)] - fn from_asc_bytes(asc_obj: &[u8]) -> Result { - if asc_obj.len() != std::mem::size_of::() { - return Err(DeterministicHostError(anyhow::anyhow!("Size does not match"))); - } + fn from_asc_bytes(asc_obj: &[u8], api_version: semver::Version) -> Result { + // Sanity check + match &api_version { + api_version if *api_version <= Version::new(0, 0, 4) => { + // This was using an double equal sign before instead of less than. + // This happened because of the new apiVersion support. + // Since some structures need different implementations for each + // version, their memory size got bigger because we're using an enum + // that contains both versions (each in a variant), and that increased + // the memory size, so that's why we use less than. + if asc_obj.len() < std::mem::size_of::() { + return Err(DeterministicHostError(anyhow::anyhow!("Size does not match"))); + } + } + _ => { + let content_size = size_of::(); + let aligned_size = graph::runtime::padding_to_16(content_size); + + if graph::runtime::HEADER_SIZE + asc_obj.len() == aligned_size + content_size { + return Err(DeterministicHostError(anyhow::anyhow!("Size does not match"))); + } + }, + }; + let mut offset = 0; #( @@ -88,7 +109,7 @@ fn asc_type_derive_struct(item_struct: ItemStruct) -> TokenStream { let field_data = asc_obj.get(offset..(offset + field_size)).ok_or_else(|| { DeterministicHostError(anyhow::anyhow!("Attempted to read past end of array")) })?; - let #field_names2 = AscType::from_asc_bytes(&field_data)?; + let #field_names2 = AscType::from_asc_bytes(&field_data, api_version.clone())?; offset += field_size; )* @@ -126,9 +147,9 @@ fn asc_type_derive_struct(item_struct: ItemStruct) -> TokenStream { // Ok(discriminant.to_asc_bytes()) // } // -// fn from_asc_bytes(asc_obj: &[u8]) -> Result { +// fn from_asc_bytes(asc_obj: &[u8], _api_version: semver::Version) -> Result { // let mut u32_bytes: [u8; size_of::()] = [0; size_of::()]; -// if std::mem::size_of_val(&u32_bytes) != std::mem::size_of_val(&as_obj) { +// if std::mem::size_of_val(&u32_bytes) != std::mem::size_of_val(&asc_obj) { // return Err(DeterministicHostError(anyhow::anyhow!("Invalid asc bytes size"))); // } // u32_bytes.copy_from_slice(&asc_obj); @@ -140,7 +161,7 @@ fn asc_type_derive_struct(item_struct: ItemStruct) -> TokenStream { // 3u32 => JsonValueKind::String, // 4u32 => JsonValueKind::Array, // 5u32 => JsonValueKind::Object, -// _ => Err(DeterministicHostError(anyhow::anyhow!("value {} is out of range for {}", discr, "JsonValueKind")), +// _ => Err(DeterministicHostError(anyhow::anyhow!("value {} is out of range for {}", discr, "JsonValueKind"))), // } // } // } @@ -170,7 +191,7 @@ fn asc_type_derive_enum(item_enum: ItemEnum) -> TokenStream { discriminant.to_asc_bytes() } - fn from_asc_bytes(asc_obj: &[u8]) -> Result { + fn from_asc_bytes(asc_obj: &[u8], _api_version: semver::Version) -> Result { let u32_bytes = ::std::convert::TryFrom::try_from(asc_obj) .map_err(|_| DeterministicHostError(anyhow::anyhow!("Invalid asc bytes size")))?; let discr = u32::from_le_bytes(u32_bytes); diff --git a/runtime/test/src/test.rs b/runtime/test/src/test.rs index 0c2d63e96ce..666fde42416 100644 --- a/runtime/test/src/test.rs +++ b/runtime/test/src/test.rs @@ -24,29 +24,44 @@ use web3::types::{Address, H160}; 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 subgraph_id_with_api_version(subgraph_id: &str, api_version: Version) -> String { + format!( + "{}_{}_{}_{}", + subgraph_id, api_version.major, api_version.minor, api_version.patch + ) +} + fn test_valid_module_and_store( subgraph_id: &str, data_source: DataSource, + api_version: Version, ) -> ( WasmInstance, Arc, DeploymentLocator, ) { - test_valid_module_and_store_with_timeout(subgraph_id, data_source, None) + test_valid_module_and_store_with_timeout(subgraph_id, data_source, api_version, None) } fn test_valid_module_and_store_with_timeout( subgraph_id: &str, data_source: DataSource, + api_version: Version, timeout: Option, ) -> ( WasmInstance, Arc, DeploymentLocator, ) { + let subgraph_id_with_api_version = + subgraph_id_with_api_version(subgraph_id, api_version.clone()); + let store = STORE.clone(); let metrics_registry = Arc::new(MockMetricsRegistry::new()); - let deployment_id = DeploymentHash::new(subgraph_id).unwrap(); + let deployment_id = DeploymentHash::new(&subgraph_id_with_api_version).unwrap(); let deployment = test_store::create_test_subgraph( &deployment_id, "type User @entity { @@ -79,7 +94,12 @@ fn test_valid_module_and_store_with_timeout( let module = WasmInstance::from_valid_module_with_ctx( Arc::new(ValidModule::new(data_source.mapping.runtime.as_ref()).unwrap()), - mock_context(deployment.clone(), data_source, store.subgraph_store()), + mock_context( + deployment.clone(), + data_source, + store.subgraph_store(), + api_version, + ), host_metrics, timeout, experimental_features, @@ -89,11 +109,19 @@ fn test_valid_module_and_store_with_timeout( (module, store.subgraph_store(), deployment) } -fn test_module(subgraph_id: &str, data_source: DataSource) -> WasmInstance { - test_valid_module_and_store(subgraph_id, data_source).0 +fn test_module( + subgraph_id: &str, + data_source: DataSource, + api_version: Version, +) -> WasmInstance { + test_valid_module_and_store(subgraph_id, data_source, api_version).0 } -fn mock_data_source(path: &str) -> DataSource { +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 { @@ -107,7 +135,7 @@ fn mock_data_source(path: &str) -> DataSource { }, mapping: Mapping { kind: String::from("ethereum/events"), - api_version: Version::parse("0.1.0").unwrap(), + api_version, language: String::from("wasm/assemblyscript"), entities: vec![], abis: vec![], @@ -150,6 +178,7 @@ fn mock_host_exports( subgraph_id: DeploymentHash, data_source: DataSource, store: Arc, + api_version: Version, ) -> HostExports { let arweave_adapter = Arc::new(ArweaveAdapter::new("https://arweave.net".to_string())); let three_box_adapter = Arc::new(ThreeBoxAdapter::new("https://ipfs.3box.io/".to_string())); @@ -163,7 +192,7 @@ fn mock_host_exports( }, mapping: Mapping { kind: String::from("ethereum/events"), - api_version: Version::parse("0.1.0").unwrap(), + api_version, language: String::from("wasm/assemblyscript"), entities: vec![], abis: vec![], @@ -194,6 +223,7 @@ fn mock_context( deployment: DeploymentLocator, data_source: DataSource, store: Arc, + api_version: Version, ) -> MappingContext { MappingContext { logger: test_store::LOGGER.clone(), @@ -205,6 +235,7 @@ fn mock_context( deployment.hash.clone(), data_source, store.clone(), + api_version, )), state: BlockState::new(store.writable(&deployment).unwrap(), Default::default()), proof_of_indexing: None, @@ -213,6 +244,7 @@ fn mock_context( } trait WasmInstanceExt { + fn invoke_export0(&self, f: &str); fn invoke_export(&self, f: &str, arg: AscPtr) -> AscPtr; fn invoke_export2(&self, f: &str, arg0: AscPtr, arg1: AscPtr) -> AscPtr; fn invoke_export2_void( @@ -226,6 +258,11 @@ trait WasmInstanceExt { } impl WasmInstanceExt for WasmInstance { + fn invoke_export0(&self, f: &str) { + let func = self.get_func(f).typed().unwrap().clone(); + let _: () = func.call(()).unwrap(); + } + fn invoke_export(&self, f: &str, arg: AscPtr) -> AscPtr { let func = self.get_func(f).typed().unwrap().clone(); let ptr: u32 = func.call(arg.wasm_ptr()).unwrap(); @@ -260,11 +297,11 @@ impl WasmInstanceExt for WasmInstance { } } -#[tokio::test] -async fn json_conversions() { +fn test_json_conversions(api_version: Version) { let mut module = test_module( "jsonConversions", - mock_data_source("wasm_test/string_to_number.wasm"), + mock_data_source("string_to_number.wasm", api_version.clone()), + api_version, ); // test u64 conversion @@ -297,10 +334,20 @@ async fn json_conversions() { } #[tokio::test] -async fn json_parsing() { +async fn json_conversions_v0_0_4() { + test_json_conversions(API_VERSION_0_0_4); +} + +#[tokio::test] +async fn json_conversions_v0_0_5() { + test_json_conversions(API_VERSION_0_0_5); +} + +fn test_json_parsing(api_version: Version) { let mut module = test_module( "jsonParsing", - mock_data_source("wasm_test/json_parsing.wasm"), + mock_data_source("json_parsing.wasm", api_version.clone()), + api_version, ); // Parse invalid JSON and handle the error gracefully @@ -320,8 +367,17 @@ async fn json_parsing() { assert_eq!(output, "OK: foo, ERROR: false"); } -#[tokio::test(threaded_scheduler)] -async fn ipfs_cat() { +#[tokio::test] +async fn json_parsing_v0_0_4() { + test_json_parsing(API_VERSION_0_0_4); +} + +#[tokio::test] +async fn json_parsing_v0_0_5() { + test_json_parsing(API_VERSION_0_0_5); +} + +async fn test_ipfs_cat(api_version: Version) { let ipfs = IpfsClient::localhost(); let hash = ipfs.add("42".into()).await.unwrap().hash; @@ -330,7 +386,11 @@ async fn ipfs_cat() { let runtime = tokio::runtime::Handle::current(); std::thread::spawn(move || { runtime.enter(|| { - let mut module = test_module("ipfsCat", mock_data_source("wasm_test/ipfs_cat.wasm")); + let mut module = test_module( + "ipfsCat", + mock_data_source("ipfs_cat.wasm", api_version.clone()), + api_version, + ); let arg = asc_new(&mut module, &hash).unwrap(); let converted: AscPtr = module.invoke_export("ipfsCatString", arg); let data: String = asc_get(&module, converted).unwrap(); @@ -341,16 +401,33 @@ async fn ipfs_cat() { .unwrap(); } +#[tokio::test(threaded_scheduler)] +async fn ipfs_cat_v0_0_4() { + test_ipfs_cat(API_VERSION_0_0_4).await; +} + +#[tokio::test(threaded_scheduler)] +async fn ipfs_cat_v0_0_5() { + test_ipfs_cat(API_VERSION_0_0_5).await; +} + // The user_data value we use with calls to ipfs_map const USER_DATA: &str = "user_data"; -fn make_thing(subgraph_id: &str, id: &str, value: &str) -> (String, EntityModification) { +fn make_thing( + subgraph_id: &str, + id: &str, + value: &str, + api_version: Version, +) -> (String, EntityModification) { + let subgraph_id_with_api_version = subgraph_id_with_api_version(subgraph_id, api_version); + let mut data = Entity::new(); data.set("id", id); data.set("value", value); data.set("extra", USER_DATA); let key = EntityKey::data( - DeploymentHash::new(subgraph_id).unwrap(), + DeploymentHash::new(&subgraph_id_with_api_version).unwrap(), "Thing".to_string(), id.to_string(), ); @@ -360,75 +437,86 @@ fn make_thing(subgraph_id: &str, id: &str, value: &str) -> (String, EntityModifi ) } -#[tokio::test(threaded_scheduler)] -async fn ipfs_map() { - const BAD_IPFS_HASH: &str = "bad-ipfs-hash"; +const BAD_IPFS_HASH: &str = "bad-ipfs-hash"; + +async fn run_ipfs_map( + ipfs: IpfsClient, + subgraph_id: &'static str, + json_string: String, + api_version: Version, +) -> Result, anyhow::Error> { + let hash = if json_string == BAD_IPFS_HASH { + "Qm".to_string() + } else { + ipfs.add(json_string.into()).await.unwrap().hash + }; + // Ipfs host functions use `block_on` which must be called from a sync context, + // so we replicate what we do `spawn_module`. + let runtime = tokio::runtime::Handle::current(); + std::thread::spawn(move || { + runtime.enter(|| { + let (mut module, _, _) = test_valid_module_and_store( + &subgraph_id, + mock_data_source("ipfs_map.wasm", api_version.clone()), + api_version, + ); + let value = asc_new(&mut module, &hash).unwrap(); + let user_data = asc_new(&mut module, USER_DATA).unwrap(); + + // Invoke the callback + let func = module.get_func("ipfsMap").typed().unwrap().clone(); + let _: () = func.call((value.wasm_ptr(), user_data.wasm_ptr()))?; + let mut mods = module + .take_ctx() + .ctx + .state + .entity_cache + .as_modifications()? + .modifications; + + // Bring the modifications into a predictable order (by entity_id) + mods.sort_by(|a, b| { + a.entity_key() + .entity_id + .partial_cmp(&b.entity_key().entity_id) + .unwrap() + }); + Ok(mods) + }) + }) + .join() + .unwrap() +} + +async fn test_ipfs_map(api_version: Version, json_error_msg: &str) { let ipfs = IpfsClient::localhost(); let subgraph_id = "ipfsMap"; - async fn run_ipfs_map( - ipfs: IpfsClient, - subgraph_id: &'static str, - json_string: String, - ) -> Result, anyhow::Error> { - let hash = if json_string == BAD_IPFS_HASH { - "Qm".to_string() - } else { - ipfs.add(json_string.into()).await.unwrap().hash - }; - - // Ipfs host functions use `block_on` which must be called from a sync context, - // so we replicate what we do `spawn_module`. - let runtime = tokio::runtime::Handle::current(); - std::thread::spawn(move || { - runtime.enter(|| { - let (mut module, _, _) = test_valid_module_and_store( - subgraph_id, - mock_data_source("wasm_test/ipfs_map.wasm"), - ); - let value = asc_new(&mut module, &hash).unwrap(); - let user_data = asc_new(&mut module, USER_DATA).unwrap(); - - // Invoke the callback - let func = module.get_func("ipfsMap").typed().unwrap().clone(); - let _: () = func.call((value.wasm_ptr(), user_data.wasm_ptr()))?; - let mut mods = module - .take_ctx() - .ctx - .state - .entity_cache - .as_modifications()? - .modifications; - - // Bring the modifications into a predictable order (by entity_id) - mods.sort_by(|a, b| { - a.entity_key() - .entity_id - .partial_cmp(&b.entity_key().entity_id) - .unwrap() - }); - Ok(mods) - }) - }) - .join() - .unwrap() - } - // Try it with two valid objects - let (str1, thing1) = make_thing(subgraph_id, "one", "eins"); - let (str2, thing2) = make_thing(subgraph_id, "two", "zwei"); - let ops = run_ipfs_map(ipfs.clone(), subgraph_id, format!("{}\n{}", str1, str2)) - .await - .expect("call failed"); + let (str1, thing1) = make_thing(&subgraph_id, "one", "eins", api_version.clone()); + let (str2, thing2) = make_thing(&subgraph_id, "two", "zwei", api_version.clone()); + let ops = run_ipfs_map( + ipfs.clone(), + subgraph_id, + format!("{}\n{}", str1, str2), + api_version.clone(), + ) + .await + .expect("call failed"); let expected = vec![thing1, thing2]; assert_eq!(expected, ops); // Valid JSON, but not what the callback expected; it will // fail on an assertion - let err = run_ipfs_map(ipfs.clone(), subgraph_id, format!("{}\n[1,2]", str1)) - .await - .unwrap_err(); + let err = run_ipfs_map( + ipfs.clone(), + subgraph_id, + format!("{}\n[1,2]", str1), + api_version.clone(), + ) + .await + .unwrap_err(); assert!( format!("{:#}", err).contains("JSON value is not an object."), "{:#}", @@ -436,16 +524,26 @@ async fn ipfs_map() { ); // Malformed JSON - let errmsg = run_ipfs_map(ipfs.clone(), subgraph_id, format!("{}\n[", str1)) - .await - .unwrap_err() - .to_string(); + let errmsg = run_ipfs_map( + ipfs.clone(), + subgraph_id, + format!("{}\n[", str1), + api_version.clone(), + ) + .await + .unwrap_err() + .to_string(); assert!(errmsg.contains("EOF while parsing a list")); // Empty input - let ops = run_ipfs_map(ipfs.clone(), subgraph_id, "".to_string()) - .await - .expect("call failed for emoty string"); + let ops = run_ipfs_map( + ipfs.clone(), + subgraph_id, + "".to_string(), + api_version.clone(), + ) + .await + .expect("call failed for emoty string"); assert_eq!(0, ops.len()); // Missing entry in the JSON object @@ -455,29 +553,48 @@ async fn ipfs_map() { ipfs.clone(), subgraph_id, "{\"value\": \"drei\"}".to_string(), + api_version.clone(), ) .await .unwrap_err() ); - assert!(errmsg.contains("JSON value is not a string.")); + assert!(errmsg.contains(json_error_msg)); // Bad IPFS hash. - let errmsg = run_ipfs_map(ipfs.clone(), subgraph_id, BAD_IPFS_HASH.to_string()) - .await - .unwrap_err() - .to_string(); + let errmsg = run_ipfs_map( + ipfs.clone(), + subgraph_id, + BAD_IPFS_HASH.to_string(), + api_version.clone(), + ) + .await + .unwrap_err() + .to_string(); assert!(errmsg.contains("500 Internal Server Error")); } #[tokio::test(threaded_scheduler)] -async fn ipfs_fail() { +async fn ipfs_map_v0_0_4() { + test_ipfs_map(API_VERSION_0_0_4, "JSON value is not a string.").await; +} + +#[tokio::test(threaded_scheduler)] +async fn ipfs_map_v0_0_5() { + test_ipfs_map(API_VERSION_0_0_5, "'id' should not be null").await; +} + +async fn test_ipfs_fail(api_version: Version) { let runtime = tokio::runtime::Handle::current(); // Ipfs host functions use `block_on` which must be called from a sync context, // so we replicate what we do `spawn_module`. std::thread::spawn(move || { runtime.enter(|| { - let mut module = test_module("ipfsFail", mock_data_source("wasm_test/ipfs_cat.wasm")); + let mut module = test_module( + "ipfsFail", + mock_data_source("ipfs_cat.wasm", api_version.clone()), + api_version, + ); let hash = asc_new(&mut module, "invalid hash").unwrap(); assert!(module @@ -489,9 +606,22 @@ async fn ipfs_fail() { .unwrap(); } -#[tokio::test] -async fn crypto_keccak256() { - let mut module = test_module("cryptoKeccak256", mock_data_source("wasm_test/crypto.wasm")); +#[tokio::test(threaded_scheduler)] +async fn ipfs_fail_v0_0_4() { + test_ipfs_fail(API_VERSION_0_0_4).await; +} + +#[tokio::test(threaded_scheduler)] +async fn ipfs_fail_v0_0_5() { + test_ipfs_fail(API_VERSION_0_0_5).await; +} + +fn test_crypto_keccak256(api_version: Version) { + let mut module = test_module( + "cryptoKeccak256", + mock_data_source("crypto.wasm", api_version.clone()), + api_version, + ); let input: &[u8] = "eth".as_ref(); let input: AscPtr = asc_new(&mut module, input).unwrap(); @@ -504,10 +634,20 @@ async fn crypto_keccak256() { } #[tokio::test] -async fn big_int_to_hex() { +async fn crypto_keccak256_v0_0_4() { + test_crypto_keccak256(API_VERSION_0_0_4); +} + +#[tokio::test] +async fn crypto_keccak256_v0_0_5() { + test_crypto_keccak256(API_VERSION_0_0_5); +} + +fn test_big_int_to_hex(api_version: Version) { let mut module = test_module( "BigIntToHex", - mock_data_source("wasm_test/big_int_to_hex.wasm"), + mock_data_source("big_int_to_hex.wasm", api_version.clone()), + api_version, ); // Convert zero to hex @@ -536,10 +676,20 @@ async fn big_int_to_hex() { } #[tokio::test] -async fn big_int_arithmetic() { +async fn big_int_to_hex_v0_0_4() { + test_big_int_to_hex(API_VERSION_0_0_4); +} + +#[tokio::test] +async fn big_int_to_hex_v0_0_5() { + test_big_int_to_hex(API_VERSION_0_0_5); +} + +fn test_big_int_arithmetic(api_version: Version) { let mut module = test_module( "BigIntArithmetic", - mock_data_source("wasm_test/big_int_arithmetic.wasm"), + mock_data_source("big_int_arithmetic.wasm", api_version.clone()), + api_version, ); // 0 + 1 = 1 @@ -598,20 +748,46 @@ async fn big_int_arithmetic() { } #[tokio::test] -async fn abort() { - let module = test_module("abort", mock_data_source("wasm_test/abort.wasm")); +async fn big_int_arithmetic_v0_0_4() { + test_big_int_arithmetic(API_VERSION_0_0_4); +} + +#[tokio::test] +async fn big_int_arithmetic_v0_0_5() { + test_big_int_arithmetic(API_VERSION_0_0_5); +} + +fn test_abort(api_version: Version, error_msg: &str) { + let module = test_module( + "abort", + mock_data_source("abort.wasm", api_version.clone()), + api_version, + ); let res: Result<(), _> = module.get_func("abort").typed().unwrap().call(()); - assert!(res - .unwrap_err() - .to_string() - .contains("line 6, column 2, with message: not true")); + assert!(res.unwrap_err().to_string().contains(error_msg)); } #[tokio::test] -async fn bytes_to_base58() { +async fn abort_v0_0_4() { + test_abort( + API_VERSION_0_0_4, + "line 6, column 2, with message: not true", + ); +} + +#[tokio::test] +async fn abort_v0_0_5() { + test_abort( + API_VERSION_0_0_5, + "line 4, column 3, with message: not true", + ); +} + +fn test_bytes_to_base58(api_version: Version) { let mut module = test_module( "bytesToBase58", - mock_data_source("wasm_test/bytes_to_base58.wasm"), + mock_data_source("bytes_to_base58.wasm", api_version.clone()), + api_version, ); let bytes = hex::decode("12207D5A99F603F231D53A4F39D1521F98D2E8BB279CF29BEBFD0687DC98458E7F89") .unwrap(); @@ -622,14 +798,24 @@ async fn bytes_to_base58() { } #[tokio::test] -async fn data_source_create() { +async fn bytes_to_base58_v0_0_4() { + test_bytes_to_base58(API_VERSION_0_0_4); +} + +#[tokio::test] +async fn bytes_to_base58_v0_0_5() { + test_bytes_to_base58(API_VERSION_0_0_5); +} + +fn test_data_source_create(api_version: Version) { let run_data_source_create = move |name: String, params: Vec| -> Result>, wasmtime::Trap> { let mut module = test_module( "DataSourceCreate", - mock_data_source("wasm_test/data_source_create.wasm"), + mock_data_source("data_source_create.wasm", api_version.clone()), + api_version.clone(), ); let name = asc_new(&mut module, &name).unwrap(); @@ -662,10 +848,20 @@ async fn data_source_create() { } #[tokio::test] -async fn ens_name_by_hash() { +async fn data_source_create_v0_0_4() { + test_data_source_create(API_VERSION_0_0_4); +} + +#[tokio::test] +async fn data_source_create_v0_0_5() { + test_data_source_create(API_VERSION_0_0_5); +} + +fn test_ens_name_by_hash(api_version: Version) { let mut module = test_module( "EnsNameByHash", - mock_data_source("wasm_test/ens_name_by_hash.wasm"), + mock_data_source("ens_name_by_hash.wasm", api_version.clone()), + api_version, ); let hash = "0x7f0c1b04d1a4926f9c635a030eeb611d4c26e5e73291b32a1c7a4ac56935b5b3"; @@ -683,9 +879,21 @@ async fn ens_name_by_hash() { } #[tokio::test] -async fn entity_store() { - let (mut module, store, deployment) = - test_valid_module_and_store("entityStore", mock_data_source("wasm_test/store.wasm")); +async fn ens_name_by_hash_v0_0_4() { + test_ens_name_by_hash(API_VERSION_0_0_4); +} + +#[tokio::test] +async fn ens_name_by_hash_v0_0_5() { + test_ens_name_by_hash(API_VERSION_0_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()), + api_version, + ); let mut alex = Entity::new(); alex.set("id", "alex"); @@ -766,14 +974,54 @@ async fn entity_store() { assert_eq!(Some(&Value::from("Brine-O")), data.get("name")); } _ => assert!(false, "expected Insert modification"), - } + }; } #[tokio::test] -async fn detect_contract_calls() { - let data_source_without_calls = mock_data_source("wasm_test/abi_store_value.wasm"); +async fn entity_store_v0_0_4() { + test_entity_store(API_VERSION_0_0_4); +} + +#[tokio::test] +async fn entity_store_v0_0_5() { + test_entity_store(API_VERSION_0_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()); assert_eq!(data_source_without_calls.mapping.requires_archive(), false); - let data_source_with_calls = mock_data_source("wasm_test/contract_calls.wasm"); + let data_source_with_calls = mock_data_source("contract_calls.wasm", api_version); assert_eq!(data_source_with_calls.mapping.requires_archive(), true); } + +#[tokio::test] +async fn detect_contract_calls_v0_0_4() { + test_detect_contract_calls(API_VERSION_0_0_4); +} + +#[tokio::test] +async fn detect_contract_calls_v0_0_5() { + test_detect_contract_calls(API_VERSION_0_0_5); +} + +fn test_allocate_global(api_version: Version) { + let module = test_module( + "AllocateGlobal", + mock_data_source("allocate_global.wasm", api_version.clone()), + api_version, + ); + + // Assert globals can be allocated and don't break the heap + module.invoke_export0("assert_global_works"); +} + +#[tokio::test] +async fn allocate_global_v0_0_5() { + // Only in apiVersion v0.0.5 because there's no issue in older versions. + // The problem with the new one is related to the AS stub runtime `offset` + // variable not being initialized (lazy) before we use it so this test checks + // that it works (at the moment using __alloc call to force offset to be eagerly + // evaluated). + test_allocate_global(API_VERSION_0_0_5); +} diff --git a/runtime/test/src/test/abi.rs b/runtime/test/src/test/abi.rs index 475aa59f77f..6619e4bbdf7 100644 --- a/runtime/test/src/test/abi.rs +++ b/runtime/test/src/test/abi.rs @@ -1,19 +1,20 @@ use graph::prelude::{ethabi::Token, web3::types::U256}; use graph_runtime_wasm::{ asc_abi::class::{ - ArrayBuffer, AscEnum, AscEnumArray, EthereumValueKind, StoreValueKind, TypedArray, + ArrayBuffer, AscAddress, AscEnum, AscEnumArray, EthereumValueKind, StoreValueKind, + TypedArray, }, TRAP_TIMEOUT, }; use super::*; -#[tokio::test(threaded_scheduler)] -async fn unbounded_loop() { +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("wasm_test/non_terminating.wasm"), + mock_data_source("non_terminating.wasm", api_version.clone()), + api_version, Some(Duration::from_secs(3)), ) .0; @@ -21,11 +22,21 @@ async fn unbounded_loop() { assert!(res.unwrap_err().to_string().contains(TRAP_TIMEOUT)); } -#[tokio::test] -async fn unbounded_recursion() { +#[tokio::test(threaded_scheduler)] +async fn unbounded_loop_v0_0_4() { + test_unbounded_loop(API_VERSION_0_0_4); +} + +#[tokio::test(threaded_scheduler)] +async fn unbounded_loop_v0_0_5() { + test_unbounded_loop(API_VERSION_0_0_5); +} + +fn test_unbounded_recursion(api_version: Version) { let module = test_module( "unboundedRecursion", - mock_data_source("wasm_test/non_terminating.wasm"), + mock_data_source("non_terminating.wasm", api_version.clone()), + api_version, ); let res: Result<(), _> = module.get_func("rabbit_hole").typed().unwrap().call(()); let err_msg = res.unwrap_err().to_string(); @@ -33,8 +44,21 @@ async fn unbounded_recursion() { } #[tokio::test] -async fn abi_array() { - let mut module = test_module("abiArray", mock_data_source("wasm_test/abi_classes.wasm")); +async fn unbounded_recursion_v0_0_4() { + test_unbounded_recursion(API_VERSION_0_0_4); +} + +#[tokio::test] +async fn unbounded_recursion_v0_0_5() { + test_unbounded_recursion(API_VERSION_0_0_5); +} + +fn test_abi_array(api_version: Version) { + let mut module = test_module( + "abiArray", + mock_data_source("abi_classes.wasm", api_version.clone()), + api_version, + ); let vec = vec![ "1".to_owned(), @@ -54,16 +78,26 @@ async fn abi_array() { "2".to_owned(), "3".to_owned(), "4".to_owned(), - "5".to_owned() + "5".to_owned(), ] - ) + ); } #[tokio::test] -async fn abi_subarray() { +async fn abi_array_v0_0_4() { + test_abi_array(API_VERSION_0_0_4); +} + +#[tokio::test] +async fn abi_array_v0_0_5() { + test_abi_array(API_VERSION_0_0_5); +} + +fn test_abi_subarray(api_version: Version) { let mut module = test_module( "abiSubarray", - mock_data_source("wasm_test/abi_classes.wasm"), + mock_data_source("abi_classes.wasm", api_version.clone()), + api_version, ); let vec: Vec = vec![1, 2, 3, 4]; @@ -73,14 +107,24 @@ async fn abi_subarray() { module.invoke_export("byte_array_third_quarter", vec_obj); let new_vec: Vec = asc_get(&module, new_vec_obj).unwrap(); - assert_eq!(new_vec, vec![3]) + assert_eq!(new_vec, vec![3]); } #[tokio::test] -async fn abi_bytes_and_fixed_bytes() { +async fn abi_subarray_v0_0_4() { + test_abi_subarray(API_VERSION_0_0_4); +} + +#[tokio::test] +async fn abi_subarray_v0_0_5() { + test_abi_subarray(API_VERSION_0_0_5); +} + +fn test_abi_bytes_and_fixed_bytes(api_version: Version) { let mut module = test_module( "abiBytesAndFixedBytes", - mock_data_source("wasm_test/abi_classes.wasm"), + mock_data_source("abi_classes.wasm", api_version.clone()), + api_version, ); let bytes1: Vec = vec![42, 45, 7, 245, 45]; let bytes2: Vec = vec![3, 12, 0, 1, 255]; @@ -97,13 +141,21 @@ async fn abi_bytes_and_fixed_bytes() { assert_eq!(new_vec, concated); } -/// Test a roundtrip Token -> Payload -> Token identity conversion through asc, -/// and assert the final token is the same as the starting one. #[tokio::test] -async fn abi_ethabi_token_identity() { +async fn abi_bytes_and_fixed_bytes_v0_0_4() { + test_abi_bytes_and_fixed_bytes(API_VERSION_0_0_4); +} + +#[tokio::test] +async fn abi_bytes_and_fixed_bytes_v0_0_5() { + test_abi_bytes_and_fixed_bytes(API_VERSION_0_0_5); +} + +fn test_abi_ethabi_token_identity(api_version: Version) { let mut module = test_module( "abiEthabiTokenIdentity", - mock_data_source("wasm_test/abi_token.wasm"), + mock_data_source("abi_token.wasm", api_version.clone()), + api_version, ); // Token::Address @@ -111,7 +163,7 @@ async fn abi_ethabi_token_identity() { let token_address = Token::Address(address); let token_address_ptr = asc_new(&mut module, &token_address).unwrap(); - let new_address_obj: AscPtr = + let new_address_obj: AscPtr = module.invoke_export("token_to_address", token_address_ptr); let new_token_ptr = module.invoke_export("token_from_address", new_address_obj); @@ -192,13 +244,25 @@ async fn abi_ethabi_token_identity() { assert_eq!(new_token, token_array_nested); } +/// Test a roundtrip Token -> Payload -> Token identity conversion through asc, +/// and assert the final token is the same as the starting one. #[tokio::test] -async fn abi_store_value() { - use graph::data::store::Value; +async fn abi_ethabi_token_identity_v0_0_4() { + test_abi_ethabi_token_identity(API_VERSION_0_0_4); +} +/// Test a roundtrip Token -> Payload -> Token identity conversion through asc, +/// and assert the final token is the same as the starting one. +#[tokio::test] +async fn abi_ethabi_token_identity_v0_0_5() { + test_abi_ethabi_token_identity(API_VERSION_0_0_5); +} + +fn test_abi_store_value(api_version: Version) { let mut module = test_module( "abiStoreValue", - mock_data_source("wasm_test/abi_store_value.wasm"), + mock_data_source("abi_store_value.wasm", api_version.clone()), + api_version, ); // Value::Null @@ -291,8 +355,21 @@ async fn abi_store_value() { } #[tokio::test] -async fn abi_h160() { - let mut module = test_module("abiH160", mock_data_source("wasm_test/abi_classes.wasm")); +async fn abi_store_value_v0_0_4() { + test_abi_store_value(API_VERSION_0_0_4); +} + +#[tokio::test] +async fn abi_store_value_v0_0_5() { + test_abi_store_value(API_VERSION_0_0_5); +} + +fn test_abi_h160(api_version: Version) { + let mut module = test_module( + "abiH160", + mock_data_source("abi_classes.wasm", api_version.clone()), + api_version, + ); let address = H160::zero(); // As an `Uint8Array` @@ -309,19 +386,45 @@ async fn abi_h160() { } #[tokio::test] -async fn string() { - let mut module = test_module("string", mock_data_source("wasm_test/abi_classes.wasm")); +async fn abi_h160_v0_0_4() { + test_abi_h160(API_VERSION_0_0_4); +} + +#[tokio::test] +async fn abi_h160_v0_0_5() { + test_abi_h160(API_VERSION_0_0_5); +} + +fn test_string(api_version: Version) { + let mut module = test_module( + "string", + mock_data_source("abi_classes.wasm", api_version.clone()), + api_version, + ); let string = " 漢字Double_Me🇧🇷 "; let trimmed_string_ptr = asc_new(&mut module, string).unwrap(); let trimmed_string_obj: AscPtr = module.invoke_export("repeat_twice", trimmed_string_ptr); let doubled_string: String = asc_get(&module, trimmed_string_obj).unwrap(); - assert_eq!(doubled_string, string.repeat(2)) + assert_eq!(doubled_string, string.repeat(2)); +} + +#[tokio::test] +async fn string_v0_0_4() { + test_string(API_VERSION_0_0_4); } #[tokio::test] -async fn abi_big_int() { - let mut module = test_module("abiBigInt", mock_data_source("wasm_test/abi_classes.wasm")); +async fn string_v0_0_5() { + test_string(API_VERSION_0_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()), + api_version, + ); // Test passing in 0 and increment it by 1 let old_uint = U256::zero(); @@ -344,10 +447,20 @@ async fn abi_big_int() { } #[tokio::test] -async fn big_int_to_string() { +async fn abi_big_int_v0_0_4() { + test_abi_big_int(API_VERSION_0_0_4); +} + +#[tokio::test] +async fn abi_big_int_v0_0_5() { + test_abi_big_int(API_VERSION_0_0_5); +} + +fn test_big_int_to_string(api_version: Version) { let mut module = test_module( "bigIntToString", - mock_data_source("wasm_test/big_int_to_string.wasm"), + mock_data_source("big_int_to_string.wasm", api_version.clone()), + api_version, ); let big_int_str = "30145144166666665000000000000000000"; @@ -358,14 +471,21 @@ async fn big_int_to_string() { assert_eq!(string, big_int_str); } -// This should panic rather than exhibiting UB. It's hard to test for UB, but -// when reproducing a SIGILL was observed which would be caught by this. #[tokio::test] -#[should_panic] -async fn invalid_discriminant() { +async fn big_int_to_string_v0_0_4() { + test_big_int_to_string(API_VERSION_0_0_4); +} + +#[tokio::test] +async fn big_int_to_string_v0_0_5() { + test_big_int_to_string(API_VERSION_0_0_5); +} + +fn test_invalid_discriminant(api_version: Version) { let module = test_module( "invalidDiscriminant", - mock_data_source("wasm_test/abi_store_value.wasm"), + mock_data_source("abi_store_value.wasm", api_version.clone()), + api_version, ); let func = module @@ -376,3 +496,19 @@ async fn invalid_discriminant() { let ptr: u32 = func.call(()).unwrap(); let _value: Value = try_asc_get(&module, ptr.into()).unwrap(); } + +// This should panic rather than exhibiting UB. It's hard to test for UB, but +// when reproducing a SIGILL was observed which would be caught by this. +#[tokio::test] +#[should_panic] +async fn invalid_discriminant_v0_0_4() { + test_invalid_discriminant(API_VERSION_0_0_4); +} + +// This should panic rather than exhibiting UB. It's hard to test for UB, but +// when reproducing a SIGILL was observed which would be caught by this. +#[tokio::test] +#[should_panic] +async fn invalid_discriminant_v0_0_5() { + test_invalid_discriminant(API_VERSION_0_0_5); +} diff --git a/runtime/test/wasm_test/Makefile b/runtime/test/wasm_test/Makefile deleted file mode 100644 index 640008e62b1..00000000000 --- a/runtime/test/wasm_test/Makefile +++ /dev/null @@ -1,10 +0,0 @@ -TS_FILES=$(wildcard *.ts) -WASM_FILES=$(patsubst %.ts,%.wasm,$(TS_FILES)) - -all: $(WASM_FILES) - -%.wasm: %.ts - @asc $< -b $@ --validate - -clean: - rm $(WASM_FILES) diff --git a/runtime/test/wasm_test/abi_classes.ts b/runtime/test/wasm_test/api_version_0_0_4/abi_classes.ts similarity index 100% rename from runtime/test/wasm_test/abi_classes.ts rename to runtime/test/wasm_test/api_version_0_0_4/abi_classes.ts diff --git a/runtime/test/wasm_test/abi_classes.wasm b/runtime/test/wasm_test/api_version_0_0_4/abi_classes.wasm similarity index 100% rename from runtime/test/wasm_test/abi_classes.wasm rename to runtime/test/wasm_test/api_version_0_0_4/abi_classes.wasm diff --git a/runtime/test/wasm_test/abi_store_value.ts b/runtime/test/wasm_test/api_version_0_0_4/abi_store_value.ts similarity index 100% rename from runtime/test/wasm_test/abi_store_value.ts rename to runtime/test/wasm_test/api_version_0_0_4/abi_store_value.ts diff --git a/runtime/test/wasm_test/abi_store_value.wasm b/runtime/test/wasm_test/api_version_0_0_4/abi_store_value.wasm similarity index 100% rename from runtime/test/wasm_test/abi_store_value.wasm rename to runtime/test/wasm_test/api_version_0_0_4/abi_store_value.wasm diff --git a/runtime/test/wasm_test/abi_token.ts b/runtime/test/wasm_test/api_version_0_0_4/abi_token.ts similarity index 100% rename from runtime/test/wasm_test/abi_token.ts rename to runtime/test/wasm_test/api_version_0_0_4/abi_token.ts diff --git a/runtime/test/wasm_test/abi_token.wasm b/runtime/test/wasm_test/api_version_0_0_4/abi_token.wasm similarity index 100% rename from runtime/test/wasm_test/abi_token.wasm rename to runtime/test/wasm_test/api_version_0_0_4/abi_token.wasm diff --git a/runtime/test/wasm_test/abort.ts b/runtime/test/wasm_test/api_version_0_0_4/abort.ts similarity index 100% rename from runtime/test/wasm_test/abort.ts rename to runtime/test/wasm_test/api_version_0_0_4/abort.ts diff --git a/runtime/test/wasm_test/abort.wasm b/runtime/test/wasm_test/api_version_0_0_4/abort.wasm similarity index 100% rename from runtime/test/wasm_test/abort.wasm rename to runtime/test/wasm_test/api_version_0_0_4/abort.wasm diff --git a/runtime/test/wasm_test/add_fn.wasm b/runtime/test/wasm_test/api_version_0_0_4/add_fn.wasm similarity index 100% rename from runtime/test/wasm_test/add_fn.wasm rename to runtime/test/wasm_test/api_version_0_0_4/add_fn.wasm diff --git a/runtime/test/wasm_test/big_int_arithmetic.ts b/runtime/test/wasm_test/api_version_0_0_4/big_int_arithmetic.ts similarity index 100% rename from runtime/test/wasm_test/big_int_arithmetic.ts rename to runtime/test/wasm_test/api_version_0_0_4/big_int_arithmetic.ts diff --git a/runtime/test/wasm_test/big_int_arithmetic.wasm b/runtime/test/wasm_test/api_version_0_0_4/big_int_arithmetic.wasm similarity index 100% rename from runtime/test/wasm_test/big_int_arithmetic.wasm rename to runtime/test/wasm_test/api_version_0_0_4/big_int_arithmetic.wasm diff --git a/runtime/test/wasm_test/big_int_to_hex.ts b/runtime/test/wasm_test/api_version_0_0_4/big_int_to_hex.ts similarity index 100% rename from runtime/test/wasm_test/big_int_to_hex.ts rename to runtime/test/wasm_test/api_version_0_0_4/big_int_to_hex.ts diff --git a/runtime/test/wasm_test/big_int_to_hex.wasm b/runtime/test/wasm_test/api_version_0_0_4/big_int_to_hex.wasm similarity index 100% rename from runtime/test/wasm_test/big_int_to_hex.wasm rename to runtime/test/wasm_test/api_version_0_0_4/big_int_to_hex.wasm diff --git a/runtime/test/wasm_test/big_int_to_string.ts b/runtime/test/wasm_test/api_version_0_0_4/big_int_to_string.ts similarity index 100% rename from runtime/test/wasm_test/big_int_to_string.ts rename to runtime/test/wasm_test/api_version_0_0_4/big_int_to_string.ts diff --git a/runtime/test/wasm_test/big_int_to_string.wasm b/runtime/test/wasm_test/api_version_0_0_4/big_int_to_string.wasm similarity index 100% rename from runtime/test/wasm_test/big_int_to_string.wasm rename to runtime/test/wasm_test/api_version_0_0_4/big_int_to_string.wasm diff --git a/runtime/test/wasm_test/bytes_to_base58.ts b/runtime/test/wasm_test/api_version_0_0_4/bytes_to_base58.ts similarity index 100% rename from runtime/test/wasm_test/bytes_to_base58.ts rename to runtime/test/wasm_test/api_version_0_0_4/bytes_to_base58.ts diff --git a/runtime/test/wasm_test/bytes_to_base58.wasm b/runtime/test/wasm_test/api_version_0_0_4/bytes_to_base58.wasm similarity index 100% rename from runtime/test/wasm_test/bytes_to_base58.wasm rename to runtime/test/wasm_test/api_version_0_0_4/bytes_to_base58.wasm diff --git a/runtime/test/wasm_test/contract_calls.ts b/runtime/test/wasm_test/api_version_0_0_4/contract_calls.ts similarity index 100% rename from runtime/test/wasm_test/contract_calls.ts rename to runtime/test/wasm_test/api_version_0_0_4/contract_calls.ts diff --git a/runtime/test/wasm_test/contract_calls.wasm b/runtime/test/wasm_test/api_version_0_0_4/contract_calls.wasm similarity index 100% rename from runtime/test/wasm_test/contract_calls.wasm rename to runtime/test/wasm_test/api_version_0_0_4/contract_calls.wasm diff --git a/runtime/test/wasm_test/crypto.ts b/runtime/test/wasm_test/api_version_0_0_4/crypto.ts similarity index 100% rename from runtime/test/wasm_test/crypto.ts rename to runtime/test/wasm_test/api_version_0_0_4/crypto.ts diff --git a/runtime/test/wasm_test/crypto.wasm b/runtime/test/wasm_test/api_version_0_0_4/crypto.wasm similarity index 100% rename from runtime/test/wasm_test/crypto.wasm rename to runtime/test/wasm_test/api_version_0_0_4/crypto.wasm diff --git a/runtime/test/wasm_test/data_source_create.ts b/runtime/test/wasm_test/api_version_0_0_4/data_source_create.ts similarity index 100% rename from runtime/test/wasm_test/data_source_create.ts rename to runtime/test/wasm_test/api_version_0_0_4/data_source_create.ts diff --git a/runtime/test/wasm_test/data_source_create.wasm b/runtime/test/wasm_test/api_version_0_0_4/data_source_create.wasm similarity index 100% rename from runtime/test/wasm_test/data_source_create.wasm rename to runtime/test/wasm_test/api_version_0_0_4/data_source_create.wasm diff --git a/runtime/test/wasm_test/ens_name_by_hash.ts b/runtime/test/wasm_test/api_version_0_0_4/ens_name_by_hash.ts similarity index 100% rename from runtime/test/wasm_test/ens_name_by_hash.ts rename to runtime/test/wasm_test/api_version_0_0_4/ens_name_by_hash.ts diff --git a/runtime/test/wasm_test/ens_name_by_hash.wasm b/runtime/test/wasm_test/api_version_0_0_4/ens_name_by_hash.wasm similarity index 100% rename from runtime/test/wasm_test/ens_name_by_hash.wasm rename to runtime/test/wasm_test/api_version_0_0_4/ens_name_by_hash.wasm diff --git a/runtime/test/wasm_test/ipfs_cat.ts b/runtime/test/wasm_test/api_version_0_0_4/ipfs_cat.ts similarity index 100% rename from runtime/test/wasm_test/ipfs_cat.ts rename to runtime/test/wasm_test/api_version_0_0_4/ipfs_cat.ts diff --git a/runtime/test/wasm_test/ipfs_cat.wasm b/runtime/test/wasm_test/api_version_0_0_4/ipfs_cat.wasm similarity index 100% rename from runtime/test/wasm_test/ipfs_cat.wasm rename to runtime/test/wasm_test/api_version_0_0_4/ipfs_cat.wasm diff --git a/runtime/test/wasm_test/ipfs_map.ts b/runtime/test/wasm_test/api_version_0_0_4/ipfs_map.ts similarity index 100% rename from runtime/test/wasm_test/ipfs_map.ts rename to runtime/test/wasm_test/api_version_0_0_4/ipfs_map.ts diff --git a/runtime/test/wasm_test/ipfs_map.wasm b/runtime/test/wasm_test/api_version_0_0_4/ipfs_map.wasm similarity index 100% rename from runtime/test/wasm_test/ipfs_map.wasm rename to runtime/test/wasm_test/api_version_0_0_4/ipfs_map.wasm diff --git a/runtime/test/wasm_test/json_parsing.ts b/runtime/test/wasm_test/api_version_0_0_4/json_parsing.ts similarity index 100% rename from runtime/test/wasm_test/json_parsing.ts rename to runtime/test/wasm_test/api_version_0_0_4/json_parsing.ts diff --git a/runtime/test/wasm_test/json_parsing.wasm b/runtime/test/wasm_test/api_version_0_0_4/json_parsing.wasm similarity index 100% rename from runtime/test/wasm_test/json_parsing.wasm rename to runtime/test/wasm_test/api_version_0_0_4/json_parsing.wasm diff --git a/runtime/test/wasm_test/non_terminating.ts b/runtime/test/wasm_test/api_version_0_0_4/non_terminating.ts similarity index 100% rename from runtime/test/wasm_test/non_terminating.ts rename to runtime/test/wasm_test/api_version_0_0_4/non_terminating.ts diff --git a/runtime/test/wasm_test/non_terminating.wasm b/runtime/test/wasm_test/api_version_0_0_4/non_terminating.wasm similarity index 100% rename from runtime/test/wasm_test/non_terminating.wasm rename to runtime/test/wasm_test/api_version_0_0_4/non_terminating.wasm diff --git a/runtime/test/wasm_test/store.ts b/runtime/test/wasm_test/api_version_0_0_4/store.ts similarity index 100% rename from runtime/test/wasm_test/store.ts rename to runtime/test/wasm_test/api_version_0_0_4/store.ts diff --git a/runtime/test/wasm_test/store.wasm b/runtime/test/wasm_test/api_version_0_0_4/store.wasm similarity index 100% rename from runtime/test/wasm_test/store.wasm rename to runtime/test/wasm_test/api_version_0_0_4/store.wasm diff --git a/runtime/test/wasm_test/string_to_number.ts b/runtime/test/wasm_test/api_version_0_0_4/string_to_number.ts similarity index 100% rename from runtime/test/wasm_test/string_to_number.ts rename to runtime/test/wasm_test/api_version_0_0_4/string_to_number.ts diff --git a/runtime/test/wasm_test/string_to_number.wasm b/runtime/test/wasm_test/api_version_0_0_4/string_to_number.wasm similarity index 100% rename from runtime/test/wasm_test/string_to_number.wasm rename to runtime/test/wasm_test/api_version_0_0_4/string_to_number.wasm diff --git a/runtime/test/wasm_test/api_version_0_0_5/abi_classes.ts b/runtime/test/wasm_test/api_version_0_0_5/abi_classes.ts new file mode 100644 index 00000000000..037e0ef2703 --- /dev/null +++ b/runtime/test/wasm_test/api_version_0_0_5/abi_classes.ts @@ -0,0 +1,62 @@ +export * from './common/global' +import { Address, Uint8, FixedBytes, Bytes, Payload, Value } from './common/types' + +// Clone the address to a new buffer, add 1 to the first and last bytes of the +// address and return the new address. +export function test_address(address: Address): Address { + let new_address = address.subarray(); + + // Add 1 to the first and last byte. + new_address[0] += 1; + new_address[address.length - 1] += 1; + + return changetype
(new_address) +} + +// Clone the Uint8 to a new buffer, add 1 to the first and last `u8`s and return +// the new Uint8 +export function test_uint(address: Uint8): Uint8 { + let new_address = address.subarray(); + + // Add 1 to the first byte. + new_address[0] += 1; + + return new_address +} + +// Return the string repeated twice. +export function repeat_twice(original: string): string { + return original.repeat(2) +} + +// Concatenate two byte sequences into a new one. +export function concat(bytes1: Bytes, bytes2: FixedBytes): Bytes { + let concated_buff = new ArrayBuffer(bytes1.byteLength + bytes2.byteLength); + let concated_buff_ptr = changetype(concated_buff); + + let bytes1_ptr = changetype(bytes1); + let bytes1_buff_ptr = load(bytes1_ptr); + + let bytes2_ptr = changetype(bytes2); + let bytes2_buff_ptr = load(bytes2_ptr); + + // Move bytes1. + memory.copy(concated_buff_ptr, bytes1_buff_ptr, bytes1.byteLength); + concated_buff_ptr += bytes1.byteLength + + // Move bytes2. + memory.copy(concated_buff_ptr, bytes2_buff_ptr, bytes2.byteLength); + + let new_typed_array = Uint8Array.wrap(concated_buff); + + return changetype(new_typed_array); +} + +export function test_array(strings: Array): Array { + strings.push("5") + return strings +} + +export function byte_array_third_quarter(bytes: Uint8Array): Uint8Array { + return bytes.subarray(bytes.length * 2/4, bytes.length * 3/4) +} diff --git a/runtime/test/wasm_test/api_version_0_0_5/abi_classes.wasm b/runtime/test/wasm_test/api_version_0_0_5/abi_classes.wasm new file mode 100644 index 00000000000..8a29ed1866e Binary files /dev/null and b/runtime/test/wasm_test/api_version_0_0_5/abi_classes.wasm differ diff --git a/runtime/test/wasm_test/api_version_0_0_5/abi_store_value.ts b/runtime/test/wasm_test/api_version_0_0_5/abi_store_value.ts new file mode 100644 index 00000000000..4a2a58b99d7 --- /dev/null +++ b/runtime/test/wasm_test/api_version_0_0_5/abi_store_value.ts @@ -0,0 +1,76 @@ +export * from './common/global' +import { BigInt, BigDecimal, Bytes, Value, ValueKind } from './common/types' + +export function value_from_string(str: string): Value { + let token = new Value(); + token.kind = ValueKind.STRING; + token.data = changetype(str); + return token +} + +export function value_from_int(int: i32): Value { + let value = new Value(); + value.kind = ValueKind.INT; + value.data = int as u64 + return value +} + +export function value_from_big_decimal(float: BigInt): Value { + let value = new Value(); + value.kind = ValueKind.BIG_DECIMAL; + value.data = changetype(float); + return value +} + +export function value_from_bool(bool: boolean): Value { + let value = new Value(); + value.kind = ValueKind.BOOL; + value.data = bool ? 1 : 0; + return value +} + +export function array_from_values(str: string, i: i32): Value { + let array = new Array(); + array.push(value_from_string(str)); + array.push(value_from_int(i)); + + let value = new Value(); + value.kind = ValueKind.ARRAY; + value.data = changetype(array); + return value +} + +export function value_null(): Value { + let value = new Value(); + value.kind = ValueKind.NULL; + return value +} + +export function value_from_bytes(bytes: Bytes): Value { + let value = new Value(); + value.kind = ValueKind.BYTES; + value.data = changetype(bytes); + return value +} + +export function value_from_bigint(bigint: BigInt): Value { + let value = new Value(); + value.kind = ValueKind.BIG_INT; + value.data = changetype(bigint); + return value +} + +export function value_from_array(array: Array): Value { + let value = new Value() + value.kind = ValueKind.ARRAY + value.data = changetype(array) + return value +} + +// Test that this does not cause undefined behaviour in Rust. +export function invalid_discriminant(): Value { + let token = new Value(); + token.kind = 70; + token.data = changetype("blebers"); + return token +} diff --git a/runtime/test/wasm_test/api_version_0_0_5/abi_store_value.wasm b/runtime/test/wasm_test/api_version_0_0_5/abi_store_value.wasm new file mode 100644 index 00000000000..5ac9f91d55d Binary files /dev/null and b/runtime/test/wasm_test/api_version_0_0_5/abi_store_value.wasm differ diff --git a/runtime/test/wasm_test/api_version_0_0_5/abi_token.ts b/runtime/test/wasm_test/api_version_0_0_5/abi_token.ts new file mode 100644 index 00000000000..5a170b5fb77 --- /dev/null +++ b/runtime/test/wasm_test/api_version_0_0_5/abi_token.ts @@ -0,0 +1,91 @@ +export * from './common/global' +import { Address, Bytes, Token, TokenKind, Int64, Uint64 } from './common/types' + +export function token_to_address(token: Token): Address { + assert(token.kind == TokenKind.ADDRESS, "Token is not an address."); + return changetype
(token.data as u32); +} + +export function token_to_bytes(token: Token): Bytes { + assert(token.kind == TokenKind.FIXED_BYTES + || token.kind == TokenKind.BYTES, "Token is not bytes.") + return changetype(token.data as u32) +} + +export function token_to_int(token: Token): Int64 { + assert(token.kind == TokenKind.INT + || token.kind == TokenKind.UINT, "Token is not an int or uint.") + return changetype(token.data as u32) +} + +export function token_to_uint(token: Token): Uint64 { + assert(token.kind == TokenKind.INT + || token.kind == TokenKind.UINT, "Token is not an int or uint.") + return changetype(token.data as u32) +} + +export function token_to_bool(token: Token): boolean { + assert(token.kind == TokenKind.BOOL, "Token is not a boolean.") + return token.data != 0 +} + +export function token_to_string(token: Token): string { + assert(token.kind == TokenKind.STRING, "Token is not a string.") + return changetype(token.data as u32) +} + +export function token_to_array(token: Token): Array { + assert(token.kind == TokenKind.FIXED_ARRAY || + token.kind == TokenKind.ARRAY, "Token is not an array.") + return changetype>(token.data as u32) +} + + +export function token_from_address(address: Address): Token { + let token = new Token(); + token.kind = TokenKind.ADDRESS; + token.data = changetype(address); + return token +} + +export function token_from_bytes(bytes: Bytes): Token { + let token = new Token(); + token.kind = TokenKind.BYTES; + token.data = changetype(bytes); + return token +} + +export function token_from_int(int: Int64): Token { + let token = new Token(); + token.kind = TokenKind.INT; + token.data = changetype(int); + return token +} + +export function token_from_uint(uint: Uint64): Token { + let token = new Token(); + token.kind = TokenKind.UINT; + token.data = changetype(uint); + return token +} + +export function token_from_bool(bool: boolean): Token { + let token = new Token(); + token.kind = TokenKind.BOOL; + token.data = bool as u32; + return token +} + +export function token_from_string(str: string): Token { + let token = new Token(); + token.kind = TokenKind.STRING; + token.data = changetype(str); + return token +} + +export function token_from_array(array: Token): Token { + let token = new Token(); + token.kind = TokenKind.ARRAY; + token.data = changetype(array); + return token +} diff --git a/runtime/test/wasm_test/api_version_0_0_5/abi_token.wasm b/runtime/test/wasm_test/api_version_0_0_5/abi_token.wasm new file mode 100644 index 00000000000..0cd6cd2e76f Binary files /dev/null and b/runtime/test/wasm_test/api_version_0_0_5/abi_token.wasm differ diff --git a/runtime/test/wasm_test/api_version_0_0_5/abort.ts b/runtime/test/wasm_test/api_version_0_0_5/abort.ts new file mode 100644 index 00000000000..88f7dee9df3 --- /dev/null +++ b/runtime/test/wasm_test/api_version_0_0_5/abort.ts @@ -0,0 +1,5 @@ +export * from './common/global' + +export function abort(): void { + assert(false, "not true") +} diff --git a/runtime/test/wasm_test/api_version_0_0_5/abort.wasm b/runtime/test/wasm_test/api_version_0_0_5/abort.wasm new file mode 100644 index 00000000000..b8e08d7f13c Binary files /dev/null and b/runtime/test/wasm_test/api_version_0_0_5/abort.wasm differ diff --git a/runtime/test/wasm_test/api_version_0_0_5/add_fn.wasm b/runtime/test/wasm_test/api_version_0_0_5/add_fn.wasm new file mode 100755 index 00000000000..29d105018f5 Binary files /dev/null and b/runtime/test/wasm_test/api_version_0_0_5/add_fn.wasm differ diff --git a/runtime/test/wasm_test/api_version_0_0_5/allocate_global.ts b/runtime/test/wasm_test/api_version_0_0_5/allocate_global.ts new file mode 100644 index 00000000000..c1f0a90e3ff --- /dev/null +++ b/runtime/test/wasm_test/api_version_0_0_5/allocate_global.ts @@ -0,0 +1,13 @@ +export * from './common/global' +import { BigInt } from './common/types' + +let globalOne = bigInt.fromString("1") + +declare namespace bigInt { + function fromString(s: string): BigInt +} + +export function assert_global_works(): void { + let localOne = bigInt.fromString("1") + assert(globalOne != localOne) +} diff --git a/runtime/test/wasm_test/api_version_0_0_5/allocate_global.wasm b/runtime/test/wasm_test/api_version_0_0_5/allocate_global.wasm new file mode 100644 index 00000000000..779d93052d4 Binary files /dev/null and b/runtime/test/wasm_test/api_version_0_0_5/allocate_global.wasm differ diff --git a/runtime/test/wasm_test/api_version_0_0_5/big_int_arithmetic.ts b/runtime/test/wasm_test/api_version_0_0_5/big_int_arithmetic.ts new file mode 100644 index 00000000000..6cea38e144d --- /dev/null +++ b/runtime/test/wasm_test/api_version_0_0_5/big_int_arithmetic.ts @@ -0,0 +1,30 @@ +export * from './common/global' +import { BigInt } from './common/types' + +declare namespace bigInt { + function plus(x: BigInt, y: BigInt): BigInt + function minus(x: BigInt, y: BigInt): BigInt + function times(x: BigInt, y: BigInt): BigInt + function dividedBy(x: BigInt, y: BigInt): BigInt + function mod(x: BigInt, y: BigInt): BigInt +} + +export function plus(x: BigInt, y: BigInt): BigInt { + return bigInt.plus(x, y) +} + +export function minus(x: BigInt, y: BigInt): BigInt { + return bigInt.minus(x, y) +} + +export function times(x: BigInt, y: BigInt): BigInt { + return bigInt.times(x, y) +} + +export function dividedBy(x: BigInt, y: BigInt): BigInt { + return bigInt.dividedBy(x, y) +} + +export function mod(x: BigInt, y: BigInt): BigInt { + return bigInt.mod(x, y) +} diff --git a/runtime/test/wasm_test/api_version_0_0_5/big_int_arithmetic.wasm b/runtime/test/wasm_test/api_version_0_0_5/big_int_arithmetic.wasm new file mode 100644 index 00000000000..a69c40525b6 Binary files /dev/null and b/runtime/test/wasm_test/api_version_0_0_5/big_int_arithmetic.wasm differ diff --git a/runtime/test/wasm_test/api_version_0_0_5/big_int_to_hex.ts b/runtime/test/wasm_test/api_version_0_0_5/big_int_to_hex.ts new file mode 100644 index 00000000000..4da12651dc0 --- /dev/null +++ b/runtime/test/wasm_test/api_version_0_0_5/big_int_to_hex.ts @@ -0,0 +1,9 @@ +export * from './common/global' + +declare namespace typeConversion { + function bigIntToHex(n: Uint8Array): string +} + +export function big_int_to_hex(n: Uint8Array): string { + return typeConversion.bigIntToHex(n) +} diff --git a/runtime/test/wasm_test/api_version_0_0_5/big_int_to_hex.wasm b/runtime/test/wasm_test/api_version_0_0_5/big_int_to_hex.wasm new file mode 100644 index 00000000000..fae821a5d44 Binary files /dev/null and b/runtime/test/wasm_test/api_version_0_0_5/big_int_to_hex.wasm differ diff --git a/runtime/test/wasm_test/api_version_0_0_5/big_int_to_string.ts b/runtime/test/wasm_test/api_version_0_0_5/big_int_to_string.ts new file mode 100644 index 00000000000..8c9c5eb7d88 --- /dev/null +++ b/runtime/test/wasm_test/api_version_0_0_5/big_int_to_string.ts @@ -0,0 +1,9 @@ +export * from './common/global' + +declare namespace typeConversion { + function bigIntToString(n: Uint8Array): string +} + +export function big_int_to_string(n: Uint8Array): string { + return typeConversion.bigIntToString(n) +} diff --git a/runtime/test/wasm_test/api_version_0_0_5/big_int_to_string.wasm b/runtime/test/wasm_test/api_version_0_0_5/big_int_to_string.wasm new file mode 100644 index 00000000000..137414ef4e8 Binary files /dev/null and b/runtime/test/wasm_test/api_version_0_0_5/big_int_to_string.wasm differ diff --git a/runtime/test/wasm_test/api_version_0_0_5/bytes_to_base58.ts b/runtime/test/wasm_test/api_version_0_0_5/bytes_to_base58.ts new file mode 100644 index 00000000000..38c956d2aeb --- /dev/null +++ b/runtime/test/wasm_test/api_version_0_0_5/bytes_to_base58.ts @@ -0,0 +1,9 @@ +export * from './common/global' + +declare namespace typeConversion { + function bytesToBase58(n: Uint8Array): string +} + +export function bytes_to_base58(n: Uint8Array): string { + return typeConversion.bytesToBase58(n) +} diff --git a/runtime/test/wasm_test/api_version_0_0_5/bytes_to_base58.wasm b/runtime/test/wasm_test/api_version_0_0_5/bytes_to_base58.wasm new file mode 100644 index 00000000000..851bb6046cd Binary files /dev/null and b/runtime/test/wasm_test/api_version_0_0_5/bytes_to_base58.wasm differ diff --git a/runtime/test/wasm_test/api_version_0_0_5/common/global.ts b/runtime/test/wasm_test/api_version_0_0_5/common/global.ts new file mode 100644 index 00000000000..7979b147e3c --- /dev/null +++ b/runtime/test/wasm_test/api_version_0_0_5/common/global.ts @@ -0,0 +1,173 @@ +__alloc(0); + +import { BigDecimal, TypedMapEntry, Entity, TypedMap, Result, Wrapped, JSONValue, Value, Token } from './types' + +export enum TypeId { + String = 0, + ArrayBuffer = 1, + Int8Array = 2, + Int16Array = 3, + Int32Array = 4, + Int64Array = 5, + Uint8Array = 6, + Uint16Array = 7, + Uint32Array = 8, + Uint64Array = 9, + Float32Array = 10, + Float64Array = 11, + BigDecimal = 12, + ArrayBool = 13, + ArrayUint8Array = 14, + ArrayEthereumValue = 15, + ArrayStoreValue = 16, + ArrayJsonValue = 17, + ArrayString = 18, + ArrayEventParam = 19, + ArrayTypedMapEntryStringJsonValue = 20, + ArrayTypedMapEntryStringStoreValue = 21, + SmartContractCall = 22, + EventParam = 23, + // EthereumTransaction = 24, + // EthereumBlock = 25, + // EthereumCall = 26, + WrappedTypedMapStringJsonValue = 27, + WrappedBool = 28, + WrappedJsonValue = 29, + EthereumValue = 30, + StoreValue = 31, + JsonValue = 32, + // EthereumEvent = 33, + TypedMapEntryStringStoreValue = 34, + TypedMapEntryStringJsonValue = 35, + TypedMapStringStoreValue = 36, + TypedMapStringJsonValue = 37, + TypedMapStringTypedMapStringJsonValue = 38, + ResultTypedMapStringJsonValueBool = 39, + ResultJsonValueBool = 40, + ArrayU8 = 41, + ArrayU16 = 42, + ArrayU32 = 43, + ArrayU64 = 44, + ArrayI8 = 45, + ArrayI16 = 46, + ArrayI32 = 47, + ArrayI64 = 48, + ArrayF32 = 49, + ArrayF64 = 50, + ArrayBigDecimal = 51, +} + +export function id_of_type(typeId: TypeId): usize { + switch (typeId) { + case TypeId.String: + return idof() + case TypeId.ArrayBuffer: + return idof() + case TypeId.Int8Array: + return idof() + case TypeId.Int16Array: + return idof() + case TypeId.Int32Array: + return idof() + case TypeId.Int64Array: + return idof() + case TypeId.Uint8Array: + return idof() + case TypeId.Uint16Array: + return idof() + case TypeId.Uint32Array: + return idof() + case TypeId.Uint64Array: + return idof() + case TypeId.Float32Array: + return idof() + case TypeId.Float64Array: + return idof() + case TypeId.BigDecimal: + return idof() + case TypeId.ArrayBool: + return idof>() + case TypeId.ArrayUint8Array: + return idof>() + case TypeId.ArrayEthereumValue: + return idof>() + case TypeId.ArrayStoreValue: + return idof>() + case TypeId.ArrayJsonValue: + return idof>() + case TypeId.ArrayString: + return idof>() + // case TypeId.ArrayEventParam: + // return idof>() + case TypeId.ArrayTypedMapEntryStringJsonValue: + return idof>>() + case TypeId.ArrayTypedMapEntryStringStoreValue: + return idof>() + case TypeId.WrappedTypedMapStringJsonValue: + return idof>>() + case TypeId.WrappedBool: + return idof>() + case TypeId.WrappedJsonValue: + return idof>() + // case TypeId.SmartContractCall: + // return idof() + // case TypeId.EventParam: + // return idof() + // case TypeId.EthereumTransaction: + // return idof() + // case TypeId.EthereumBlock: + // return idof() + // case TypeId.EthereumCall: + // return idof() + case TypeId.EthereumValue: + return idof() + case TypeId.StoreValue: + return idof() + case TypeId.JsonValue: + return idof() + // case TypeId.EthereumEvent: + // return idof() + case TypeId.TypedMapEntryStringStoreValue: + return idof() + case TypeId.TypedMapEntryStringJsonValue: + return idof>() + case TypeId.TypedMapStringStoreValue: + return idof>() + case TypeId.TypedMapStringJsonValue: + return idof>() + case TypeId.TypedMapStringTypedMapStringJsonValue: + return idof>>() + case TypeId.ResultTypedMapStringJsonValueBool: + return idof, boolean>>() + case TypeId.ResultJsonValueBool: + return idof>() + case TypeId.ArrayU8: + return idof>() + case TypeId.ArrayU16: + return idof>() + case TypeId.ArrayU32: + return idof>() + case TypeId.ArrayU64: + return idof>() + case TypeId.ArrayI8: + return idof>() + case TypeId.ArrayI16: + return idof>() + case TypeId.ArrayI32: + return idof>() + case TypeId.ArrayI64: + return idof>() + case TypeId.ArrayF32: + return idof>() + case TypeId.ArrayF64: + return idof>() + case TypeId.ArrayBigDecimal: + return idof>() + default: + return 0 + } +} + +export function allocate(size: usize): usize { + return __alloc(size) +} diff --git a/runtime/test/wasm_test/api_version_0_0_5/common/types.ts b/runtime/test/wasm_test/api_version_0_0_5/common/types.ts new file mode 100644 index 00000000000..c90ab655a29 --- /dev/null +++ b/runtime/test/wasm_test/api_version_0_0_5/common/types.ts @@ -0,0 +1,560 @@ +/** A dynamically-sized byte array. */ +export class Bytes extends ByteArray { } + +/** An Ethereum address (20 bytes). */ +export class Address extends Bytes { + static fromString(s: string): Address { + return typeConversion.stringToH160(s) as Address + } +} + +// Sequence of 20 `u8`s. +// export type Address = Uint8Array; + +// Sequence of 32 `u8`s. +export type Uint8 = Uint8Array; + +// Sequences of `u8`s. +export type FixedBytes = Uint8Array; +// export type Bytes = Uint8Array; + +/** + * Enum for supported value types. + */ +export enum ValueKind { + STRING = 0, + INT = 1, + BIG_DECIMAL = 2, + BOOL = 3, + ARRAY = 4, + NULL = 5, + BYTES = 6, + BIG_INT = 7, +} +// Big enough to fit any pointer or native `this.data`. +export type Payload = u64 +/** + * A dynamically typed value. + */ +export class Value { + kind: ValueKind + data: Payload + + toAddress(): Address { + assert(this.kind == ValueKind.BYTES, 'Value is not an address.') + return changetype
(this.data as u32) + } + + toBoolean(): boolean { + if (this.kind == ValueKind.NULL) { + return false; + } + assert(this.kind == ValueKind.BOOL, 'Value is not a boolean.') + return this.data != 0 + } + + toBytes(): Bytes { + assert(this.kind == ValueKind.BYTES, 'Value is not a byte array.') + return changetype(this.data as u32) + } + + toI32(): i32 { + if (this.kind == ValueKind.NULL) { + return 0; + } + assert(this.kind == ValueKind.INT, 'Value is not an i32.') + return this.data as i32 + } + + toString(): string { + assert(this.kind == ValueKind.STRING, 'Value is not a string.') + return changetype(this.data as u32) + } + + toBigInt(): BigInt { + assert(this.kind == ValueKind.BIGINT, 'Value is not a BigInt.') + return changetype(this.data as u32) + } + + toBigDecimal(): BigDecimal { + assert(this.kind == ValueKind.BIGDECIMAL, 'Value is not a BigDecimal.') + return changetype(this.data as u32) + } + + toArray(): Array { + assert(this.kind == ValueKind.ARRAY, 'Value is not an array.') + return changetype>(this.data as u32) + } + + toBooleanArray(): Array { + let values = this.toArray() + let output = new Array(values.length) + for (let i: i32; i < values.length; i++) { + output[i] = values[i].toBoolean() + } + return output + } + + toBytesArray(): Array { + let values = this.toArray() + let output = new Array(values.length) + for (let i: i32 = 0; i < values.length; i++) { + output[i] = values[i].toBytes() + } + return output + } + + toStringArray(): Array { + let values = this.toArray() + let output = new Array(values.length) + for (let i: i32 = 0; i < values.length; i++) { + output[i] = values[i].toString() + } + return output + } + + toI32Array(): Array { + let values = this.toArray() + let output = new Array(values.length) + for (let i: i32 = 0; i < values.length; i++) { + output[i] = values[i].toI32() + } + return output + } + + toBigIntArray(): Array { + let values = this.toArray() + let output = new Array(values.length) + for (let i: i32 = 0; i < values.length; i++) { + output[i] = values[i].toBigInt() + } + return output + } + + toBigDecimalArray(): Array { + let values = this.toArray() + let output = new Array(values.length) + for (let i: i32 = 0; i < values.length; i++) { + output[i] = values[i].toBigDecimal() + } + return output + } + + static fromBooleanArray(input: Array): Value { + let output = new Array(input.length) + for (let i: i32 = 0; i < input.length; i++) { + output[i] = Value.fromBoolean(input[i]) + } + return Value.fromArray(output) + } + + static fromBytesArray(input: Array): Value { + let output = new Array(input.length) + for (let i: i32 = 0; i < input.length; i++) { + output[i] = Value.fromBytes(input[i]) + } + return Value.fromArray(output) + } + + static fromI32Array(input: Array): Value { + let output = new Array(input.length) + for (let i: i32 = 0; i < input.length; i++) { + output[i] = Value.fromI32(input[i]) + } + return Value.fromArray(output) + } + + static fromBigIntArray(input: Array): Value { + let output = new Array(input.length) + for (let i: i32 = 0; i < input.length; i++) { + output[i] = Value.fromBigInt(input[i]) + } + return Value.fromArray(output) + } + + static fromBigDecimalArray(input: Array): Value { + let output = new Array(input.length) + for (let i: i32 = 0; i < input.length; i++) { + output[i] = Value.fromBigDecimal(input[i]) + } + return Value.fromArray(output) + } + + static fromStringArray(input: Array): Value { + let output = new Array(input.length) + for (let i: i32 = 0; i < input.length; i++) { + output[i] = Value.fromString(input[i]) + } + return Value.fromArray(output) + } + + static fromArray(input: Array): Value { + let value = new Value() + value.kind = ValueKind.ARRAY + value.data = input as u64 + return value + } + + static fromBigInt(n: BigInt): Value { + let value = new Value() + value.kind = ValueKind.BIGINT + value.data = n as u64 + return value + } + + static fromBoolean(b: boolean): Value { + let value = new Value() + value.kind = ValueKind.BOOL + value.data = b ? 1 : 0 + return value + } + + static fromBytes(bytes: Bytes): Value { + let value = new Value() + value.kind = ValueKind.BYTES + value.data = bytes as u64 + return value + } + + static fromNull(): Value { + let value = new Value() + value.kind = ValueKind.NULL + return value + } + + static fromI32(n: i32): Value { + let value = new Value() + value.kind = ValueKind.INT + value.data = n as u64 + return value + } + + static fromString(s: string): Value { + let value = new Value() + value.kind = ValueKind.STRING + value.data = changetype(s) + return value + } + + static fromBigDecimal(n: BigDecimal): Value { + let value = new Value() + value.kind = ValueKind.BIGDECIMAL + value.data = n as u64 + return value + } +} + +/** An arbitrary size integer represented as an array of bytes. */ +export class BigInt extends Uint8Array { + toHex(): string { + return typeConversion.bigIntToHex(this) + } + + toHexString(): string { + return typeConversion.bigIntToHex(this) + } + + toString(): string { + return typeConversion.bigIntToString(this) + } + + static fromI32(x: i32): BigInt { + return typeConversion.i32ToBigInt(x) as BigInt + } + + toI32(): i32 { + return typeConversion.bigIntToI32(this) + } + + @operator('+') + plus(other: BigInt): BigInt { + return bigInt.plus(this, other) + } + + @operator('-') + minus(other: BigInt): BigInt { + return bigInt.minus(this, other) + } + + @operator('*') + times(other: BigInt): BigInt { + return bigInt.times(this, other) + } + + @operator('/') + div(other: BigInt): BigInt { + return bigInt.dividedBy(this, other) + } + + divDecimal(other: BigDecimal): BigDecimal { + return bigInt.dividedByDecimal(this, other) + } + + @operator('%') + mod(other: BigInt): BigInt { + return bigInt.mod(this, other) + } + + @operator('==') + equals(other: BigInt): boolean { + if (this.length !== other.length) { + return false; + } + for (let i = 0; i < this.length; i++) { + if (this[i] !== other[i]) { + return false; + } + } + return true; + } + + toBigDecimal(): BigDecimal { + return new BigDecimal(this) + } +} + +export class BigDecimal { + exp!: BigInt + digits!: BigInt + + constructor(bigInt: BigInt) { + this.digits = bigInt + this.exp = BigInt.fromI32(0) + } + + static fromString(s: string): BigDecimal { + return bigDecimal.fromString(s) + } + + toString(): string { + return bigDecimal.toString(this) + } + + truncate(decimals: i32): BigDecimal { + let digitsRightOfZero = this.digits.toString().length + this.exp.toI32() + let newDigitLength = decimals + digitsRightOfZero + let truncateLength = this.digits.toString().length - newDigitLength + if (truncateLength < 0) { + return this + } else { + for (let i = 0; i < truncateLength; i++) { + this.digits = this.digits.div(BigInt.fromI32(10)) + } + this.exp = BigInt.fromI32(decimals* -1) + return this + } + } + + @operator('+') + plus(other: BigDecimal): BigDecimal { + return bigDecimal.plus(this, other) + } + + @operator('-') + minus(other: BigDecimal): BigDecimal { + return bigDecimal.minus(this, other) + } + + @operator('*') + times(other: BigDecimal): BigDecimal { + return bigDecimal.times(this, other) + } + + @operator('/') + div(other: BigDecimal): BigDecimal { + return bigDecimal.dividedBy(this, other) + } + + @operator('==') + equals(other: BigDecimal): boolean { + return bigDecimal.equals(this, other) + } +} + +export enum TokenKind { + ADDRESS = 0, + FIXED_BYTES = 1, + BYTES = 2, + INT = 3, + UINT = 4, + BOOL = 5, + STRING = 6, + FIXED_ARRAY = 7, + ARRAY = 8 +} +export class Token { + kind: TokenKind + data: Payload +} + +// Sequence of 4 `u64`s. +export type Int64 = Uint64Array; +export type Uint64 = Uint64Array; + +/** + * TypedMap entry. + */ +export class TypedMapEntry { + key: K + value: V + + constructor(key: K, value: V) { + this.key = key + this.value = value + } +} + +/** Typed map */ +export class TypedMap { + entries: Array> + + constructor() { + this.entries = new Array>(0) + } + + set(key: K, value: V): void { + let entry = this.getEntry(key) + if (entry !== null) { + entry.value = value + } else { + let entry = new TypedMapEntry(key, value) + this.entries.push(entry) + } + } + + getEntry(key: K): TypedMapEntry | null { + for (let i: i32 = 0; i < this.entries.length; i++) { + if (this.entries[i].key == key) { + return this.entries[i] + } + } + return null + } + + get(key: K): V | null { + for (let i: i32 = 0; i < this.entries.length; i++) { + if (this.entries[i].key == key) { + return this.entries[i].value + } + } + return null + } + + isSet(key: K): bool { + for (let i: i32 = 0; i < this.entries.length; i++) { + if (this.entries[i].key == key) { + return true + } + } + return false + } +} + +/** + * Common representation for entity data, storing entity attributes + * as `string` keys and the attribute values as dynamically-typed + * `Value` objects. + */ +export class Entity extends TypedMap { + unset(key: string): void { + this.set(key, Value.fromNull()) + } + + /** Assigns properties from sources to this Entity in right-to-left order */ + merge(sources: Array): Entity { + var target = this + for (let i = 0; i < sources.length; i++) { + let entries = sources[i].entries + for (let j = 0; j < entries.length; j++) { + target.set(entries[j].key, entries[j].value) + } + } + return target + } +} + +/** Type hint for JSON values. */ +export enum JSONValueKind { + NULL = 0, + BOOL = 1, + NUMBER = 2, + STRING = 3, + ARRAY = 4, + OBJECT = 5, +} + +/** + * Pointer type for JSONValue data. + * + * Big enough to fit any pointer or native `this.data`. + */ +export type JSONValuePayload = u64 +export class JSONValue { + kind: JSONValueKind + data: JSONValuePayload + + toString(): string { + assert(this.kind == JSONValueKind.STRING, 'JSON value is not a string.') + return changetype(this.data as u32) + } + + toObject(): TypedMap { + assert(this.kind == JSONValueKind.OBJECT, 'JSON value is not an object.') + return changetype>(this.data as u32) + } +} + +export class Wrapped { + inner: T; + + constructor(inner: T) { + this.inner = inner; + } +} + +export class Result { + _value: Wrapped | null; + _error: Wrapped | null; + + get isOk(): boolean { + return this._value !== null; + } + + get isError(): boolean { + return this._error !== null; + } + + get value(): V { + assert(this._value != null, "Trying to get a value from an error result"); + return (this._value as Wrapped).inner; + } + + get error(): E { + assert( + this._error != null, + "Trying to get an error from a successful result" + ); + return (this._error as Wrapped).inner; + } +} + +/** + * Byte array + */ +class ByteArray extends Uint8Array { + toHex(): string { + return typeConversion.bytesToHex(this) + } + + toHexString(): string { + return typeConversion.bytesToHex(this) + } + + toString(): string { + return typeConversion.bytesToString(this) + } + + toBase58(): string { + return typeConversion.bytesToBase58(this) + } +} diff --git a/runtime/test/wasm_test/api_version_0_0_5/contract_calls.ts b/runtime/test/wasm_test/api_version_0_0_5/contract_calls.ts new file mode 100644 index 00000000000..223ea0caf1a --- /dev/null +++ b/runtime/test/wasm_test/api_version_0_0_5/contract_calls.ts @@ -0,0 +1,10 @@ +export * from './common/global' +import { Address } from './common/types' + +export declare namespace ethereum { + function call(call: Address): Array
| null +} + +export function callContract(address: Address): void { + ethereum.call(address) +} diff --git a/runtime/test/wasm_test/api_version_0_0_5/contract_calls.wasm b/runtime/test/wasm_test/api_version_0_0_5/contract_calls.wasm new file mode 100644 index 00000000000..b2ab791af9c Binary files /dev/null and b/runtime/test/wasm_test/api_version_0_0_5/contract_calls.wasm differ diff --git a/runtime/test/wasm_test/api_version_0_0_5/crypto.ts b/runtime/test/wasm_test/api_version_0_0_5/crypto.ts new file mode 100644 index 00000000000..89a1781a1ea --- /dev/null +++ b/runtime/test/wasm_test/api_version_0_0_5/crypto.ts @@ -0,0 +1,9 @@ +export * from './common/global' + +declare namespace crypto { + function keccak256(input: Uint8Array): Uint8Array +} + +export function hash(input: Uint8Array): Uint8Array { + return crypto.keccak256(input) +} diff --git a/runtime/test/wasm_test/api_version_0_0_5/crypto.wasm b/runtime/test/wasm_test/api_version_0_0_5/crypto.wasm new file mode 100644 index 00000000000..393c57a967c Binary files /dev/null and b/runtime/test/wasm_test/api_version_0_0_5/crypto.wasm differ diff --git a/runtime/test/wasm_test/api_version_0_0_5/data_source_create.ts b/runtime/test/wasm_test/api_version_0_0_5/data_source_create.ts new file mode 100644 index 00000000000..c071dd84308 --- /dev/null +++ b/runtime/test/wasm_test/api_version_0_0_5/data_source_create.ts @@ -0,0 +1,9 @@ +export * from './common/global' + +declare namespace dataSource { + function create(name: string, params: Array): void +} + +export function dataSourceCreate(name: string, params: Array): void { + dataSource.create(name, params) +} diff --git a/runtime/test/wasm_test/api_version_0_0_5/data_source_create.wasm b/runtime/test/wasm_test/api_version_0_0_5/data_source_create.wasm new file mode 100644 index 00000000000..6b3f7e69429 Binary files /dev/null and b/runtime/test/wasm_test/api_version_0_0_5/data_source_create.wasm differ diff --git a/runtime/test/wasm_test/api_version_0_0_5/ens_name_by_hash.ts b/runtime/test/wasm_test/api_version_0_0_5/ens_name_by_hash.ts new file mode 100644 index 00000000000..8f11518b00e --- /dev/null +++ b/runtime/test/wasm_test/api_version_0_0_5/ens_name_by_hash.ts @@ -0,0 +1,9 @@ +export * from './common/global' + +declare namespace ens { + function nameByHash(hash: string): string|null +} + +export function nameByHash(hash: string): string|null { + return ens.nameByHash(hash) +} diff --git a/runtime/test/wasm_test/api_version_0_0_5/ens_name_by_hash.wasm b/runtime/test/wasm_test/api_version_0_0_5/ens_name_by_hash.wasm new file mode 100644 index 00000000000..6fe996026e1 Binary files /dev/null and b/runtime/test/wasm_test/api_version_0_0_5/ens_name_by_hash.wasm differ diff --git a/runtime/test/wasm_test/api_version_0_0_5/ipfs_cat.ts b/runtime/test/wasm_test/api_version_0_0_5/ipfs_cat.ts new file mode 100644 index 00000000000..a66540b9ac5 --- /dev/null +++ b/runtime/test/wasm_test/api_version_0_0_5/ipfs_cat.ts @@ -0,0 +1,17 @@ +export * from './common/global' + +declare namespace typeConversion { + function bytesToString(bytes: Uint8Array): string +} + +declare namespace ipfs { + function cat(hash: String): Uint8Array +} + +export function ipfsCatString(hash: string): string { + return typeConversion.bytesToString(ipfs.cat(hash)) +} + +export function ipfsCat(hash: string): Uint8Array { + return ipfs.cat(hash) +} diff --git a/runtime/test/wasm_test/api_version_0_0_5/ipfs_cat.wasm b/runtime/test/wasm_test/api_version_0_0_5/ipfs_cat.wasm new file mode 100644 index 00000000000..b803eb68343 Binary files /dev/null and b/runtime/test/wasm_test/api_version_0_0_5/ipfs_cat.wasm differ diff --git a/runtime/test/wasm_test/api_version_0_0_5/ipfs_map.ts b/runtime/test/wasm_test/api_version_0_0_5/ipfs_map.ts new file mode 100644 index 00000000000..b52d31b6563 --- /dev/null +++ b/runtime/test/wasm_test/api_version_0_0_5/ipfs_map.ts @@ -0,0 +1,41 @@ +export * from './common/global' +import { Value, ValueKind, TypedMapEntry, TypedMap, Entity, JSONValueKind, JSONValue } from './common/types' + +/* + * Declarations copied from graph-ts/input.ts and edited for brevity + */ + +declare namespace store { + function set(entity: string, id: string, data: Entity): void +} + +/* + * Actual setup for the test + */ +declare namespace ipfs { + function map(hash: String, callback: String, userData: Value, flags: String[]): void +} + +export function echoToStore(data: JSONValue, userData: Value): void { + // expect a map of the form { "id": "anId", "value": "aValue" } + let map = data.toObject(); + + let id = map.get("id"); + let value = map.get("value"); + + assert(id !== null, "'id' should not be null"); + assert(value !== null, "'value' should not be null"); + + let stringId = id!.toString(); + let stringValue = value!.toString(); + + let entity = new Entity(); + entity.set("id", Value.fromString(stringId)); + entity.set("value", Value.fromString(stringValue)); + entity.set("extra", userData); + store.set("Thing", stringId, entity); +} + +export function ipfsMap(hash: string, userData: string): void { + ipfs.map(hash, "echoToStore", Value.fromString(userData), ["json"]) +} diff --git a/runtime/test/wasm_test/api_version_0_0_5/ipfs_map.wasm b/runtime/test/wasm_test/api_version_0_0_5/ipfs_map.wasm new file mode 100644 index 00000000000..e4b5cdc4595 Binary files /dev/null and b/runtime/test/wasm_test/api_version_0_0_5/ipfs_map.wasm differ diff --git a/runtime/test/wasm_test/api_version_0_0_5/json_parsing.ts b/runtime/test/wasm_test/api_version_0_0_5/json_parsing.ts new file mode 100644 index 00000000000..d1d27ffb0f9 --- /dev/null +++ b/runtime/test/wasm_test/api_version_0_0_5/json_parsing.ts @@ -0,0 +1,49 @@ +import { JSONValue, JSONValueKind, Bytes, Wrapped, Result } from './common/types' + +enum IndexForAscTypeId { + STRING = 0, + ARRAY_BUFFER = 1, + UINT8_ARRAY = 6, + WRAPPED_BOOL = 28, + WRAPPED_JSON_VALUE = 29, + JSON_VALUE = 32, + RESULT_JSON_VALUE_BOOL = 40, +} + +export function id_of_type(type_id_index: IndexForAscTypeId): usize { + switch (type_id_index) { + case IndexForAscTypeId.STRING: + return idof(); + case IndexForAscTypeId.ARRAY_BUFFER: + return idof(); + case IndexForAscTypeId.UINT8_ARRAY: + return idof(); + case IndexForAscTypeId.WRAPPED_BOOL: + return idof>(); + case IndexForAscTypeId.WRAPPED_JSON_VALUE: + return idof>(); + case IndexForAscTypeId.JSON_VALUE: + return idof(); + case IndexForAscTypeId.RESULT_JSON_VALUE_BOOL: + return idof>(); + default: + return 0; + } +} + +export function allocate(n: usize): usize { + return __alloc(n); +} + +declare namespace json { + function try_fromBytes(data: Bytes): Result; +} + +export function handleJsonError(data: Bytes): string { + let result = json.try_fromBytes(data); + if (result.isOk) { + return "OK: " + result.value.toString() + ", ERROR: " + (result.isError ? "true" : "false"); + } else { + return "ERROR: " + (result.error ? "true" : "false"); + } +} diff --git a/runtime/test/wasm_test/api_version_0_0_5/json_parsing.wasm b/runtime/test/wasm_test/api_version_0_0_5/json_parsing.wasm new file mode 100644 index 00000000000..99479a56624 Binary files /dev/null and b/runtime/test/wasm_test/api_version_0_0_5/json_parsing.wasm differ diff --git a/runtime/test/wasm_test/api_version_0_0_5/non_terminating.ts b/runtime/test/wasm_test/api_version_0_0_5/non_terminating.ts new file mode 100644 index 00000000000..77866c3d317 --- /dev/null +++ b/runtime/test/wasm_test/api_version_0_0_5/non_terminating.ts @@ -0,0 +1,10 @@ +export * from './common/global' + +// Test that non-terminating handlers are killed by timeout. +export function loop(): void { + while (true) {} +} + +export function rabbit_hole(): void { + rabbit_hole() +} diff --git a/runtime/test/wasm_test/api_version_0_0_5/non_terminating.wasm b/runtime/test/wasm_test/api_version_0_0_5/non_terminating.wasm new file mode 100644 index 00000000000..bd9d56445c0 Binary files /dev/null and b/runtime/test/wasm_test/api_version_0_0_5/non_terminating.wasm differ diff --git a/runtime/test/wasm_test/api_version_0_0_5/store.ts b/runtime/test/wasm_test/api_version_0_0_5/store.ts new file mode 100644 index 00000000000..e755c278606 --- /dev/null +++ b/runtime/test/wasm_test/api_version_0_0_5/store.ts @@ -0,0 +1,53 @@ +export * from './common/global' +import { TypedMap, Entity, BigDecimal, Value } from './common/types' + +/** Definitions copied from graph-ts/index.ts */ +declare namespace store { + function get(entity: string, id: string): Entity | null + function set(entity: string, id: string, data: Entity): void + function remove(entity: string, id: string): void +} + +/** Host Ethereum interface */ +declare namespace ethereum { + function call(call: SmartContractCall): Array +} + +/** Host interface for BigInt arithmetic */ +declare namespace bigInt { + function plus(x: BigInt, y: BigInt): BigInt + function minus(x: BigInt, y: BigInt): BigInt + function times(x: BigInt, y: BigInt): BigInt + function dividedBy(x: BigInt, y: BigInt): BigInt + function dividedByDecimal(x: BigInt, y: BigDecimal): BigDecimal + function mod(x: BigInt, y: BigInt): BigInt +} + +/** Host interface for BigDecimal */ +declare namespace bigDecimal { + function plus(x: BigDecimal, y: BigDecimal): BigDecimal + function minus(x: BigDecimal, y: BigDecimal): BigDecimal + function times(x: BigDecimal, y: BigDecimal): BigDecimal + function dividedBy(x: BigDecimal, y: BigDecimal): BigDecimal + function equals(x: BigDecimal, y: BigDecimal): boolean + function toString(bigDecimal: BigDecimal): string + function fromString(s: string): BigDecimal +} + +/** + * Test functions + */ +export function getUser(id: string): Entity | null { + return store.get("User", id); +} + +export function loadAndSetUserName(id: string, name: string) : void { + let user = store.get("User", id); + if (user == null) { + user = new Entity(); + user.set("id", Value.fromString(id)); + } + user.set("name", Value.fromString(name)); + // save it + store.set("User", id, (user as Entity)); +} diff --git a/runtime/test/wasm_test/api_version_0_0_5/store.wasm b/runtime/test/wasm_test/api_version_0_0_5/store.wasm new file mode 100644 index 00000000000..577d136256a Binary files /dev/null and b/runtime/test/wasm_test/api_version_0_0_5/store.wasm differ diff --git a/runtime/test/wasm_test/api_version_0_0_5/string_to_number.ts b/runtime/test/wasm_test/api_version_0_0_5/string_to_number.ts new file mode 100644 index 00000000000..027b387e007 --- /dev/null +++ b/runtime/test/wasm_test/api_version_0_0_5/string_to_number.ts @@ -0,0 +1,26 @@ +export * from './common/global' +import { BigInt } from './common/types' + +/** Host JSON interface */ +declare namespace json { + function toI64(decimal: string): i64 + function toU64(decimal: string): u64 + function toF64(decimal: string): f64 + function toBigInt(decimal: string): BigInt +} + +export function testToI64(decimal: string): i64 { + return json.toI64(decimal); +} + +export function testToU64(decimal: string): u64 { + return json.toU64(decimal); +} + +export function testToF64(decimal: string): f64 { + return json.toF64(decimal) +} + +export function testToBigInt(decimal: string): BigInt { + return json.toBigInt(decimal) +} diff --git a/runtime/test/wasm_test/api_version_0_0_5/string_to_number.wasm b/runtime/test/wasm_test/api_version_0_0_5/string_to_number.wasm new file mode 100644 index 00000000000..bc46b67f423 Binary files /dev/null and b/runtime/test/wasm_test/api_version_0_0_5/string_to_number.wasm differ diff --git a/runtime/wasm/src/asc_abi/class.rs b/runtime/wasm/src/asc_abi/class.rs index 9a5fe75f280..58a4d503ab6 100644 --- a/runtime/wasm/src/asc_abi/class.rs +++ b/runtime/wasm/src/asc_abi/class.rs @@ -1,147 +1,86 @@ -use anyhow::anyhow; +use crate::asc_abi::{v0_0_4, v0_0_5}; use ethabi; use graph::{ data::store, - runtime::{AscHeap, AscType, AscValue}, + runtime::{AscHeap, AscIndexId, AscType, AscValue, IndexForAscTypeId}, }; use graph::{prelude::serde_json, runtime::DeterministicHostError}; use graph::{prelude::slog, runtime::AscPtr}; use graph_runtime_derive::AscType; -use std::convert::TryInto as _; -use std::marker::PhantomData; -use std::mem::{size_of, size_of_val}; +use semver::Version; +use std::mem::size_of; ///! Rust types that have with a direct correspondence to an Asc class, ///! with their `AscType` implementations. -/// Asc std ArrayBuffer: "a generic, fixed-length raw binary data buffer". -/// See https://github.com/AssemblyScript/assemblyscript/wiki/Memory-Layout-&-Management#arrays -pub struct ArrayBuffer { - byte_length: u32, - // Asc allocators always align at 8 bytes, we already have 4 bytes from - // `byte_length_size` so with 4 more bytes we align the contents at 8 - // bytes. No Asc type has alignment greater than 8, so the - // elements in `content` will be aligned for any element type. - padding: [u8; 4], - // In Asc this slice is layed out inline with the ArrayBuffer. - content: Box<[u8]>, +/// Wrapper of ArrayBuffer for multiple AssemblyScript versions. +/// It just delegates its method calls to the correct mappings apiVersion. +pub enum ArrayBuffer { + ApiVersion0_0_4(v0_0_4::ArrayBuffer), + ApiVersion0_0_5(v0_0_5::ArrayBuffer), } impl ArrayBuffer { - fn new(values: &[T]) -> Result { - let mut content = Vec::new(); - for value in values { - let asc_bytes = value.to_asc_bytes()?; - // An `AscValue` has size equal to alignment, no padding required. - content.extend(&asc_bytes); - } - - if content.len() > u32::max_value() as usize { - return Err(DeterministicHostError(anyhow::anyhow!( - "slice cannot fit in WASM memory" - ))); + pub(crate) fn new( + values: &[T], + api_version: Version, + ) -> Result { + match api_version { + version if version <= Version::new(0, 0, 4) => { + Ok(Self::ApiVersion0_0_4(v0_0_4::ArrayBuffer::new(values)?)) + } + _ => Ok(Self::ApiVersion0_0_5(v0_0_5::ArrayBuffer::new(values)?)), } - Ok(ArrayBuffer { - byte_length: content.len() as u32, - padding: [0; 4], - content: content.into(), - }) - } - - /// Read `length` elements of type `T` starting at `byte_offset`. - /// - /// Panics if that tries to read beyond the length of `self.content`. - fn get( - &self, - byte_offset: u32, - length: u32, - ) -> Result, DeterministicHostError> { - let length = length as usize; - let byte_offset = byte_offset as usize; - - self.content[byte_offset..] - .chunks(size_of::()) - .take(length) - .map(T::from_asc_bytes) - .collect() - - // TODO: This code is preferred as it validates the length of the array. - // But, some existing subgraphs were found to break when this was added. - // This needs to be root caused - /* - let range = byte_offset..byte_offset + length * size_of::(); - self.content - .get(range) - .ok_or_else(|| { - DeterministicHostError(anyhow::anyhow!("Attempted to read past end of array")) - })? - .chunks_exact(size_of::()) - .map(|bytes| T::from_asc_bytes(bytes)) - .collect() - */ } } impl AscType for ArrayBuffer { fn to_asc_bytes(&self) -> Result, DeterministicHostError> { - let mut asc_layout: Vec = Vec::new(); - - let byte_length: [u8; 4] = self.byte_length.to_le_bytes(); - asc_layout.extend(&byte_length); - asc_layout.extend(&self.padding); - asc_layout.extend(self.content.iter()); - - // Allocate extra capacity to next power of two, as required by asc. - let header_size = size_of_val(&byte_length) + size_of_val(&self.padding); - let total_size = self.byte_length as usize + header_size; - let total_capacity = total_size.next_power_of_two(); - let extra_capacity = total_capacity - total_size; - asc_layout.extend(std::iter::repeat(0).take(extra_capacity)); - assert_eq!(asc_layout.len(), total_capacity); - - Ok(asc_layout) - } - - /// The Rust representation of an Asc object as layed out in Asc memory. - fn from_asc_bytes(asc_obj: &[u8]) -> Result { - // Skip `byte_length` and the padding. - let content_offset = size_of::() + 4; - let byte_length = asc_obj.get(..size_of::()).ok_or_else(|| { - DeterministicHostError(anyhow!("Attempted to read past end of array")) - })?; - let content = asc_obj.get(content_offset..).ok_or_else(|| { - DeterministicHostError(anyhow!("Attempted to read past end of array")) - })?; - Ok(ArrayBuffer { - byte_length: u32::from_asc_bytes(&byte_length)?, - padding: [0; 4], - content: content.to_vec().into(), - }) + match self { + Self::ApiVersion0_0_4(a) => a.to_asc_bytes(), + Self::ApiVersion0_0_5(a) => a.to_asc_bytes(), + } + } + + fn from_asc_bytes( + asc_obj: &[u8], + api_version: Version, + ) -> Result { + match &api_version { + version if *version <= Version::new(0, 0, 4) => Ok(Self::ApiVersion0_0_4( + v0_0_4::ArrayBuffer::from_asc_bytes(asc_obj, api_version.clone())?, + )), + _ => Ok(Self::ApiVersion0_0_5(v0_0_5::ArrayBuffer::from_asc_bytes( + asc_obj, + api_version, + )?)), + } } fn asc_size( ptr: AscPtr, heap: &H, ) -> Result { - let byte_length = ptr.read_u32(heap)?; - let byte_length_size = size_of::() as u32; - let padding_size = size_of::() as u32; - Ok(byte_length_size + padding_size + byte_length) + v0_0_4::ArrayBuffer::asc_size(AscPtr::new(ptr.wasm_ptr()), heap) + } + + fn content_len(&self, asc_bytes: &[u8]) -> usize { + match self { + Self::ApiVersion0_0_5(a) => a.content_len(asc_bytes), + _ => unreachable!("Only called for apiVersion >=0.0.5"), + } } } -/// A typed, indexable view of an `ArrayBuffer` of Asc primitives. In Asc it's -/// an abstract class with subclasses for each primitive, for example -/// `Uint8Array` is `TypedArray`. -/// See https://github.com/AssemblyScript/assemblyscript/wiki/Memory-Layout-&-Management#arrays -#[repr(C)] -#[derive(AscType)] -pub struct TypedArray { - pub buffer: AscPtr, - /// Byte position in `buffer` of the array start. - byte_offset: u32, - byte_length: u32, - ty: PhantomData, +impl AscIndexId for ArrayBuffer { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::ArrayBuffer; +} + +/// Wrapper of TypedArray for multiple AssemblyScript versions. +/// It just delegates its method calls to the correct mappings apiVersion. +pub enum TypedArray { + ApiVersion0_0_4(v0_0_4::TypedArray), + ApiVersion0_0_5(v0_0_5::TypedArray), } impl TypedArray { @@ -149,141 +88,165 @@ impl TypedArray { content: &[T], heap: &mut H, ) -> Result { - let buffer = ArrayBuffer::new(content)?; - Ok(TypedArray { - byte_length: buffer.byte_length, - buffer: AscPtr::alloc_obj(buffer, heap)?, - byte_offset: 0, - ty: PhantomData, - }) + match heap.api_version() { + version if version <= Version::new(0, 0, 4) => Ok(Self::ApiVersion0_0_4( + v0_0_4::TypedArray::new(content, heap)?, + )), + _ => Ok(Self::ApiVersion0_0_5(v0_0_5::TypedArray::new( + content, heap, + )?)), + } } pub(crate) fn to_vec( &self, heap: &H, ) -> Result, DeterministicHostError> { - self.buffer - .read_ptr(heap)? - .get(self.byte_offset, self.byte_length / size_of::() as u32) + match self { + Self::ApiVersion0_0_4(t) => t.to_vec(heap), + Self::ApiVersion0_0_5(t) => t.to_vec(heap), + } + } +} + +impl AscType for TypedArray { + fn to_asc_bytes(&self) -> Result, DeterministicHostError> { + match self { + Self::ApiVersion0_0_4(t) => t.to_asc_bytes(), + Self::ApiVersion0_0_5(t) => t.to_asc_bytes(), + } + } + + fn from_asc_bytes( + asc_obj: &[u8], + api_version: Version, + ) -> Result { + match &api_version { + version if *version <= Version::new(0, 0, 4) => Ok(Self::ApiVersion0_0_4( + v0_0_4::TypedArray::from_asc_bytes(asc_obj, api_version.clone())?, + )), + _ => Ok(Self::ApiVersion0_0_5(v0_0_5::TypedArray::from_asc_bytes( + asc_obj, + api_version, + )?)), + } } } pub type Uint8Array = TypedArray; -/// Asc std string: "Strings are encoded as UTF-16LE in AssemblyScript, and are -/// prefixed with their length (in character codes) as a 32-bit integer". See -/// https://github.com/AssemblyScript/assemblyscript/wiki/Memory-Layout-&-Management#strings -pub struct AscString { - // In number of UTF-16 code units (2 bytes each). - length: u32, - // The sequence of UTF-16LE code units that form the string. - pub content: Box<[u16]>, +impl AscIndexId for TypedArray { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::Int8Array; } -impl AscString { - pub fn new(content: &[u16]) -> Result { - if size_of_val(content) > u32::max_value() as usize { - return Err(DeterministicHostError(anyhow!( - "string cannot fit in WASM memory" - ))); - } +impl AscIndexId for TypedArray { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::Int16Array; +} - Ok(AscString { - length: content.len() as u32, - content: content.into(), - }) - } +impl AscIndexId for TypedArray { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::Int32Array; } -impl AscType for AscString { - fn to_asc_bytes(&self) -> Result, DeterministicHostError> { - let mut asc_layout: Vec = Vec::new(); +impl AscIndexId for TypedArray { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::Int64Array; +} - let length: [u8; 4] = self.length.to_le_bytes(); - asc_layout.extend(&length); +impl AscIndexId for TypedArray { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::Uint8Array; +} - // Write the code points, in little-endian (LE) order. - for &code_unit in self.content.iter() { - let low_byte = code_unit as u8; - let high_byte = (code_unit >> 8) as u8; - asc_layout.push(low_byte); - asc_layout.push(high_byte); - } +impl AscIndexId for TypedArray { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::Uint16Array; +} - Ok(asc_layout) - } +impl AscIndexId for TypedArray { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::Uint32Array; +} + +impl AscIndexId for TypedArray { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::Uint64Array; +} + +impl AscIndexId for TypedArray { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::Float32Array; +} - /// The Rust representation of an Asc object as layed out in Asc memory. - fn from_asc_bytes(asc_obj: &[u8]) -> Result { - // Pointer for our current position within `asc_obj`, - // initially at the start of the content skipping `length`. - let mut offset = size_of::(); +impl AscIndexId for TypedArray { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::Float64Array; +} - let length = asc_obj - .get(..offset) - .ok_or(DeterministicHostError(anyhow::anyhow!( - "String bytes not long enough to contain length" - )))?; +/// Wrapper of String for multiple AssemblyScript versions. +/// It just delegates its method calls to the correct mappings apiVersion. +pub enum AscString { + ApiVersion0_0_4(v0_0_4::AscString), + ApiVersion0_0_5(v0_0_5::AscString), +} - // Does not panic - already validated slice length == size_of::. - let length = i32::from_le_bytes(length.try_into().unwrap()); - if length.checked_mul(2).and_then(|l| l.checked_add(4)) != asc_obj.len().try_into().ok() { - return Err(DeterministicHostError(anyhow::anyhow!( - "String length header does not equal byte length" - ))); +impl AscString { + pub fn new(content: &[u16], api_version: Version) -> Result { + match api_version { + version if version <= Version::new(0, 0, 4) => { + Ok(Self::ApiVersion0_0_4(v0_0_4::AscString::new(content)?)) + } + _ => Ok(Self::ApiVersion0_0_5(v0_0_5::AscString::new(content)?)), } + } + + pub fn content(&self) -> &[u16] { + match self { + Self::ApiVersion0_0_4(s) => &s.content, + Self::ApiVersion0_0_5(s) => &s.content, + } + } +} - // Prevents panic when accessing offset + 1 in the loop - if asc_obj.len() % 2 != 0 { - return Err(DeterministicHostError(anyhow::anyhow!( - "Invalid string length" - ))); +impl AscIndexId for AscString { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::String; +} + +impl AscType for AscString { + fn to_asc_bytes(&self) -> Result, DeterministicHostError> { + match self { + Self::ApiVersion0_0_4(s) => s.to_asc_bytes(), + Self::ApiVersion0_0_5(s) => s.to_asc_bytes(), } + } - // UTF-16 (used in assemblyscript) always uses one - // pair of bytes per code unit. - // https://mathiasbynens.be/notes/javascript-encoding - // UTF-16 (16-bit Unicode Transformation Format) is an - // extension of UCS-2 that allows representing code points - // outside the BMP. It produces a variable-length result - // of either one or two 16-bit code units per code point. - // This way, it can encode code points in the range from 0 - // to 0x10FFFF. - - // Read the content. - let mut content = Vec::new(); - while offset < asc_obj.len() { - let code_point_bytes = [asc_obj[offset], asc_obj[offset + 1]]; - let code_point = u16::from_le_bytes(code_point_bytes); - content.push(code_point); - offset += size_of::(); + fn from_asc_bytes( + asc_obj: &[u8], + api_version: Version, + ) -> Result { + match &api_version { + version if *version <= Version::new(0, 0, 4) => Ok(Self::ApiVersion0_0_4( + v0_0_4::AscString::from_asc_bytes(asc_obj, api_version.clone())?, + )), + _ => Ok(Self::ApiVersion0_0_5(v0_0_5::AscString::from_asc_bytes( + asc_obj, + api_version, + )?)), } - AscString::new(&content) } fn asc_size( ptr: AscPtr, heap: &H, ) -> Result { - let length = ptr.read_u32(heap)?; - let length_size = size_of::() as u32; - let code_point_size = size_of::() as u32; - let data_size = code_point_size.checked_mul(length); - let total_size = data_size.and_then(|d| d.checked_add(length_size)); - total_size.ok_or_else(|| { - DeterministicHostError(anyhow::anyhow!("Overflowed when getting size of string")) - }) + v0_0_4::AscString::asc_size(AscPtr::new(ptr.wasm_ptr()), heap) + } + + fn content_len(&self, asc_bytes: &[u8]) -> usize { + match self { + Self::ApiVersion0_0_5(s) => s.content_len(asc_bytes), + _ => unreachable!("Only called for apiVersion >=0.0.5"), + } } } -/// Growable array backed by an `ArrayBuffer`. -/// See https://github.com/AssemblyScript/assemblyscript/wiki/Memory-Layout-&-Management#arrays -#[repr(C)] -#[derive(AscType)] -pub struct Array { - buffer: AscPtr, - length: u32, - ty: PhantomData, +/// Wrapper of Array for multiple AssemblyScript versions. +/// It just delegates its method calls to the correct mappings apiVersion. +pub enum Array { + ApiVersion0_0_4(v0_0_4::Array), + ApiVersion0_0_5(v0_0_5::Array), } impl Array { @@ -291,20 +254,125 @@ impl Array { content: &[T], heap: &mut H, ) -> Result { - Ok(Array { - buffer: AscPtr::alloc_obj(ArrayBuffer::new(content)?, heap)?, - // If this cast would overflow, the above line has already panicked. - length: content.len() as u32, - ty: PhantomData, - }) + match heap.api_version() { + version if version <= Version::new(0, 0, 4) => { + Ok(Self::ApiVersion0_0_4(v0_0_4::Array::new(content, heap)?)) + } + _ => Ok(Self::ApiVersion0_0_5(v0_0_5::Array::new(content, heap)?)), + } } pub(crate) fn to_vec( &self, heap: &H, ) -> Result, DeterministicHostError> { - self.buffer.read_ptr(heap)?.get(0, self.length) + match self { + Self::ApiVersion0_0_4(a) => a.to_vec(heap), + Self::ApiVersion0_0_5(a) => a.to_vec(heap), + } + } +} + +impl AscType for Array { + fn to_asc_bytes(&self) -> Result, DeterministicHostError> { + match self { + Self::ApiVersion0_0_4(a) => a.to_asc_bytes(), + Self::ApiVersion0_0_5(a) => a.to_asc_bytes(), + } } + + fn from_asc_bytes( + asc_obj: &[u8], + api_version: Version, + ) -> Result { + match &api_version { + version if *version <= Version::new(0, 0, 4) => Ok(Self::ApiVersion0_0_4( + v0_0_4::Array::from_asc_bytes(asc_obj, api_version.clone())?, + )), + _ => Ok(Self::ApiVersion0_0_5(v0_0_5::Array::from_asc_bytes( + asc_obj, + api_version, + )?)), + } + } +} + +impl AscIndexId for Array { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::ArrayBool; +} + +impl AscIndexId for Array { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::ArrayUint8Array; +} + +impl AscIndexId for Array>> { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::ArrayEthereumValue; +} + +impl AscIndexId for Array>> { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::ArrayStoreValue; +} + +impl AscIndexId for Array>> { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::ArrayJsonValue; +} + +impl AscIndexId for Array> { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::ArrayString; +} + +impl AscIndexId for Array>>> { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = + IndexForAscTypeId::ArrayTypedMapEntryStringJsonValue; +} + +impl AscIndexId for Array>>> { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = + IndexForAscTypeId::ArrayTypedMapEntryStringStoreValue; +} + +impl AscIndexId for Array { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::ArrayU8; +} + +impl AscIndexId for Array { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::ArrayU16; +} + +impl AscIndexId for Array { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::ArrayU32; +} + +impl AscIndexId for Array { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::ArrayU64; +} + +impl AscIndexId for Array { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::ArrayI8; +} + +impl AscIndexId for Array { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::ArrayI16; +} + +impl AscIndexId for Array { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::ArrayI32; +} + +impl AscIndexId for Array { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::ArrayI64; +} + +impl AscIndexId for Array { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::ArrayF32; +} + +impl AscIndexId for Array { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::ArrayF64; +} + +impl AscIndexId for Array> { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::ArrayBigDecimal; } /// Represents any `AscValue` since they all fit in 64 bits. @@ -317,13 +385,14 @@ impl AscType for EnumPayload { self.0.to_asc_bytes() } - fn from_asc_bytes(asc_obj: &[u8]) -> Result { - Ok(EnumPayload(u64::from_asc_bytes(asc_obj)?)) + fn from_asc_bytes( + asc_obj: &[u8], + api_version: Version, + ) -> Result { + Ok(EnumPayload(u64::from_asc_bytes(asc_obj, api_version)?)) } } -impl AscValue for EnumPayload {} - impl From for i32 { fn from(payload: EnumPayload) -> i32 { payload.0 as i32 @@ -389,6 +458,18 @@ pub struct AscEnum { pub payload: EnumPayload, } +impl AscIndexId for AscEnum { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::EthereumValue; +} + +impl AscIndexId for AscEnum { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::StoreValue; +} + +impl AscIndexId for AscEnum { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::JsonValue; +} + pub type AscEnumArray = AscPtr>>>; #[repr(u32)] @@ -480,11 +561,19 @@ pub type AscH160 = Uint8Array; #[repr(C)] #[derive(AscType)] -pub(crate) struct AscTypedMapEntry { +pub struct AscTypedMapEntry { pub key: AscPtr, pub value: AscPtr, } +impl AscIndexId for AscTypedMapEntry> { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::TypedMapEntryStringStoreValue; +} + +impl AscIndexId for AscTypedMapEntry> { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::TypedMapEntryStringJsonValue; +} + pub(crate) type AscTypedMapEntryArray = Array>>; #[repr(C)] @@ -493,6 +582,19 @@ pub struct AscTypedMap { pub(crate) entries: AscPtr>, } +impl AscIndexId for AscTypedMap> { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::TypedMapStringStoreValue; +} + +impl AscIndexId for AscTypedMap> { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::TypedMapStringJsonValue; +} + +impl AscIndexId for AscTypedMap>> { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = + IndexForAscTypeId::TypedMapStringTypedMapStringJsonValue; +} + pub type AscEntity = AscTypedMap>; pub(crate) type AscJson = AscTypedMap>; @@ -539,6 +641,10 @@ pub struct AscBigDecimal { pub exp: AscPtr, } +impl AscIndexId for AscBigDecimal { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::BigDecimal; +} + #[repr(u32)] pub(crate) enum LogLevel { Critical, @@ -567,12 +673,33 @@ pub struct AscResult { pub error: AscPtr>, } +impl AscIndexId for AscResult, bool> { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = + IndexForAscTypeId::ResultTypedMapStringJsonValueBool; +} + +impl AscIndexId for AscResult>, bool> { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::ResultJsonValueBool; +} + #[repr(C)] #[derive(AscType)] pub struct AscWrapped { pub inner: V, } +impl AscIndexId for AscWrapped> { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::WrappedTypedMapStringJsonValue; +} + +impl AscIndexId for AscWrapped { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::WrappedBool; +} + +impl AscIndexId for AscWrapped>> { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::WrappedJsonValue; +} + impl Copy for AscWrapped {} impl Clone for AscWrapped { diff --git a/runtime/wasm/src/asc_abi/mod.rs b/runtime/wasm/src/asc_abi/mod.rs index 82f643ddc11..4f69f52a457 100644 --- a/runtime/wasm/src/asc_abi/mod.rs +++ b/runtime/wasm/src/asc_abi/mod.rs @@ -1,2 +1,4 @@ // This unecessary nesting of the module should be resolved by further refactoring. pub mod class; +pub mod v0_0_4; +pub mod v0_0_5; diff --git a/runtime/wasm/src/asc_abi/v0_0_4.rs b/runtime/wasm/src/asc_abi/v0_0_4.rs new file mode 100644 index 00000000000..3746b7270fb --- /dev/null +++ b/runtime/wasm/src/asc_abi/v0_0_4.rs @@ -0,0 +1,320 @@ +use crate::asc_abi::class; +use anyhow::anyhow; +use graph::runtime::{AscHeap, AscPtr, AscType, AscValue, DeterministicHostError}; +use graph_runtime_derive::AscType; +use semver::Version; +use std::convert::TryInto as _; +use std::marker::PhantomData; +use std::mem::{size_of, size_of_val}; + +/// Module related to AssemblyScript version v0.6. + +/// Asc std ArrayBuffer: "a generic, fixed-length raw binary data buffer". +/// See https://github.com/AssemblyScript/assemblyscript/wiki/Memory-Layout-&-Management/86447e88be5aa8ec633eaf5fe364651136d136ab#arrays +pub struct ArrayBuffer { + pub byte_length: u32, + // Asc allocators always align at 8 bytes, we already have 4 bytes from + // `byte_length_size` so with 4 more bytes we align the contents at 8 + // bytes. No Asc type has alignment greater than 8, so the + // elements in `content` will be aligned for any element type. + pub padding: [u8; 4], + // In Asc this slice is layed out inline with the ArrayBuffer. + pub content: Box<[u8]>, +} + +impl ArrayBuffer { + pub fn new(values: &[T]) -> Result { + let mut content = Vec::new(); + for value in values { + let asc_bytes = value.to_asc_bytes()?; + // An `AscValue` has size equal to alignment, no padding required. + content.extend(&asc_bytes); + } + + if content.len() > u32::max_value() as usize { + return Err(DeterministicHostError(anyhow::anyhow!( + "slice cannot fit in WASM memory" + ))); + } + Ok(ArrayBuffer { + byte_length: content.len() as u32, + padding: [0; 4], + content: content.into(), + }) + } + + /// Read `length` elements of type `T` starting at `byte_offset`. + /// + /// Panics if that tries to read beyond the length of `self.content`. + pub fn get( + &self, + byte_offset: u32, + length: u32, + api_version: Version, + ) -> Result, DeterministicHostError> { + let length = length as usize; + let byte_offset = byte_offset as usize; + + self.content[byte_offset..] + .chunks(size_of::()) + .take(length) + .map(|asc_obj| T::from_asc_bytes(asc_obj, api_version.clone())) + .collect() + + // TODO: This code is preferred as it validates the length of the array. + // But, some existing subgraphs were found to break when this was added. + // This needs to be root caused + /* + let range = byte_offset..byte_offset + length * size_of::(); + self.content + .get(range) + .ok_or_else(|| { + DeterministicHostError(anyhow::anyhow!("Attempted to read past end of array")) + })? + .chunks_exact(size_of::()) + .map(|bytes| T::from_asc_bytes(bytes)) + .collect() + */ + } +} + +impl AscType for ArrayBuffer { + fn to_asc_bytes(&self) -> Result, DeterministicHostError> { + let mut asc_layout: Vec = Vec::new(); + + let byte_length: [u8; 4] = self.byte_length.to_le_bytes(); + asc_layout.extend(&byte_length); + asc_layout.extend(&self.padding); + asc_layout.extend(self.content.iter()); + + // Allocate extra capacity to next power of two, as required by asc. + let header_size = size_of_val(&byte_length) + size_of_val(&self.padding); + let total_size = self.byte_length as usize + header_size; + let total_capacity = total_size.next_power_of_two(); + let extra_capacity = total_capacity - total_size; + asc_layout.extend(std::iter::repeat(0).take(extra_capacity)); + assert_eq!(asc_layout.len(), total_capacity); + + Ok(asc_layout) + } + + /// The Rust representation of an Asc object as layed out in Asc memory. + fn from_asc_bytes( + asc_obj: &[u8], + api_version: Version, + ) -> Result { + // Skip `byte_length` and the padding. + let content_offset = size_of::() + 4; + let byte_length = asc_obj.get(..size_of::()).ok_or_else(|| { + DeterministicHostError(anyhow!("Attempted to read past end of array")) + })?; + let content = asc_obj.get(content_offset..).ok_or_else(|| { + DeterministicHostError(anyhow!("Attempted to read past end of array")) + })?; + Ok(ArrayBuffer { + byte_length: u32::from_asc_bytes(&byte_length, api_version)?, + padding: [0; 4], + content: content.to_vec().into(), + }) + } + + fn asc_size( + ptr: AscPtr, + heap: &H, + ) -> Result { + let byte_length = ptr.read_u32(heap)?; + let byte_length_size = size_of::() as u32; + let padding_size = size_of::() as u32; + Ok(byte_length_size + padding_size + byte_length) + } +} + +/// A typed, indexable view of an `ArrayBuffer` of Asc primitives. In Asc it's +/// an abstract class with subclasses for each primitive, for example +/// `Uint8Array` is `TypedArray`. +/// See https://github.com/AssemblyScript/assemblyscript/wiki/Memory-Layout-&-Management/86447e88be5aa8ec633eaf5fe364651136d136ab#arrays +#[repr(C)] +#[derive(AscType)] +pub struct TypedArray { + pub buffer: AscPtr, + /// Byte position in `buffer` of the array start. + byte_offset: u32, + byte_length: u32, + ty: PhantomData, +} + +impl TypedArray { + pub(crate) fn new( + content: &[T], + heap: &mut H, + ) -> Result { + let buffer = class::ArrayBuffer::new(content, heap.api_version())?; + let buffer_byte_length = if let class::ArrayBuffer::ApiVersion0_0_4(ref a) = buffer { + a.byte_length + } else { + unreachable!("Only the correct ArrayBuffer will be constructed") + }; + let ptr = AscPtr::alloc_obj(buffer, heap)?; + Ok(TypedArray { + byte_length: buffer_byte_length, + buffer: AscPtr::new(ptr.wasm_ptr()), + byte_offset: 0, + ty: PhantomData, + }) + } + + pub(crate) fn to_vec( + &self, + heap: &H, + ) -> Result, DeterministicHostError> { + self.buffer.read_ptr(heap)?.get( + self.byte_offset, + self.byte_length / size_of::() as u32, + heap.api_version(), + ) + } +} + +/// Asc std string: "Strings are encoded as UTF-16LE in AssemblyScript, and are +/// prefixed with their length (in character codes) as a 32-bit integer". See +/// https://github.com/AssemblyScript/assemblyscript/wiki/Memory-Layout-&-Management/86447e88be5aa8ec633eaf5fe364651136d136ab#arrays +pub struct AscString { + // In number of UTF-16 code units (2 bytes each). + length: u32, + // The sequence of UTF-16LE code units that form the string. + pub content: Box<[u16]>, +} + +impl AscString { + pub fn new(content: &[u16]) -> Result { + if size_of_val(content) > u32::max_value() as usize { + return Err(DeterministicHostError(anyhow!( + "string cannot fit in WASM memory" + ))); + } + + Ok(AscString { + length: content.len() as u32, + content: content.into(), + }) + } +} + +impl AscType for AscString { + fn to_asc_bytes(&self) -> Result, DeterministicHostError> { + let mut asc_layout: Vec = Vec::new(); + + let length: [u8; 4] = self.length.to_le_bytes(); + asc_layout.extend(&length); + + // Write the code points, in little-endian (LE) order. + for &code_unit in self.content.iter() { + let low_byte = code_unit as u8; + let high_byte = (code_unit >> 8) as u8; + asc_layout.push(low_byte); + asc_layout.push(high_byte); + } + + Ok(asc_layout) + } + + /// The Rust representation of an Asc object as layed out in Asc memory. + fn from_asc_bytes( + asc_obj: &[u8], + _api_version: Version, + ) -> Result { + // Pointer for our current position within `asc_obj`, + // initially at the start of the content skipping `length`. + let mut offset = size_of::(); + + let length = asc_obj + .get(..offset) + .ok_or(DeterministicHostError(anyhow::anyhow!( + "String bytes not long enough to contain length" + )))?; + + // Does not panic - already validated slice length == size_of::. + let length = i32::from_le_bytes(length.try_into().unwrap()); + if length.checked_mul(2).and_then(|l| l.checked_add(4)) != asc_obj.len().try_into().ok() { + return Err(DeterministicHostError(anyhow::anyhow!( + "String length header does not equal byte length" + ))); + } + + // Prevents panic when accessing offset + 1 in the loop + if asc_obj.len() % 2 != 0 { + return Err(DeterministicHostError(anyhow::anyhow!( + "Invalid string length" + ))); + } + + // UTF-16 (used in assemblyscript) always uses one + // pair of bytes per code unit. + // https://mathiasbynens.be/notes/javascript-encoding + // UTF-16 (16-bit Unicode Transformation Format) is an + // extension of UCS-2 that allows representing code points + // outside the BMP. It produces a variable-length result + // of either one or two 16-bit code units per code point. + // This way, it can encode code points in the range from 0 + // to 0x10FFFF. + + // Read the content. + let mut content = Vec::new(); + while offset < asc_obj.len() { + let code_point_bytes = [asc_obj[offset], asc_obj[offset + 1]]; + let code_point = u16::from_le_bytes(code_point_bytes); + content.push(code_point); + offset += size_of::(); + } + AscString::new(&content) + } + + fn asc_size( + ptr: AscPtr, + heap: &H, + ) -> Result { + let length = ptr.read_u32(heap)?; + let length_size = size_of::() as u32; + let code_point_size = size_of::() as u32; + let data_size = code_point_size.checked_mul(length); + let total_size = data_size.and_then(|d| d.checked_add(length_size)); + total_size.ok_or_else(|| { + DeterministicHostError(anyhow::anyhow!("Overflowed when getting size of string")) + }) + } +} + +/// Growable array backed by an `ArrayBuffer`. +/// See https://github.com/AssemblyScript/assemblyscript/wiki/Memory-Layout-&-Management/86447e88be5aa8ec633eaf5fe364651136d136ab#arrays +#[repr(C)] +#[derive(AscType)] +pub struct Array { + buffer: AscPtr, + length: u32, + ty: PhantomData, +} + +impl Array { + pub fn new( + content: &[T], + heap: &mut H, + ) -> Result { + let arr_buffer = class::ArrayBuffer::new(content, heap.api_version())?; + let arr_buffer_ptr = AscPtr::alloc_obj(arr_buffer, heap)?; + Ok(Array { + buffer: AscPtr::new(arr_buffer_ptr.wasm_ptr()), + // If this cast would overflow, the above line has already panicked. + length: content.len() as u32, + ty: PhantomData, + }) + } + + pub(crate) fn to_vec( + &self, + heap: &H, + ) -> Result, DeterministicHostError> { + self.buffer + .read_ptr(heap)? + .get(0, self.length, heap.api_version()) + } +} diff --git a/runtime/wasm/src/asc_abi/v0_0_5.rs b/runtime/wasm/src/asc_abi/v0_0_5.rs new file mode 100644 index 00000000000..11c7afb1493 --- /dev/null +++ b/runtime/wasm/src/asc_abi/v0_0_5.rs @@ -0,0 +1,297 @@ +use crate::asc_abi::class; +use anyhow::anyhow; +use graph::runtime::{AscHeap, AscPtr, AscType, AscValue, DeterministicHostError, HEADER_SIZE}; +use graph_runtime_derive::AscType; +use semver::Version; +use std::marker::PhantomData; +use std::mem::{size_of, size_of_val}; + +/// Module related to AssemblyScript version >=v0.19.2. +/// All `to_asc_bytes`/`from_asc_bytes` only consider the #data/content/payload +/// not the #header, that's handled on `AscPtr`. +/// Header in question: https://www.assemblyscript.org/memory.html#common-header-layout + +/// Similar as JS ArrayBuffer, "a generic, fixed-length raw binary data buffer". +/// See https://www.assemblyscript.org/memory.html#arraybuffer-layout +pub struct ArrayBuffer { + // Not included in memory layout + pub byte_length: u32, + // #data + pub content: Box<[u8]>, +} + +impl ArrayBuffer { + pub fn new(values: &[T]) -> Result { + let mut content = Vec::new(); + for value in values { + let asc_bytes = value.to_asc_bytes()?; + content.extend(&asc_bytes); + } + + if content.len() > u32::max_value() as usize { + return Err(DeterministicHostError(anyhow::anyhow!( + "slice cannot fit in WASM memory" + ))); + } + Ok(ArrayBuffer { + byte_length: content.len() as u32, + content: content.into(), + }) + } + + /// Read `length` elements of type `T` starting at `byte_offset`. + /// + /// Panics if that tries to read beyond the length of `self.content`. + pub fn get( + &self, + byte_offset: u32, + length: u32, + api_version: Version, + ) -> Result, DeterministicHostError> { + let length = length as usize; + let byte_offset = byte_offset as usize; + + self.content[byte_offset..] + .chunks(size_of::()) + .take(length) + .map(|asc_obj| T::from_asc_bytes(asc_obj, api_version.clone())) + .collect() + } +} + +impl AscType for ArrayBuffer { + fn to_asc_bytes(&self) -> Result, DeterministicHostError> { + let mut asc_layout: Vec = Vec::new(); + + asc_layout.extend(self.content.iter()); + + // Allocate extra capacity to next power of two, as required by asc. + let total_size = self.byte_length as usize + HEADER_SIZE; + let total_capacity = total_size.next_power_of_two(); + let extra_capacity = total_capacity - total_size; + asc_layout.extend(std::iter::repeat(0).take(extra_capacity)); + + Ok(asc_layout) + } + + fn from_asc_bytes( + asc_obj: &[u8], + _api_version: Version, + ) -> Result { + Ok(ArrayBuffer { + byte_length: asc_obj.len() as u32, + content: asc_obj.to_vec().into(), + }) + } + + fn content_len(&self, _asc_bytes: &[u8]) -> usize { + self.byte_length as usize // without extra_capacity + } +} + +/// A typed, indexable view of an `ArrayBuffer` of Asc primitives. In Asc it's +/// an abstract class with subclasses for each primitive, for example +/// `Uint8Array` is `TypedArray`. +/// Also known as `ArrayBufferView`. +/// See https://www.assemblyscript.org/memory.html#arraybufferview-layout +#[repr(C)] +#[derive(AscType)] +pub struct TypedArray { + // #data -> Backing buffer reference + pub buffer: AscPtr, + // #dataStart -> Start within the #data + data_start: u32, + // #dataLength -> Length of the data from #dataStart + byte_length: u32, + // Not included in memory layout, it's just for typings + ty: PhantomData, +} + +impl TypedArray { + pub(crate) fn new( + content: &[T], + heap: &mut H, + ) -> Result { + let buffer = class::ArrayBuffer::new(content, heap.api_version())?; + let byte_length = content.len() as u32; + let ptr = AscPtr::alloc_obj(buffer, heap)?; + Ok(TypedArray { + buffer: AscPtr::new(ptr.wasm_ptr()), // new AscPtr necessary to convert type parameter + data_start: ptr.wasm_ptr(), + byte_length, + ty: PhantomData, + }) + } + + pub(crate) fn to_vec( + &self, + heap: &H, + ) -> Result, DeterministicHostError> { + // This subtraction is needed because on the ArrayBufferView memory layout + // there are two pointers to the data. + // - The first (self.buffer) points to the related ArrayBuffer. + // - The second (self.data_start) points to where in this ArrayBuffer the data starts. + // So this is basically getting the offset. + // Related docs: https://www.assemblyscript.org/memory.html#arraybufferview-layout + let data_start_with_offset = self + .data_start + .checked_sub(self.buffer.wasm_ptr()) + .ok_or_else(|| { + DeterministicHostError(anyhow::anyhow!( + "Subtract overflow on pointer: {}", // Usually when pointer is zero because of null in AssemblyScript + self.data_start + )) + })?; + + self.buffer.read_ptr(heap)?.get( + data_start_with_offset, + self.byte_length / size_of::() as u32, + heap.api_version(), + ) + } +} + +/// Asc std string: "Strings are encoded as UTF-16LE in AssemblyScript" +/// See https://www.assemblyscript.org/memory.html#string-layout +pub struct AscString { + // Not included in memory layout + // In number of UTF-16 code units (2 bytes each). + byte_length: u32, + // #data + // The sequence of UTF-16LE code units that form the string. + pub content: Box<[u16]>, +} + +impl AscString { + pub fn new(content: &[u16]) -> Result { + if size_of_val(content) > u32::max_value() as usize { + return Err(DeterministicHostError(anyhow!( + "string cannot fit in WASM memory" + ))); + } + + Ok(AscString { + byte_length: content.len() as u32, + content: content.into(), + }) + } +} + +impl AscType for AscString { + fn to_asc_bytes(&self) -> Result, DeterministicHostError> { + let mut content: Vec = Vec::new(); + + // Write the code points, in little-endian (LE) order. + for &code_unit in self.content.iter() { + let low_byte = code_unit as u8; + let high_byte = (code_unit >> 8) as u8; + content.push(low_byte); + content.push(high_byte); + } + + let header_size = 20; + let total_size = (self.byte_length as usize * 2) + header_size; + let total_capacity = total_size.next_power_of_two(); + let extra_capacity = total_capacity - total_size; + content.extend(std::iter::repeat(0).take(extra_capacity)); + + Ok(content) + } + + /// The Rust representation of an Asc object as layed out in Asc memory. + fn from_asc_bytes( + asc_obj: &[u8], + _api_version: Version, + ) -> Result { + // UTF-16 (used in assemblyscript) always uses one + // pair of bytes per code unit. + // https://mathiasbynens.be/notes/javascript-encoding + // UTF-16 (16-bit Unicode Transformation Format) is an + // extension of UCS-2 that allows representing code points + // outside the BMP. It produces a variable-length result + // of either one or two 16-bit code units per code point. + // This way, it can encode code points in the range from 0 + // to 0x10FFFF. + + let mut content = Vec::new(); + for pair in asc_obj.chunks(2) { + let code_point_bytes = [ + pair[0], + *pair.get(1).ok_or_else(|| { + DeterministicHostError(anyhow!( + "Attempted to read past end of string content bytes chunk" + )) + })?, + ]; + let code_point = u16::from_le_bytes(code_point_bytes); + content.push(code_point); + } + AscString::new(&content) + } + + fn content_len(&self, _asc_bytes: &[u8]) -> usize { + self.byte_length as usize * 2 // without extra_capacity, and times 2 because the content is measured in u8s + } +} + +/// Growable array backed by an `ArrayBuffer`. +/// See https://www.assemblyscript.org/memory.html#array-layout +#[repr(C)] +#[derive(AscType)] +pub struct Array { + // #data -> Backing buffer reference + buffer: AscPtr, + // #dataStart -> Start of the data within #data + buffer_data_start: u32, + // #dataLength -> Length of the data from #dataStart + buffer_data_length: u32, + // #length -> Mutable length of the data the user is interested in + length: i32, + // Not included in memory layout, it's just for typings + ty: PhantomData, +} + +impl Array { + pub fn new( + content: &[T], + heap: &mut H, + ) -> Result { + let arr_buffer = class::ArrayBuffer::new(content, heap.api_version())?; + let buffer = AscPtr::alloc_obj(arr_buffer, heap)?; + let buffer_data_length = buffer.read_len(heap)?; + Ok(Array { + buffer: AscPtr::new(buffer.wasm_ptr()), + buffer_data_start: buffer.wasm_ptr(), + buffer_data_length, + length: content.len() as i32, + ty: PhantomData, + }) + } + + pub(crate) fn to_vec( + &self, + heap: &H, + ) -> Result, DeterministicHostError> { + // This subtraction is needed because on the ArrayBufferView memory layout + // there are two pointers to the data. + // - The first (self.buffer) points to the related ArrayBuffer. + // - The second (self.buffer_data_start) points to where in this ArrayBuffer the data starts. + // So this is basically getting the offset. + // Related docs: https://www.assemblyscript.org/memory.html#arraybufferview-layout + let buffer_data_start_with_offset = self + .buffer_data_start + .checked_sub(self.buffer.wasm_ptr()) + .ok_or_else(|| { + DeterministicHostError(anyhow::anyhow!( + "Subtract overflow on pointer: {}", // Usually when pointer is zero because of null in AssemblyScript + self.buffer_data_start + )) + })?; + + self.buffer.read_ptr(heap)?.get( + buffer_data_start_with_offset, + self.length as u32, + heap.api_version(), + ) + } +} diff --git a/runtime/wasm/src/module/mod.rs b/runtime/wasm/src/module/mod.rs index 501e1f326d7..f2d264e3b5e 100644 --- a/runtime/wasm/src/module/mod.rs +++ b/runtime/wasm/src/module/mod.rs @@ -14,9 +14,10 @@ use crate::error::DeterminismLevel; pub use crate::host_exports; use crate::mapping::MappingContext; use anyhow::Error; +use graph::data::store; use graph::prelude::*; +use graph::runtime::{AscHeap, IndexForAscTypeId}; use graph::{components::subgraph::MappingError, runtime::AscPtr}; -use graph::{data::store, runtime::AscHeap}; use graph::{ data::subgraph::schema::SubgraphError, runtime::{asc_get, asc_new, try_asc_get, DeterministicHostError}, @@ -72,6 +73,13 @@ impl AscHeap for WasmInstance { fn api_version(&self) -> Version { self.instance_ctx().api_version() } + + fn asc_type_id( + &mut self, + type_id_index: IndexForAscTypeId, + ) -> Result { + self.instance_ctx_mut().asc_type_id(type_id_index) + } } impl WasmInstance { @@ -221,6 +229,9 @@ pub struct WasmInstanceContext { // return a pointer to the first byte of allocated space. memory_allocate: wasmtime::TypedFunc, + // Function wrapper for `idof` from AssemblyScript + id_of_type: Option>, + pub ctx: MappingContext, pub valid_module: Arc, pub host_metrics: Arc, @@ -255,6 +266,7 @@ impl WasmInstance { ) -> Result, anyhow::Error> { let mut linker = wasmtime::Linker::new(&wasmtime::Store::new(valid_module.module.engine())); let host_fns = ctx.host_fns.cheap_clone(); + let api_version = ctx.host_exports.api_version.clone(); // Used by exports to access the instance context. There are two ways this can be set: // - After instantiation, if no host export is called in the start function. @@ -513,6 +525,18 @@ impl WasmInstance { )?); } + match api_version { + version if version <= Version::new(0, 0, 4) => {} + _ => { + instance + .get_func("_start") + .context("`_start` function not found")? + .typed::<(), ()>()? + .call(()) + .unwrap(); + } + } + Ok(WasmInstance { instance, instance_ctx: shared_ctx, @@ -538,6 +562,20 @@ impl AscHeap for WasmInstanceContext { // of the node. self.arena_start_ptr = self.memory_allocate.call(arena_size).unwrap(); self.arena_free_size = arena_size; + + match &self.ctx.host_exports.api_version { + version if *version <= Version::new(0, 0, 4) => {} + _ => { + // This arithmetic is done because when you call AssemblyScripts's `__alloc` + // function, it isn't typed and it just returns `mmInfo` on it's header, + // differently from allocating on regular types (`__new` for example). + // `mmInfo` has size of 4, and everything allocated on AssemblyScript memory + // should have alignment of 16, this means we need to do a 12 offset on these + // big chunks of untyped allocation. + self.arena_start_ptr += 12; + self.arena_free_size -= 12; + } + }; }; let ptr = self.arena_start_ptr as usize; @@ -570,6 +608,20 @@ impl AscHeap for WasmInstanceContext { fn api_version(&self) -> Version { self.ctx.host_exports.api_version.clone() } + + fn asc_type_id( + &mut self, + type_id_index: IndexForAscTypeId, + ) -> Result { + let type_id = self + .id_of_type + .as_ref() + .unwrap() // Unwrap ok because it's only called on correct apiVersion, look for AscPtr::generate_header + .call(type_id_index as u32) + .with_context(|| format!("Failed to call 'asc_type_id' with '{:?}'", type_id_index)) + .map_err(DeterministicHostError)?; + Ok(type_id) + } } impl WasmInstanceContext { @@ -587,14 +639,31 @@ impl WasmInstanceContext { .get_memory("memory") .context("Failed to find memory export in the WASM module")?; - let memory_allocate = instance - .get_func("memory.allocate") - .context("`memory.allocate` function not found")? - .typed()? - .clone(); + let memory_allocate = match &ctx.host_exports.api_version { + version if *version <= Version::new(0, 0, 4) => instance + .get_func("memory.allocate") + .context("`memory.allocate` function not found"), + _ => instance + .get_func("allocate") + .context("`allocate` function not found"), + }? + .typed()? + .clone(); + + let id_of_type = match &ctx.host_exports.api_version { + version if *version <= Version::new(0, 0, 4) => None, + _ => Some( + instance + .get_func("id_of_type") + .context("`id_of_type` function not found")? + .typed()? + .clone(), + ), + }; Ok(WasmInstanceContext { memory_allocate, + id_of_type, memory, ctx, valid_module, @@ -623,14 +692,33 @@ impl WasmInstanceContext { .and_then(|e| e.into_memory()) .context("Failed to find memory export in the WASM module")?; - let memory_allocate = caller - .get_export("memory.allocate") - .and_then(|e| e.into_func()) - .context("`memory.allocate` function not found")? - .typed()? - .clone(); + let memory_allocate = match &ctx.host_exports.api_version { + version if *version <= Version::new(0, 0, 4) => caller + .get_export("memory.allocate") + .and_then(|e| e.into_func()) + .context("`memory.allocate` function not found"), + _ => caller + .get_export("allocate") + .and_then(|e| e.into_func()) + .context("`allocate` function not found"), + }? + .typed()? + .clone(); + + let id_of_type = match &ctx.host_exports.api_version { + version if *version <= Version::new(0, 0, 4) => None, + _ => Some( + caller + .get_export("id_of_type") + .and_then(|e| e.into_func()) + .context("`id_of_type` function not found")? + .typed()? + .clone(), + ), + }; Ok(WasmInstanceContext { + id_of_type, memory_allocate, memory, ctx, diff --git a/runtime/wasm/src/to_from/external.rs b/runtime/wasm/src/to_from/external.rs index 77544cf0bfd..4eb9346decd 100644 --- a/runtime/wasm/src/to_from/external.rs +++ b/runtime/wasm/src/to_from/external.rs @@ -1,6 +1,8 @@ use ethabi; -use graph::runtime::{asc_get, asc_new, try_asc_get, AscPtr, AscType, AscValue, ToAscObj}; +use graph::runtime::{ + asc_get, asc_new, try_asc_get, AscIndexId, AscPtr, AscType, AscValue, ToAscObj, +}; use graph::{data::store, runtime::DeterministicHostError}; use graph::{prelude::serde_json, runtime::FromAscObj}; use graph::{prelude::web3::types as web3, runtime::AscHeap}; @@ -354,7 +356,8 @@ impl ToAscObj> for AscWrapped { impl ToAscObj, bool>> for Result where V: ToAscObj, - VAsc: AscType, + VAsc: AscType + AscIndexId, + AscWrapped>: AscIndexId, { fn to_asc_obj( &self, diff --git a/runtime/wasm/src/to_from/mod.rs b/runtime/wasm/src/to_from/mod.rs index fce2201cf76..7e54ef65c6f 100644 --- a/runtime/wasm/src/to_from/mod.rs +++ b/runtime/wasm/src/to_from/mod.rs @@ -6,7 +6,8 @@ use graph::runtime::asc_get; use graph::runtime::asc_new; use graph::runtime::try_asc_get; use graph::runtime::{ - AscHeap, AscPtr, AscType, AscValue, DeterministicHostError, FromAscObj, ToAscObj, TryFromAscObj, + AscHeap, AscIndexId, AscPtr, AscType, AscValue, DeterministicHostError, FromAscObj, ToAscObj, + TryFromAscObj, }; use crate::asc_abi::class::*; @@ -84,9 +85,9 @@ impl FromAscObj> for [T; 4] { impl ToAscObj for str { fn to_asc_obj( &self, - _: &mut H, + heap: &mut H, ) -> Result { - AscString::new(&self.encode_utf16().collect::>()) + AscString::new(&self.encode_utf16().collect::>(), heap.api_version()) } } @@ -104,7 +105,7 @@ impl FromAscObj for String { asc_string: AscString, _: &H, ) -> Result { - let mut string = String::from_utf16(&asc_string.content) + let mut string = String::from_utf16(asc_string.content()) .map_err(|e| DeterministicHostError(e.into()))?; // Strip null characters since they are not accepted by Postgres. @@ -124,7 +125,7 @@ impl TryFromAscObj for String { } } -impl> ToAscObj>> for [T] { +impl> ToAscObj>> for [T] { fn to_asc_obj( &self, heap: &mut H, @@ -135,7 +136,7 @@ impl> ToAscObj>> for [T] { } } -impl> FromAscObj>> for Vec { +impl> FromAscObj>> for Vec { fn from_asc_obj( array: Array>, heap: &H, @@ -148,7 +149,7 @@ impl> FromAscObj>> for Vec { } } -impl> TryFromAscObj>> for Vec { +impl> TryFromAscObj>> for Vec { fn try_from_asc_obj( array: Array>, heap: &H, @@ -161,8 +162,12 @@ impl> TryFromAscObj>> for Vec } } -impl, U: TryFromAscObj> - TryFromAscObj> for (T, U) +impl< + K: AscType + AscIndexId, + V: AscType + AscIndexId, + T: TryFromAscObj, + U: TryFromAscObj, + > TryFromAscObj> for (T, U) { fn try_from_asc_obj( asc_entry: AscTypedMapEntry, @@ -175,8 +180,8 @@ impl, U: TryFromAscObj> } } -impl, U: ToAscObj> ToAscObj> - for (T, U) +impl, U: ToAscObj> + ToAscObj> for (T, U) { fn to_asc_obj( &self, @@ -189,8 +194,15 @@ impl, U: ToAscObj> ToAscObj + Hash + Eq, U: TryFromAscObj> - TryFromAscObj> for HashMap +impl< + K: AscType + AscIndexId, + V: AscType + AscIndexId, + T: TryFromAscObj + Hash + Eq, + U: TryFromAscObj, + > TryFromAscObj> for HashMap +where + Array>>: AscIndexId, + AscTypedMapEntry: AscIndexId, { fn try_from_asc_obj( asc_map: AscTypedMap, diff --git a/tests/integration-tests/api-version-v0-0-4/package.json b/tests/integration-tests/api-version-v0-0-4/package.json new file mode 100644 index 00000000000..765c499454d --- /dev/null +++ b/tests/integration-tests/api-version-v0-0-4/package.json @@ -0,0 +1,25 @@ +{ + "name": "api-version-v0-0-4", + "version": "0.1.0", + "scripts": { + "build-contracts": "../common/build-contracts.sh", + "codegen": "graph codegen", + "test": "yarn build-contracts && truffle test --compile-none --network test", + "create:test": "graph create test/api-version-v0-0-4 --node $GRAPH_NODE_ADMIN_URI", + "deploy:test": "graph deploy test/api-version-v0-0-4 --ipfs $IPFS_URI --node $GRAPH_NODE_ADMIN_URI" + }, + "devDependencies": { + "@graphprotocol/graph-cli": "https://github.com/graphprotocol/graph-cli#master", + "@graphprotocol/graph-ts": "https://github.com/graphprotocol/graph-ts#master", + "solc": "^0.8.2" + }, + "dependencies": { + "@truffle/contract": "^4.3", + "@truffle/hdwallet-provider": "^1.2", + "apollo-fetch": "^0.7.0", + "babel-polyfill": "^6.26.0", + "babel-register": "^6.26.0", + "gluegun": "^4.6.1", + "truffle": "^5.2" + } +} diff --git a/tests/integration-tests/api-version-v0-0-4/schema.graphql b/tests/integration-tests/api-version-v0-0-4/schema.graphql new file mode 100644 index 00000000000..6c007b3245b --- /dev/null +++ b/tests/integration-tests/api-version-v0-0-4/schema.graphql @@ -0,0 +1,5 @@ +# The `id` is the block number and `count` the handler invocations at that block. +type DataSourceCount @entity { + id: ID! + count: Int! +} diff --git a/tests/integration-tests/api-version-v0-0-4/src/mapping.ts b/tests/integration-tests/api-version-v0-0-4/src/mapping.ts new file mode 100644 index 00000000000..36b326f6110 --- /dev/null +++ b/tests/integration-tests/api-version-v0-0-4/src/mapping.ts @@ -0,0 +1,38 @@ +import { + ethereum, + DataSourceContext, + dataSource, + Address, + BigInt, +} from "@graphprotocol/graph-ts"; +import { Template } from "../generated/templates"; +import { DataSourceCount } from "../generated/schema"; + +export function handleBlock(block: ethereum.Block): void { + let context = new DataSourceContext(); + context.setBigInt("number", block.number); + + Template.createWithContext( + changetype
(Address.fromHexString( + "0x2E645469f354BB4F5c8a05B3b30A929361cf77eC" + )), + context + ); +} + +export function handleBlockTemplate(block: ethereum.Block): void { + let count = DataSourceCount.load(block.number.toString()); + if (count == null) { + count = new DataSourceCount(block.number.toString()); + count.count = 0; + } + + let ctx = dataSource.context(); + let number = ctx.getBigInt("number"); + assert( + count.count == number.toI32(), + "wrong count, found " + BigInt.fromI32(count.count).toString() + ); + count.count += 1; + count.save(); +} diff --git a/tests/integration-tests/api-version-v0-0-4/subgraph.yaml b/tests/integration-tests/api-version-v0-0-4/subgraph.yaml new file mode 100644 index 00000000000..cfb724e24d0 --- /dev/null +++ b/tests/integration-tests/api-version-v0-0-4/subgraph.yaml @@ -0,0 +1,43 @@ +specVersion: 0.0.2 +repository: https://github.com/graphprotocol/example-subgraph +schema: + file: ./schema.graphql +features: + - nonFatalErrors +dataSources: + - kind: ethereum/contract + name: Contract + network: test + source: + address: "0xCfEB869F69431e42cdB54A4F4f105C19C080A601" + abi: Contract + mapping: + kind: ethereum/events + apiVersion: 0.0.4 + language: wasm/assemblyscript + entities: + - Gravatar + abis: + - name: Contract + file: ./abis/Contract.abi + blockHandlers: + - handler: handleBlock + file: ./src/mapping.ts +templates: + - kind: ethereum/contract + name: Template + network: test + source: + abi: Contract + mapping: + kind: ethereum/events + apiVersion: 0.0.4 + language: wasm/assemblyscript + entities: + - Gravatar + abis: + - name: Contract + file: ./abis/Contract.abi + blockHandlers: + - handler: handleBlockTemplate + file: ./src/mapping.ts diff --git a/tests/integration-tests/api-version-v0-0-4/test/test.js b/tests/integration-tests/api-version-v0-0-4/test/test.js new file mode 100644 index 00000000000..fd0e2ee2257 --- /dev/null +++ b/tests/integration-tests/api-version-v0-0-4/test/test.js @@ -0,0 +1,79 @@ +const path = require("path"); +const execSync = require("child_process").execSync; +const { system, patching } = require("gluegun"); +const { createApolloFetch } = require("apollo-fetch"); + +const Contract = artifacts.require("./Contract.sol"); + +const srcDir = path.join(__dirname, ".."); + +const indexPort = process.env.GRAPH_NODE_INDEX_PORT || 18030; + +const fetchSubgraphs = createApolloFetch({ + uri: `http://localhost:${indexPort}/graphql`, +}); + +const exec = (cmd) => { + try { + return execSync(cmd, { cwd: srcDir, stdio: "inherit" }); + } catch (e) { + throw new Error(`Failed to run command \`${cmd}\``); + } +}; + +const waitForSubgraphToBeSynced = async () => + new Promise((resolve, reject) => { + // Wait for 60s + let deadline = Date.now() + 60 * 1000; + + // Function to check if the subgraph is synced + const checkSubgraphSynced = async () => { + try { + let result = await fetchSubgraphs({ + query: `{ indexingStatuses { synced, health } }`, + }); + + if (result.data.indexingStatuses[0].synced) { + resolve(); + } else if (result.data.indexingStatuses[0].health != "healthy") { + reject(new Error("Subgraph failed")); + } else { + throw new Error("reject or retry"); + } + } catch (e) { + if (Date.now() > deadline) { + reject(new Error(`Timed out waiting for the subgraph to sync`)); + } else { + setTimeout(checkSubgraphSynced, 500); + } + } + }; + + // Periodically check whether the subgraph has synced + setTimeout(checkSubgraphSynced, 0); + }); + +contract("Contract", (accounts) => { + // Deploy the subgraph once before all tests + before(async () => { + // Deploy the contract + const contract = await Contract.deployed(); + + // Insert its address into subgraph manifest + await patching.replace( + path.join(srcDir, "subgraph.yaml"), + "0x0000000000000000000000000000000000000000", + contract.address + ); + + // Create and deploy the subgraph + exec(`yarn codegen`); + exec(`yarn create:test`); + exec(`yarn deploy:test`); + }); + + it("subgraph does not fail", async () => { + // Wait for the subgraph to be indexed, and not fail + await waitForSubgraphToBeSynced(); + }); +}); diff --git a/tests/integration-tests/api-version-v0-0-4/truffle.js b/tests/integration-tests/api-version-v0-0-4/truffle.js new file mode 100644 index 00000000000..55e43ccf6a4 --- /dev/null +++ b/tests/integration-tests/api-version-v0-0-4/truffle.js @@ -0,0 +1,22 @@ +require("babel-register"); +require("babel-polyfill"); + +module.exports = { + contracts_directory: "../common", + migrations_directory: "../common", + contracts_build_directory: "./truffle_output", + networks: { + test: { + host: "localhost", + port: process.env.GANACHE_TEST_PORT || 18545, + network_id: "*", + gas: "100000000000", + gasPrice: "1", + }, + }, + compilers: { + solc: { + version: "0.8.2" + }, + }, +}; diff --git a/tests/integration-tests/arweave-and-3box/package.json b/tests/integration-tests/arweave-and-3box/package.json index 11a6eb20013..e0979ac24ce 100644 --- a/tests/integration-tests/arweave-and-3box/package.json +++ b/tests/integration-tests/arweave-and-3box/package.json @@ -9,8 +9,8 @@ "deploy:test": "graph deploy test/arweave-and-3box --ipfs $IPFS_URI --node $GRAPH_NODE_ADMIN_URI" }, "devDependencies": { - "@graphprotocol/graph-cli": "https://github.com/graphprotocol/graph-cli#master", - "@graphprotocol/graph-ts": "https://github.com/graphprotocol/graph-ts#master", + "@graphprotocol/graph-cli": "https://github.com/graphprotocol/graph-cli#otavio/update-assembly-script", + "@graphprotocol/graph-ts": "https://github.com/graphprotocol/graph-ts#otavio/update-assembly-script", "solc": "^0.8.2" }, "dependencies": { diff --git a/tests/integration-tests/arweave-and-3box/src/mapping.ts b/tests/integration-tests/arweave-and-3box/src/mapping.ts index 1599a8d0455..7fa425e2942 100644 --- a/tests/integration-tests/arweave-and-3box/src/mapping.ts +++ b/tests/integration-tests/arweave-and-3box/src/mapping.ts @@ -10,9 +10,9 @@ import { Trigger } from "../generated/Contract/Contract"; export function handleTrigger(event: Trigger): void { let data = json.fromBytes( - arweave.transactionData( + changetype(arweave.transactionData( "W2czhcswOAe4TgL4Q8kHHqoZ1jbFBntUCrtamYX_rOU" - ) as Bytes + )) ); assert(data.toArray()[0].toString() == "Weather data for Dallas"); @@ -21,9 +21,9 @@ export function handleTrigger(event: Trigger): void { ); assert(no_data === null); - let moo_master = box.profile( + let moo_master = changetype(box.profile( "0xc8d807011058fcc0FB717dcd549b9ced09b53404" - ) as TypedMap; + )); assert(moo_master.get("name").toString() == "Moo Master"); let nothing = box.profile("0xc33307011058fcc0FB717dcd549b9ced09b53333"); diff --git a/tests/integration-tests/arweave-and-3box/subgraph.yaml b/tests/integration-tests/arweave-and-3box/subgraph.yaml index 833dd1a3150..09e3122864c 100644 --- a/tests/integration-tests/arweave-and-3box/subgraph.yaml +++ b/tests/integration-tests/arweave-and-3box/subgraph.yaml @@ -10,7 +10,7 @@ dataSources: abi: Contract mapping: kind: ethereum/events - apiVersion: 0.0.4 + apiVersion: 0.0.5 language: wasm/assemblyscript abis: - name: Contract diff --git a/tests/integration-tests/data-source-context/package.json b/tests/integration-tests/data-source-context/package.json index 3507b114c22..6841ba58800 100644 --- a/tests/integration-tests/data-source-context/package.json +++ b/tests/integration-tests/data-source-context/package.json @@ -9,8 +9,8 @@ "deploy:test": "graph deploy test/data-source-context --ipfs $IPFS_URI --node $GRAPH_NODE_ADMIN_URI" }, "devDependencies": { - "@graphprotocol/graph-cli": "https://github.com/graphprotocol/graph-cli#master", - "@graphprotocol/graph-ts": "https://github.com/graphprotocol/graph-ts#master", + "@graphprotocol/graph-cli": "https://github.com/graphprotocol/graph-cli#otavio/update-assembly-script", + "@graphprotocol/graph-ts": "https://github.com/graphprotocol/graph-ts#otavio/update-assembly-script", "solc": "^0.8.2" }, "dependencies": { diff --git a/tests/integration-tests/data-source-context/src/mapping.ts b/tests/integration-tests/data-source-context/src/mapping.ts index f1e81d88637..17b2668b30c 100644 --- a/tests/integration-tests/data-source-context/src/mapping.ts +++ b/tests/integration-tests/data-source-context/src/mapping.ts @@ -6,9 +6,9 @@ export function handleTrigger(event: Trigger): void { let context = new DataSourceContext(); context.setI32("answer", 42); Dynamic.createWithContext( - Address.fromHexString( + changetype
(Address.fromHexString( "0x2E645469f354BB4F5c8a05B3b30A929361cf77eC" - ) as Address, + )), context ); } diff --git a/tests/integration-tests/data-source-context/subgraph.yaml b/tests/integration-tests/data-source-context/subgraph.yaml index b94130c7608..fb7f957d8ec 100644 --- a/tests/integration-tests/data-source-context/subgraph.yaml +++ b/tests/integration-tests/data-source-context/subgraph.yaml @@ -10,7 +10,7 @@ dataSources: abi: Contract mapping: kind: ethereum/events - apiVersion: 0.0.4 + apiVersion: 0.0.5 language: wasm/assemblyscript abis: - name: Contract @@ -29,7 +29,7 @@ templates: abi: Contract mapping: kind: ethereum/events - apiVersion: 0.0.4 + apiVersion: 0.0.5 language: wasm/assemblyscript abis: - name: Contract diff --git a/tests/integration-tests/data-source-revert/package.json b/tests/integration-tests/data-source-revert/package.json index 260313643f0..774942f328d 100644 --- a/tests/integration-tests/data-source-revert/package.json +++ b/tests/integration-tests/data-source-revert/package.json @@ -9,8 +9,8 @@ "deploy:test": "graph deploy test/data-source-revert --ipfs $IPFS_URI --node $GRAPH_NODE_ADMIN_URI" }, "devDependencies": { - "@graphprotocol/graph-cli": "https://github.com/graphprotocol/graph-cli#master", - "@graphprotocol/graph-ts": "https://github.com/graphprotocol/graph-ts#master", + "@graphprotocol/graph-cli": "https://github.com/graphprotocol/graph-cli#otavio/update-assembly-script", + "@graphprotocol/graph-ts": "https://github.com/graphprotocol/graph-ts#otavio/update-assembly-script", "solc": "^0.8.2" }, "dependencies": { diff --git a/tests/integration-tests/data-source-revert/src/mapping.ts b/tests/integration-tests/data-source-revert/src/mapping.ts index 7c465108a03..36b326f6110 100644 --- a/tests/integration-tests/data-source-revert/src/mapping.ts +++ b/tests/integration-tests/data-source-revert/src/mapping.ts @@ -13,9 +13,9 @@ export function handleBlock(block: ethereum.Block): void { context.setBigInt("number", block.number); Template.createWithContext( - Address.fromHexString( + changetype
(Address.fromHexString( "0x2E645469f354BB4F5c8a05B3b30A929361cf77eC" - ) as Address, + )), context ); } diff --git a/tests/integration-tests/data-source-revert/subgraph.yaml b/tests/integration-tests/data-source-revert/subgraph.yaml index cfb724e24d0..f2a5b9633ad 100644 --- a/tests/integration-tests/data-source-revert/subgraph.yaml +++ b/tests/integration-tests/data-source-revert/subgraph.yaml @@ -13,7 +13,7 @@ dataSources: abi: Contract mapping: kind: ethereum/events - apiVersion: 0.0.4 + apiVersion: 0.0.5 language: wasm/assemblyscript entities: - Gravatar @@ -31,7 +31,7 @@ templates: abi: Contract mapping: kind: ethereum/events - apiVersion: 0.0.4 + apiVersion: 0.0.5 language: wasm/assemblyscript entities: - Gravatar diff --git a/tests/integration-tests/fatal-error/package.json b/tests/integration-tests/fatal-error/package.json index 9e4c3b14e8b..2c6fbf85ec9 100644 --- a/tests/integration-tests/fatal-error/package.json +++ b/tests/integration-tests/fatal-error/package.json @@ -9,8 +9,8 @@ "deploy:test": "graph deploy test/fatal-error --ipfs $IPFS_URI --node $GRAPH_NODE_ADMIN_URI" }, "devDependencies": { - "@graphprotocol/graph-cli": "https://github.com/graphprotocol/graph-cli#master", - "@graphprotocol/graph-ts": "https://github.com/graphprotocol/graph-ts#master", + "@graphprotocol/graph-cli": "https://github.com/graphprotocol/graph-cli#otavio/update-assembly-script", + "@graphprotocol/graph-ts": "https://github.com/graphprotocol/graph-ts#otavio/update-assembly-script", "solc": "^0.8.2" }, "dependencies": { diff --git a/tests/integration-tests/fatal-error/subgraph.yaml b/tests/integration-tests/fatal-error/subgraph.yaml index 833dd1a3150..09e3122864c 100644 --- a/tests/integration-tests/fatal-error/subgraph.yaml +++ b/tests/integration-tests/fatal-error/subgraph.yaml @@ -10,7 +10,7 @@ dataSources: abi: Contract mapping: kind: ethereum/events - apiVersion: 0.0.4 + apiVersion: 0.0.5 language: wasm/assemblyscript abis: - name: Contract diff --git a/tests/integration-tests/ganache-reverts/package.json b/tests/integration-tests/ganache-reverts/package.json index 7b19decf6d5..c53587862a2 100644 --- a/tests/integration-tests/ganache-reverts/package.json +++ b/tests/integration-tests/ganache-reverts/package.json @@ -9,8 +9,8 @@ "deploy:test": "graph deploy test/ganache-reverts --ipfs $IPFS_URI --node $GRAPH_NODE_ADMIN_URI" }, "devDependencies": { - "@graphprotocol/graph-cli": "https://github.com/graphprotocol/graph-cli#master", - "@graphprotocol/graph-ts": "https://github.com/graphprotocol/graph-ts#master", + "@graphprotocol/graph-cli": "https://github.com/graphprotocol/graph-cli#otavio/update-assembly-script", + "@graphprotocol/graph-ts": "https://github.com/graphprotocol/graph-ts#otavio/update-assembly-script", "solc": "^0.8.2" }, "dependencies": { diff --git a/tests/integration-tests/ganache-reverts/subgraph.yaml b/tests/integration-tests/ganache-reverts/subgraph.yaml index 90a9c63ddc3..2b3cbfa922f 100644 --- a/tests/integration-tests/ganache-reverts/subgraph.yaml +++ b/tests/integration-tests/ganache-reverts/subgraph.yaml @@ -10,7 +10,7 @@ dataSources: abi: Contract mapping: kind: ethereum/events - apiVersion: 0.0.4 + apiVersion: 0.0.5 language: wasm/assemblyscript abis: - name: Contract diff --git a/tests/integration-tests/host-exports/package.json b/tests/integration-tests/host-exports/package.json index 7311e266157..93369163fec 100644 --- a/tests/integration-tests/host-exports/package.json +++ b/tests/integration-tests/host-exports/package.json @@ -9,8 +9,8 @@ "deploy:test": "graph deploy test/host-exports --ipfs $IPFS_URI --node $GRAPH_NODE_ADMIN_URI" }, "devDependencies": { - "@graphprotocol/graph-cli": "https://github.com/graphprotocol/graph-cli#master", - "@graphprotocol/graph-ts": "https://github.com/graphprotocol/graph-ts#master", + "@graphprotocol/graph-cli": "https://github.com/graphprotocol/graph-cli#otavio/update-assembly-script", + "@graphprotocol/graph-ts": "https://github.com/graphprotocol/graph-ts#otavio/update-assembly-script", "solc": "^0.8.2" }, "dependencies": { diff --git a/tests/integration-tests/host-exports/src/mapping.ts b/tests/integration-tests/host-exports/src/mapping.ts index cfee713c664..1e02d849132 100644 --- a/tests/integration-tests/host-exports/src/mapping.ts +++ b/tests/integration-tests/host-exports/src/mapping.ts @@ -1,7 +1,6 @@ import { Trigger } from "../generated/Contract/Contract"; import { Address, BigDecimal, BigInt, ethereum } from "@graphprotocol/graph-ts"; -// Test that host exports work in globals. let one = BigDecimal.fromString("1"); export function handleTrigger(event: Trigger): void { @@ -79,7 +78,7 @@ function ethereumAbiSimpleCase(): void { let encoded = ethereum.encode(address)!; - let decoded = ethereum.decode("address", encoded); + let decoded = ethereum.decode("address", encoded)!; assert(address.toAddress() == decoded.toAddress(), "address ethereum encoded does not equal the decoded value"); } @@ -100,16 +99,16 @@ function ethereumAbiComplexCase(): void { bool ]; - let tuple = ethereum.Value.fromTuple(tupleArray as ethereum.Tuple); + let tuple = ethereum.Value.fromTuple(changetype(tupleArray)); let token: Array = [ address, tuple ]; - let encoded = ethereum.encode(ethereum.Value.fromTuple(token as ethereum.Tuple))!; + let encoded = ethereum.encode(ethereum.Value.fromTuple(changetype(token)))!; - let decoded = ethereum.decode("(address,(uint256[2],bool))", encoded).toTuple(); + let decoded = ethereum.decode("(address,(uint256[2],bool))", encoded)!.toTuple(); let decodedAddress = decoded[0].toAddress(); let decodedTuple = decoded[1].toTuple(); diff --git a/tests/integration-tests/host-exports/subgraph.yaml b/tests/integration-tests/host-exports/subgraph.yaml index 833dd1a3150..09e3122864c 100644 --- a/tests/integration-tests/host-exports/subgraph.yaml +++ b/tests/integration-tests/host-exports/subgraph.yaml @@ -10,7 +10,7 @@ dataSources: abi: Contract mapping: kind: ethereum/events - apiVersion: 0.0.4 + apiVersion: 0.0.5 language: wasm/assemblyscript abis: - name: Contract diff --git a/tests/integration-tests/non-fatal-errors/package.json b/tests/integration-tests/non-fatal-errors/package.json index 1cfb67584e8..47a71cccaca 100644 --- a/tests/integration-tests/non-fatal-errors/package.json +++ b/tests/integration-tests/non-fatal-errors/package.json @@ -9,8 +9,8 @@ "deploy:test": "graph deploy test/non-fatal-errors --ipfs $IPFS_URI --node $GRAPH_NODE_ADMIN_URI" }, "devDependencies": { - "@graphprotocol/graph-cli": "https://github.com/graphprotocol/graph-cli#master", - "@graphprotocol/graph-ts": "https://github.com/graphprotocol/graph-ts#master", + "@graphprotocol/graph-cli": "https://github.com/graphprotocol/graph-cli#otavio/update-assembly-script", + "@graphprotocol/graph-ts": "https://github.com/graphprotocol/graph-ts#otavio/update-assembly-script", "solc": "^0.8.2" }, "dependencies": { diff --git a/tests/integration-tests/non-fatal-errors/src/mapping.ts b/tests/integration-tests/non-fatal-errors/src/mapping.ts index 66e9f2c2200..4812e3f5faa 100644 --- a/tests/integration-tests/non-fatal-errors/src/mapping.ts +++ b/tests/integration-tests/non-fatal-errors/src/mapping.ts @@ -13,9 +13,9 @@ export function handleBlockSuccess(block: ethereum.Block): void { let context = new DataSourceContext(); context.setString("id", "00"); Dynamic.createWithContext( - Address.fromHexString( + changetype
(Address.fromHexString( "0x2E645469f354BB4F5c8a05B3b30A929361cf77eC" - ) as Address, + )), context ); } @@ -26,9 +26,9 @@ export function handleBlockError(block: ethereum.Block): void { let context = new DataSourceContext(); context.setString("id", "11"); Dynamic.createWithContext( - Address.fromHexString( + changetype
(Address.fromHexString( "0x3E645469f354BB4F5c8a05B3b30A929361cf77eD" - ) as Address, + )), context ); assert(false); diff --git a/tests/integration-tests/non-fatal-errors/subgraph.yaml b/tests/integration-tests/non-fatal-errors/subgraph.yaml index 001b64f5466..1039ea79ae3 100644 --- a/tests/integration-tests/non-fatal-errors/subgraph.yaml +++ b/tests/integration-tests/non-fatal-errors/subgraph.yaml @@ -12,7 +12,7 @@ dataSources: abi: Contract mapping: kind: ethereum/events - apiVersion: 0.0.4 + apiVersion: 0.0.5 language: wasm/assemblyscript abis: - name: Contract @@ -30,7 +30,7 @@ dataSources: abi: Contract mapping: kind: ethereum/events - apiVersion: 0.0.4 + apiVersion: 0.0.5 language: wasm/assemblyscript abis: - name: Contract @@ -48,7 +48,7 @@ templates: abi: Contract mapping: kind: ethereum/events - apiVersion: 0.0.4 + apiVersion: 0.0.5 language: wasm/assemblyscript abis: - name: Contract diff --git a/tests/integration-tests/overloaded-contract-functions/package.json b/tests/integration-tests/overloaded-contract-functions/package.json index a4182ecfcc9..3d75cffa3ba 100644 --- a/tests/integration-tests/overloaded-contract-functions/package.json +++ b/tests/integration-tests/overloaded-contract-functions/package.json @@ -9,8 +9,8 @@ "deploy:test": "graph deploy test/overloaded-contract-functions --ipfs $IPFS_URI --node $GRAPH_NODE_ADMIN_URI" }, "devDependencies": { - "@graphprotocol/graph-cli": "https://github.com/graphprotocol/graph-cli#master", - "@graphprotocol/graph-ts": "https://github.com/graphprotocol/graph-ts#master", + "@graphprotocol/graph-cli": "https://github.com/graphprotocol/graph-cli#otavio/update-assembly-script", + "@graphprotocol/graph-ts": "https://github.com/graphprotocol/graph-ts#otavio/update-assembly-script", "solc": "^0.8.2" }, "dependencies": { diff --git a/tests/integration-tests/overloaded-contract-functions/src/mapping.ts b/tests/integration-tests/overloaded-contract-functions/src/mapping.ts index 238fa6d092e..191b205075e 100644 --- a/tests/integration-tests/overloaded-contract-functions/src/mapping.ts +++ b/tests/integration-tests/overloaded-contract-functions/src/mapping.ts @@ -7,7 +7,7 @@ export function handleTrigger(event: Trigger): void { let call = new Call("bytes32 -> uint256"); call.value = contract - .exampleFunction(Bytes.fromHexString("0x1234") as Bytes) + .exampleFunction(changetype(Bytes.fromHexString("0x1234"))) .toString(); call.save(); diff --git a/tests/integration-tests/overloaded-contract-functions/subgraph.yaml b/tests/integration-tests/overloaded-contract-functions/subgraph.yaml index 90a9c63ddc3..2b3cbfa922f 100644 --- a/tests/integration-tests/overloaded-contract-functions/subgraph.yaml +++ b/tests/integration-tests/overloaded-contract-functions/subgraph.yaml @@ -10,7 +10,7 @@ dataSources: abi: Contract mapping: kind: ethereum/events - apiVersion: 0.0.4 + apiVersion: 0.0.5 language: wasm/assemblyscript abis: - name: Contract diff --git a/tests/integration-tests/package.json b/tests/integration-tests/package.json index b0e922aba21..9b9f346ca9d 100644 --- a/tests/integration-tests/package.json +++ b/tests/integration-tests/package.json @@ -1,6 +1,7 @@ { "private": true, "workspaces": [ + "api-version-v0-0-4", "arweave-and-3box", "data-source-context", "data-source-revert", diff --git a/tests/integration-tests/remove-then-update/package.json b/tests/integration-tests/remove-then-update/package.json index 8adc6635efd..124c9ffc9b5 100644 --- a/tests/integration-tests/remove-then-update/package.json +++ b/tests/integration-tests/remove-then-update/package.json @@ -9,8 +9,8 @@ "deploy:test": "graph deploy test/remove-then-update --ipfs $IPFS_URI --node $GRAPH_NODE_ADMIN_URI" }, "devDependencies": { - "@graphprotocol/graph-cli": "https://github.com/graphprotocol/graph-cli#master", - "@graphprotocol/graph-ts": "https://github.com/graphprotocol/graph-ts#master", + "@graphprotocol/graph-cli": "https://github.com/graphprotocol/graph-cli#otavio/update-assembly-script", + "@graphprotocol/graph-ts": "https://github.com/graphprotocol/graph-ts#otavio/update-assembly-script", "solc": "^0.8.2" }, "dependencies": { diff --git a/tests/integration-tests/remove-then-update/src/mapping.ts b/tests/integration-tests/remove-then-update/src/mapping.ts index fa0e9858758..9c1e7a67779 100644 --- a/tests/integration-tests/remove-then-update/src/mapping.ts +++ b/tests/integration-tests/remove-then-update/src/mapping.ts @@ -12,7 +12,7 @@ export function handleTrigger(event: Trigger): void { obj.removed = true; obj.save(); - obj = Foo.load("0") as Foo; + obj = Foo.load("0"); assert(obj.value == null); } } diff --git a/tests/integration-tests/remove-then-update/subgraph.yaml b/tests/integration-tests/remove-then-update/subgraph.yaml index 833dd1a3150..09e3122864c 100644 --- a/tests/integration-tests/remove-then-update/subgraph.yaml +++ b/tests/integration-tests/remove-then-update/subgraph.yaml @@ -10,7 +10,7 @@ dataSources: abi: Contract mapping: kind: ethereum/events - apiVersion: 0.0.4 + apiVersion: 0.0.5 language: wasm/assemblyscript abis: - name: Contract diff --git a/tests/integration-tests/value-roundtrip/package.json b/tests/integration-tests/value-roundtrip/package.json index 5fc6a153dbf..9267e8cc3be 100644 --- a/tests/integration-tests/value-roundtrip/package.json +++ b/tests/integration-tests/value-roundtrip/package.json @@ -9,8 +9,8 @@ "deploy:test": "graph deploy test/value-roundtrip --ipfs $IPFS_URI --node $GRAPH_NODE_ADMIN_URI" }, "devDependencies": { - "@graphprotocol/graph-cli": "https://github.com/graphprotocol/graph-cli#master", - "@graphprotocol/graph-ts": "https://github.com/graphprotocol/graph-ts#master", + "@graphprotocol/graph-cli": "https://github.com/graphprotocol/graph-cli#otavio/update-assembly-script", + "@graphprotocol/graph-ts": "https://github.com/graphprotocol/graph-ts#otavio/update-assembly-script", "solc": "^0.8.2" }, "dependencies": { diff --git a/tests/integration-tests/value-roundtrip/src/mapping.ts b/tests/integration-tests/value-roundtrip/src/mapping.ts index d7aeb488f59..dc227e43f6e 100644 --- a/tests/integration-tests/value-roundtrip/src/mapping.ts +++ b/tests/integration-tests/value-roundtrip/src/mapping.ts @@ -7,6 +7,6 @@ export function handleTrigger(event: Trigger): void { obj.save(); // Null characters are stripped. - obj = Foo.load("0") as Foo; + obj = Foo.load("0"); assert(obj.value == "bla", "nulls not stripped"); } diff --git a/tests/integration-tests/value-roundtrip/subgraph.yaml b/tests/integration-tests/value-roundtrip/subgraph.yaml index 833dd1a3150..09e3122864c 100644 --- a/tests/integration-tests/value-roundtrip/subgraph.yaml +++ b/tests/integration-tests/value-roundtrip/subgraph.yaml @@ -10,7 +10,7 @@ dataSources: abi: Contract mapping: kind: ethereum/events - apiVersion: 0.0.4 + apiVersion: 0.0.5 language: wasm/assemblyscript abis: - name: Contract diff --git a/tests/integration-tests/yarn.lock b/tests/integration-tests/yarn.lock index d64f1a52bd2..f45d2216bf2 100644 --- a/tests/integration-tests/yarn.lock +++ b/tests/integration-tests/yarn.lock @@ -781,12 +781,45 @@ optionalDependencies: keytar "^7.4.0" +"@graphprotocol/graph-cli@https://github.com/graphprotocol/graph-cli#otavio/update-assembly-script": + version "0.20.1" + resolved "https://github.com/graphprotocol/graph-cli#8ff5148bb7c5f73a7cc73f77acbb68716696c45a" + dependencies: + assemblyscript "0.19.2" + chalk "^3.0.0" + chokidar "^3.0.2" + debug "^4.1.1" + docker-compose "^0.23.2" + dockerode "^2.5.8" + fs-extra "^9.0.0" + glob "^7.1.2" + gluegun "^4.3.1" + graphql "^15.5.0" + immutable "^3.8.2" + ipfs-http-client "^34.0.0" + jayson "^3.0.2" + js-yaml "^3.13.1" + node-fetch "^2.3.0" + pkginfo "^0.4.1" + prettier "^1.13.5" + request "^2.88.0" + tmp-promise "^3.0.2" + yaml "^1.5.1" + optionalDependencies: + keytar "^7.4.0" + "@graphprotocol/graph-ts@https://github.com/graphprotocol/graph-ts#master": version "0.20.0" resolved "https://github.com/graphprotocol/graph-ts#56adb62d9e4233c6fc6c38bc0519a8a566afdd9e" dependencies: assemblyscript "https://github.com/AssemblyScript/assemblyscript#36040d5b5312f19a025782b5e36663823494c2f3" +"@graphprotocol/graph-ts@https://github.com/graphprotocol/graph-ts#otavio/update-assembly-script": + version "0.21.0" + resolved "https://github.com/graphprotocol/graph-ts#da9d30f105695aa86df90ed559fc984768e0a9f1" + dependencies: + assemblyscript "0.19.2" + "@graphql-tools/batch-delegate@^6.2.4", "@graphql-tools/batch-delegate@^6.2.6": version "6.2.6" resolved "https://registry.yarnpkg.com/@graphql-tools/batch-delegate/-/batch-delegate-6.2.6.tgz#fbea98dc825f87ef29ea5f3f371912c2a2aa2f2c" @@ -2279,6 +2312,14 @@ asn1@~0.2.3: dependencies: safer-buffer "~2.1.0" +assemblyscript@0.19.2: + version "0.19.2" + resolved "https://registry.yarnpkg.com/assemblyscript/-/assemblyscript-0.19.2.tgz#952e185d170d5da3ee3a5314734f6244c3619488" + integrity sha512-0ETRv6oSOhqgPmf23KrD+111QL685IxY/k3UHlC7NzGK2VAi+3Em1U2E06zCPnb45tjUD86wBANlDJDGEZisBg== + dependencies: + binaryen "101.0.0-nightly.20210604" + long "^4.0.0" + "assemblyscript@https://github.com/AssemblyScript/assemblyscript#36040d5b5312f19a025782b5e36663823494c2f3": version "0.6.0" resolved "https://github.com/AssemblyScript/assemblyscript#36040d5b5312f19a025782b5e36663823494c2f3" @@ -2629,6 +2670,11 @@ binary-extensions@^2.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== +binaryen@101.0.0-nightly.20210604: + version "101.0.0-nightly.20210604" + resolved "https://registry.yarnpkg.com/binaryen/-/binaryen-101.0.0-nightly.20210604.tgz#3498a0a0c1108f3386b15ca79f1608425057db9e" + integrity sha512-aTgX1JDN8m3tTFK8g9hazJcEOdQl7mK4yVfElkKAh7q+TRUCaea4a2SMLr1z2xZL7s9N4lkrvrBblxRuEPvxWQ== + binaryen@77.0.0-nightly.20190407: version "77.0.0-nightly.20190407" resolved "https://registry.yarnpkg.com/binaryen/-/binaryen-77.0.0-nightly.20190407.tgz#fbe4f8ba0d6bd0809a84eb519d2d5b5ddff3a7d1" @@ -9681,6 +9727,13 @@ rimraf@^2.2.8, rimraf@^2.6.1, rimraf@^2.6.3: dependencies: glob "^7.1.3" +rimraf@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + ripemd160@^2.0.0, ripemd160@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" @@ -10579,6 +10632,13 @@ title-case@^2.1.0: no-case "^2.2.0" upper-case "^1.0.3" +tmp-promise@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/tmp-promise/-/tmp-promise-3.0.2.tgz#6e933782abff8b00c3119d63589ca1fb9caaa62a" + integrity sha512-OyCLAKU1HzBjL6Ev3gxUeraJNlbNingmi8IrHHEsYH8LTmEuhvYfqvhn2F/je+mjf4N58UmZ96OMEy1JanSCpA== + dependencies: + tmp "^0.2.0" + tmp@0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -10593,6 +10653,13 @@ tmp@^0.1.0: dependencies: rimraf "^2.6.3" +tmp@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" + integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== + dependencies: + rimraf "^3.0.0" + to-absolute-glob@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/to-absolute-glob/-/to-absolute-glob-0.1.1.tgz#1cdfa472a9ef50c239ee66999b662ca0eb39937f" diff --git a/tests/tests/parallel_tests.rs b/tests/tests/parallel_tests.rs index 1b2e16df945..0fd0a22c239 100644 --- a/tests/tests/parallel_tests.rs +++ b/tests/tests/parallel_tests.rs @@ -24,7 +24,8 @@ lazy_static::lazy_static! { } /// All integration tests subdirectories to run -pub const INTEGRATION_TESTS_DIRECTORIES: [&str; 9] = [ +pub const INTEGRATION_TESTS_DIRECTORIES: [&str; 10] = [ + "api-version-v0-0-4", // "arweave-and-3box", "data-source-context", "data-source-revert", @@ -386,6 +387,9 @@ async fn run_graph_node(test_setup: &IntegrationTestSetup) -> anyhow::Result