diff --git a/graph/src/data/subgraph/api_version.rs b/graph/src/data/subgraph/api_version.rs index 6cdc7a6bcc5..f0e7a213af2 100644 --- a/graph/src/data/subgraph/api_version.rs +++ b/graph/src/data/subgraph/api_version.rs @@ -24,6 +24,9 @@ pub const API_VERSION_0_0_8: Version = Version::new(0, 0, 8); /// Enables new host function `eth_get_balance` pub const API_VERSION_0_0_9: Version = Version::new(0, 0, 9); +/// Enables new host function `ethereum.decodeParams` +pub const API_VERSION_0_0_10: Version = Version::new(0, 0, 10); + /// Before this check was introduced, there were already subgraphs in the wild with spec version /// 0.0.3, due to confusion with the api version. To avoid breaking those, we accept 0.0.3 though it /// doesn't exist. diff --git a/graph/src/env/mappings.rs b/graph/src/env/mappings.rs index 83ebef18962..8acab1b5d05 100644 --- a/graph/src/env/mappings.rs +++ b/graph/src/env/mappings.rs @@ -175,7 +175,7 @@ pub struct InnerMappingHandlers { entity_cache_dead_weight: EnvVarBoolean, #[envconfig(from = "GRAPH_ENTITY_CACHE_SIZE", default = "10000")] entity_cache_size_in_kb: usize, - #[envconfig(from = "GRAPH_MAX_API_VERSION", default = "0.0.9")] + #[envconfig(from = "GRAPH_MAX_API_VERSION", default = "0.0.10")] max_api_version: Version, #[envconfig(from = "GRAPH_MAPPING_HANDLER_TIMEOUT")] mapping_handler_timeout_in_secs: Option, diff --git a/runtime/wasm/src/host_exports.rs b/runtime/wasm/src/host_exports.rs index 37963cb8534..b6ab94a066f 100644 --- a/runtime/wasm/src/host_exports.rs +++ b/runtime/wasm/src/host_exports.rs @@ -1236,6 +1236,30 @@ impl HostExports { ty.abi_decode(&data).context("Failed to decode") } + /// Like [`Self::ethereum_decode`], but decodes `data` as ABI function + /// parameters (the layout used by transaction calldata and event data) + /// rather than as a single ABI value. The two differ only for a top-level + /// tuple with at least one dynamic field: calldata has no leading offset + /// word, which `abi_decode` would otherwise expect. + pub(crate) fn ethereum_decode_params( + &self, + types: String, + data: Vec, + gas: &GasCounter, + state: &mut BlockState, + ) -> Result { + Self::track_gas_and_ops( + gas, + state, + gas::DEFAULT_GAS_OP.with_args(complexity::Size, &data), + "ethereum_decode_params", + )?; + + let ty: abi::DynSolType = types.parse().context("Failed to read types")?; + + ty.abi_decode_params(&data).context("Failed to decode") + } + pub(crate) fn yaml_from_bytes( &self, bytes: &[u8], diff --git a/runtime/wasm/src/module/context.rs b/runtime/wasm/src/module/context.rs index 3f32d235378..70d65489304 100644 --- a/runtime/wasm/src/module/context.rs +++ b/runtime/wasm/src/module/context.rs @@ -1180,6 +1180,26 @@ impl WasmInstanceContext<'_> { } } + /// function decodeParams(types: String, data: Bytes): ethereum.Value | null + pub async fn ethereum_decode_params( + &mut self, + gas: &GasCounter, + types_ptr: AscPtr, + data_ptr: AscPtr, + ) -> Result>, HostExportError> { + let types = asc_get(self, types_ptr, gas)?; + let data = asc_get(self, data_ptr, gas)?; + let host_exports = self.as_ref().ctx.host_exports.cheap_clone(); + let ctx = &mut self.as_mut().ctx; + let result = host_exports.ethereum_decode_params(types, data, gas, &mut ctx.state); + + // return `null` if it fails + match result { + Ok(token) => asc_new(self, &token, gas).await, + Err(_) => Ok(AscPtr::null()), + } + } + /// function arweave.transactionData(txId: string): Bytes | null pub async fn arweave_transaction_data( &self, diff --git a/runtime/wasm/src/module/instance.rs b/runtime/wasm/src/module/instance.rs index 3a5f016502d..79017beadc1 100644 --- a/runtime/wasm/src/module/instance.rs +++ b/runtime/wasm/src/module/instance.rs @@ -462,6 +462,12 @@ pub(crate) fn build_linker( link!("ethereum.encode", ethereum_encode, params_ptr); link!("ethereum.decode", ethereum_decode, params_ptr, data_ptr); + link!( + "ethereum.decodeParams", + ethereum_decode_params, + params_ptr, + data_ptr + ); link!("abort", abort, message_ptr, file_name_ptr, line, column);