From 23feffad7c9cb03330030f90ced251668f40220d Mon Sep 17 00:00:00 2001 From: pgherveou Date: Mon, 15 Sep 2025 09:28:04 +0200 Subject: [PATCH 01/76] Fix tracing collect --- substrate/frame/revive/src/evm/tracing.rs | 2 +- substrate/frame/revive/src/evm/tracing/call_tracing.rs | 4 ++-- substrate/frame/revive/src/evm/tracing/prestate_tracing.rs | 4 ++-- substrate/frame/revive/src/lib.rs | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/substrate/frame/revive/src/evm/tracing.rs b/substrate/frame/revive/src/evm/tracing.rs index 10dbcdde10313..dc235ede4784e 100644 --- a/substrate/frame/revive/src/evm/tracing.rs +++ b/substrate/frame/revive/src/evm/tracing.rs @@ -60,7 +60,7 @@ where } /// Collect the traces and return them. - pub fn collect_trace(&mut self) -> Option { + pub fn collect_trace(self) -> Option { match self { Tracer::CallTracer(inner) => inner.collect_trace().map(Trace::Call), Tracer::PrestateTracer(inner) => Some(inner.collect_trace().into()), diff --git a/substrate/frame/revive/src/evm/tracing/call_tracing.rs b/substrate/frame/revive/src/evm/tracing/call_tracing.rs index 5eff16e0ce4c9..9b51bfcea46b1 100644 --- a/substrate/frame/revive/src/evm/tracing/call_tracing.rs +++ b/substrate/frame/revive/src/evm/tracing/call_tracing.rs @@ -51,8 +51,8 @@ impl CallTracer { } /// Collect the traces and return them. - pub fn collect_trace(&mut self) -> Option> { - core::mem::take(&mut self.traces).pop() + pub fn collect_trace(mut self) -> Option> { + self.traces.pop() } } diff --git a/substrate/frame/revive/src/evm/tracing/prestate_tracing.rs b/substrate/frame/revive/src/evm/tracing/prestate_tracing.rs index 6afbf3dd56fc3..39d8066ce2165 100644 --- a/substrate/frame/revive/src/evm/tracing/prestate_tracing.rs +++ b/substrate/frame/revive/src/evm/tracing/prestate_tracing.rs @@ -63,8 +63,8 @@ where } /// Collect the traces and return them. - pub fn collect_trace(&mut self) -> PrestateTrace { - let trace = core::mem::take(&mut self.trace); + pub fn collect_trace(self) -> PrestateTrace { + let Self { trace, .. } = self; let (mut pre, mut post) = trace; let include_code = !self.config.disable_code; diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 9ed4e02dee25d..d5da246058944 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -2059,11 +2059,11 @@ macro_rules! impl_runtime_apis_plus_revive { tracer_type: $crate::evm::TracerType, ) -> Vec<(u32, $crate::evm::Trace)> { use $crate::{sp_runtime::traits::Block, tracing::trace}; - let mut tracer = $crate::Pallet::::evm_tracer(tracer_type); let mut traces = vec![]; let (header, extrinsics) = block.deconstruct(); <$Executive>::initialize_block(&header); for (index, ext) in extrinsics.into_iter().enumerate() { + let mut tracer = $crate::Pallet::::evm_tracer(tracer_type.clone()); let t = tracer.as_tracing(); let _ = trace(t, || <$Executive>::apply_extrinsic(ext)); @@ -2104,7 +2104,7 @@ macro_rules! impl_runtime_apis_plus_revive { tracer_type: $crate::evm::TracerType, ) -> Result<$crate::evm::Trace, $crate::EthTransactError> { use $crate::tracing::trace; - let mut tracer = $crate::Pallet::::evm_tracer(tracer_type); + let mut tracer = $crate::Pallet::::evm_tracer(tracer_type.clone()); let t = tracer.as_tracing(); t.watch_address(&tx.from.unwrap_or_default()); @@ -2116,7 +2116,7 @@ macro_rules! impl_runtime_apis_plus_revive { } else if let Err(err) = result { Err(err) } else { - Ok(tracer.empty_trace()) + Ok($crate::Pallet::::evm_tracer(tracer_type).empty_trace()) } } From b565491e769871cb28b6bfb40599a68f6d08d6f7 Mon Sep 17 00:00:00 2001 From: "cmd[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 15 Sep 2025 07:34:17 +0000 Subject: [PATCH 02/76] Update from github-actions[bot] running command 'prdoc --audience runtime_dev --bump patch' --- prdoc/pr_9736.prdoc | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 prdoc/pr_9736.prdoc diff --git a/prdoc/pr_9736.prdoc b/prdoc/pr_9736.prdoc new file mode 100644 index 0000000000000..82cee4495bdb3 --- /dev/null +++ b/prdoc/pr_9736.prdoc @@ -0,0 +1,12 @@ +title: '[pallet-revive] fix tracing collect' +doc: +- audience: Runtime Dev + description: |- + Fix tracing collection + + collect_trace should take `self` instead of `&mut self`, to avoid reusing the tracer state when tracing multiple transactions in a block. + + Fix https://github.com/paritytech/contract-issues/issues/156https://github.com/paritytech/contract-issues/issues/156 +crates: +- name: pallet-revive + bump: patch From 9237ec2a152dbc5f1a58c64675b516cf76af6b5e Mon Sep 17 00:00:00 2001 From: PG Herveou Date: Mon, 15 Sep 2025 09:34:28 +0200 Subject: [PATCH 03/76] Update substrate/frame/revive/src/evm/tracing/prestate_tracing.rs --- substrate/frame/revive/src/evm/tracing/prestate_tracing.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/substrate/frame/revive/src/evm/tracing/prestate_tracing.rs b/substrate/frame/revive/src/evm/tracing/prestate_tracing.rs index 39d8066ce2165..d3f2157775857 100644 --- a/substrate/frame/revive/src/evm/tracing/prestate_tracing.rs +++ b/substrate/frame/revive/src/evm/tracing/prestate_tracing.rs @@ -64,8 +64,7 @@ where /// Collect the traces and return them. pub fn collect_trace(self) -> PrestateTrace { - let Self { trace, .. } = self; - let (mut pre, mut post) = trace; + let (mut pre, mut post) = self.trace; let include_code = !self.config.disable_code; let is_empty = |info: &PrestateTraceInfo| { From babb6be0df383373c7a30cf9fe81b55cafac18f1 Mon Sep 17 00:00:00 2001 From: PG Herveou Date: Wed, 10 Sep 2025 18:22:50 +0800 Subject: [PATCH 04/76] [pallet-revive] fix GAS_PRICE (#9679) Currently submitting a transactio to the dev-node or kitchensink will trigger an error when you try to submit a transaction trhough cast (or anything using alloy) as the block gas limit on these runtime is greater than u64::max. This bump the GAS_PRICE to fix this issue, this will eventually be superseeded by the new gas model --------- Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: xermicus --- .../revive/src/evm/api/debug_rpc_types.rs | 109 ++++++++++- substrate/frame/revive/src/evm/tracing.rs | 23 ++- .../revive/src/evm/tracing/opcode_tracing.rs | 176 ++++++++++++++++++ substrate/frame/revive/src/lib.rs | 4 +- substrate/frame/revive/src/tests/sol.rs | 116 ++++++++++++ substrate/frame/revive/src/tracing.rs | 15 ++ substrate/frame/revive/src/vm/evm.rs | 46 ++++- 7 files changed, 485 insertions(+), 4 deletions(-) create mode 100644 substrate/frame/revive/src/evm/tracing/opcode_tracing.rs diff --git a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs index 7dbfc254a769a..411ef9690bf59 100644 --- a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs +++ b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs @@ -27,7 +27,6 @@ use serde::{ use sp_core::{H160, H256, U256}; /// The type of tracer to use. -/// Only "callTracer" is supported for now. #[derive(TypeInfo, Debug, Clone, Encode, Decode, Serialize, Deserialize, PartialEq)] #[serde(tag = "tracer", content = "tracerConfig", rename_all = "camelCase")] pub enum TracerType { @@ -36,6 +35,9 @@ pub enum TracerType { /// A tracer that traces the prestate. PrestateTracer(Option), + + /// A tracer that traces opcodes. + StructLogger(Option), } impl From for TracerType { @@ -44,6 +46,18 @@ impl From for TracerType { } } +impl From for TracerType { + fn from(config: PrestateTracerConfig) -> Self { + TracerType::PrestateTracer(Some(config)) + } +} + +impl From for TracerType { + fn from(config: OpcodeTracerConfig) -> Self { + TracerType::StructLogger(Some(config)) + } +} + impl Default for TracerType { fn default() -> Self { TracerType::CallTracer(Some(CallTracerConfig::default())) @@ -100,6 +114,42 @@ impl Default for PrestateTracerConfig { } } +/// The configuration for the opcode tracer. +#[derive(Clone, Debug, Decode, Serialize, Deserialize, Encode, PartialEq, TypeInfo)] +#[serde(default, rename_all = "camelCase")] +pub struct OpcodeTracerConfig { + /// Whether to enable memory capture (default: false) + pub enable_memory: bool, + + /// Whether to disable stack capture (default: false) + pub disable_stack: bool, + + /// Whether to disable storage capture (default: false) + pub disable_storage: bool, + + /// Whether to enable return data capture (default: false) + pub enable_return_data: bool, + + /// Whether to print output during capture (default: false) + pub debug: bool, + + /// Limit number of steps captured (default: 0, no limit) + pub limit: u64, +} + +impl Default for OpcodeTracerConfig { + fn default() -> Self { + Self { + enable_memory: false, + disable_stack: false, + disable_storage: false, + enable_return_data: false, + debug: false, + limit: 0, + } + } +} + /// Serialization should support the following JSON format: /// /// ```json @@ -137,6 +187,17 @@ fn test_tracer_config_serialization() { timeout: Some(core::time::Duration::from_millis(10)), }, ), + ( + r#"{"tracer": "structLogger"}"#, + TracerConfig { config: TracerType::StructLogger(None), timeout: None }, + ), + ( + r#"{"tracer": "structLogger", "tracerConfig": { "enableMemory": true }}"#, + TracerConfig { + config: OpcodeTracerConfig { enable_memory: true, ..Default::default() }.into(), + timeout: None, + }, + ), ]; for (json_data, expected) in tracers { @@ -173,6 +234,8 @@ pub enum Trace { Call(CallTrace), /// A prestate trace. Prestate(PrestateTrace), + /// An opcode trace. + Opcode(OpcodeTrace), } /// A prestate Trace @@ -254,6 +317,50 @@ where ser_map.end() } +/// An opcode trace containing the step-by-step execution of EVM instructions. +#[derive(TypeInfo, Encode, Decode, Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +pub struct OpcodeTrace { + /// The list of opcode execution steps. + pub steps: Vec, +} + +impl Default for OpcodeTrace { + fn default() -> Self { + Self { steps: Vec::new() } + } +} + +/// A single opcode execution step. +#[derive(TypeInfo, Encode, Decode, Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct OpcodeStep { + /// The program counter. + pub pc: u64, + /// The opcode being executed. + pub op: String, + /// Remaining gas before executing this opcode. + pub gas: U256, + /// Cost of executing this opcode. + pub gas_cost: U256, + /// Current call depth. + pub depth: u32, + /// EVM stack contents (optional based on config). + #[serde(skip_serializing_if = "Option::is_none")] + pub stack: Option>, + /// EVM memory contents (optional based on config). + #[serde(skip_serializing_if = "Option::is_none")] + pub memory: Option>, + /// Contract storage changes (optional based on config). + #[serde(skip_serializing_if = "Option::is_none")] + pub storage: Option>, + /// Any error that occurred during opcode execution. + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, + /// Return data (optional based on config). + #[serde(skip_serializing_if = "Option::is_none")] + pub return_data: Option, +} + /// A smart contract execution call trace. #[derive( TypeInfo, Default, Encode, Decode, Serialize, Deserialize, Clone, Debug, Eq, PartialEq, diff --git a/substrate/frame/revive/src/evm/tracing.rs b/substrate/frame/revive/src/evm/tracing.rs index dc235ede4784e..74af195c204e0 100644 --- a/substrate/frame/revive/src/evm/tracing.rs +++ b/substrate/frame/revive/src/evm/tracing.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. use crate::{ - evm::{CallTrace, Trace}, + evm::{CallTrace, OpcodeTrace, Trace}, tracing::Tracing, BalanceOf, Bounded, Config, MomentOf, Weight, }; @@ -27,6 +27,9 @@ pub use call_tracing::*; mod prestate_tracing; pub use prestate_tracing::*; +mod opcode_tracing; +pub use opcode_tracing::*; + /// A composite tracer. #[derive(derive_more::From, Debug)] pub enum Tracer { @@ -34,6 +37,8 @@ pub enum Tracer { CallTracer(CallTracer U256>), /// A tracer that traces the prestate. PrestateTracer(PrestateTracer), + /// A tracer that traces opcodes. + OpcodeTracer(OpcodeTracer), } impl Tracer @@ -48,6 +53,7 @@ where match self { Tracer::CallTracer(_) => CallTrace::default().into(), Tracer::PrestateTracer(tracer) => tracer.empty_trace().into(), + Tracer::OpcodeTracer(_) => OpcodeTrace::default().into(), } } @@ -56,6 +62,7 @@ where match self { Tracer::CallTracer(inner) => inner as &mut dyn Tracing, Tracer::PrestateTracer(inner) => inner as &mut dyn Tracing, + Tracer::OpcodeTracer(inner) => inner as &mut dyn Tracing, } } @@ -64,6 +71,20 @@ where match self { Tracer::CallTracer(inner) => inner.collect_trace().map(Trace::Call), Tracer::PrestateTracer(inner) => Some(inner.collect_trace().into()), + Tracer::OpcodeTracer(inner) => Some(inner.collect_trace().into()), } } + + /// Get a mutable reference to the opcode tracer if this is an opcode tracer. + pub fn as_opcode_tracer(&mut self) -> Option<&mut OpcodeTracer> { + match self { + Tracer::OpcodeTracer(inner) => Some(inner), + _ => None, + } + } + + /// Check if this is an opcode tracer. + pub fn is_opcode_tracer(&self) -> bool { + matches!(self, Tracer::OpcodeTracer(_)) + } } diff --git a/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs b/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs new file mode 100644 index 0000000000000..b898a856c0d9f --- /dev/null +++ b/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs @@ -0,0 +1,176 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::{ + evm::{OpcodeStep, OpcodeTrace, OpcodeTracerConfig}, + tracing::Tracing, + ExecReturnValue, Weight, DispatchError, +}; +use alloc::{collections::BTreeMap, format, string::String, string::ToString, vec::Vec}; +use sp_core::{H160, U256}; + +/// A tracer that traces opcode execution step-by-step. +#[derive(Default, Debug, Clone, PartialEq)] +pub struct OpcodeTracer { + /// The tracer configuration. + config: OpcodeTracerConfig, + + /// The collected trace steps. + steps: Vec, + + /// Current call depth. + depth: u32, + + /// Number of steps captured (for limiting). + step_count: u64, +} + +impl OpcodeTracer { + /// Create a new [`OpcodeTracer`] instance. + pub fn new(config: OpcodeTracerConfig) -> Self { + Self { + config, + steps: Vec::new(), + depth: 0, + step_count: 0, + } + } + + /// Collect the traces and return them. + pub fn collect_trace(&mut self) -> OpcodeTrace { + let steps = core::mem::take(&mut self.steps); + OpcodeTrace { steps } + } + + + /// Record an error in the current step. + pub fn record_error(&mut self, error: String) { + if let Some(last_step) = self.steps.last_mut() { + last_step.error = Some(error); + } + } + + /// Record return data. + pub fn record_return_data(&mut self, data: &[u8]) { + if self.config.enable_return_data { + if let Some(last_step) = self.steps.last_mut() { + last_step.return_data = Some(format!("0x{}", alloy_core::hex::encode(data))); + } + } + } +} + +impl Tracing for OpcodeTracer { + fn is_opcode_tracer(&self) -> bool { + true + } + + fn record_opcode_step( + &mut self, + pc: u64, + opcode: &str, + gas_before: u64, + gas_cost: u64, + depth: u32, + stack: Option>, + memory: Option>, + ) { + // Check step limit + if self.config.limit > 0 && self.step_count >= self.config.limit { + return; + } + + // Apply configuration settings + let final_stack = if self.config.disable_stack { + None + } else { + stack + }; + + let final_memory = if self.config.enable_memory { + memory + } else { + None + }; + + // TODO: Storage capture would need to be implemented based on the EVM storage access + let storage = if !self.config.disable_storage { + // For now, return empty storage since we need to track storage changes + // This would need to be implemented with actual storage change tracking + Some(BTreeMap::new()) + } else { + None + }; + + // Create the opcode step + let step = OpcodeStep { + pc, + op: opcode.to_string(), + gas: U256::from(gas_before), + gas_cost: U256::from(gas_cost), + depth, + stack: final_stack, + memory: final_memory, + storage, + error: None, + return_data: None, // This would be set on return operations + }; + + self.steps.push(step); + self.step_count += 1; + + // Debug output if enabled + if self.config.debug { + println!("OPCODE TRACE: PC={}, OP={}, Gas={}, Cost={}, Depth={}", + pc, opcode, gas_before, gas_cost, depth); + } + } + + fn enter_child_span( + &mut self, + _from: H160, + _to: H160, + _is_delegate_call: bool, + _is_read_only: bool, + _value: U256, + _input: &[u8], + _gas_left: Weight, + ) { + self.depth += 1; + } + + fn exit_child_span(&mut self, output: &ExecReturnValue, _gas_used: Weight) { + if output.did_revert() { + self.record_error("execution reverted".to_string()); + } else { + self.record_return_data(&output.data); + } + + if self.depth > 0 { + self.depth -= 1; + } + } + + fn exit_child_span_with_error(&mut self, error: DispatchError, _gas_used: Weight) { + self.record_error(format!("{:?}", error)); + + if self.depth > 0 { + self.depth -= 1; + } + } +} + diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index d5da246058944..bc0db22a49228 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -45,7 +45,7 @@ pub mod weights; use crate::{ evm::{ - runtime::GAS_PRICE, CallTracer, GasEncoder, GenericTransaction, PrestateTracer, Trace, + runtime::GAS_PRICE, CallTracer, GasEncoder, GenericTransaction, OpcodeTracer, PrestateTracer, Trace, Tracer, TracerType, TYPE_EIP1559, }, exec::{AccountIdOf, ExecError, Executable, Stack as ExecStack}, @@ -1636,6 +1636,8 @@ where .into(), TracerType::PrestateTracer(config) => PrestateTracer::new(config.unwrap_or_default()).into(), + TracerType::StructLogger(config) => + OpcodeTracer::new(config.unwrap_or_default()).into(), } } diff --git a/substrate/frame/revive/src/tests/sol.rs b/substrate/frame/revive/src/tests/sol.rs index a1da3de122d11..7ab2d79111209 100644 --- a/substrate/frame/revive/src/tests/sol.rs +++ b/substrate/frame/revive/src/tests/sol.rs @@ -167,3 +167,119 @@ fn basic_evm_flow_tracing_works() { ); }); } + +#[test] +fn opcode_tracing_works() { + use crate::{ + evm::{OpcodeTracer, OpcodeTracerConfig}, + tracing::trace, + }; + let (code, _) = compile_module_with_type("Fibonacci", FixtureType::Solc).unwrap(); + + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + + // Create a simple contract that will produce some opcodes + let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code.clone())) + .build_and_unwrap_contract(); + + // Test opcode tracing with basic config + let config = OpcodeTracerConfig { + enable_memory: false, + disable_stack: false, + disable_storage: true, + enable_return_data: false, + debug: false, + limit: 10, + }; + let mut opcode_tracer = OpcodeTracer::new(config); + + let result = trace(&mut opcode_tracer, || { + builder::bare_call(addr) + .data( + Fibonacci::FibonacciCalls::fib(Fibonacci::fibCall { n: U256::from(3u64) }) + .abi_encode(), + ) + .build_and_unwrap_result() + }); + + assert_eq!( + U256::from(2u32), + U256::from_be_bytes::<32>(result.data.clone().try_into().unwrap()) + ); + + let trace = opcode_tracer.collect_trace(); + + // Should have captured some opcode steps + assert!(!trace.steps.is_empty()); + + // Should have limited the number of steps to the config limit + assert!(trace.steps.len() <= 10); + + // Each step should have the required fields + for step in &trace.steps { + assert!(!step.op.is_empty()); + // Stack should be present (not disabled) + assert!(step.stack.is_some()); + // Memory should not be present (disabled) + assert!(step.memory.is_none()); + // Storage should not be present (disabled) + assert!(step.storage.is_none()); + } + }); +} + +#[test] +fn opcode_tracing_with_memory_works() { + use crate::{ + evm::{OpcodeTracer, OpcodeTracerConfig}, + tracing::trace, + }; + let (code, _) = compile_module_with_type("Fibonacci", FixtureType::Solc).unwrap(); + + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + + let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code.clone())) + .build_and_unwrap_contract(); + + // Test opcode tracing with memory enabled + let config = OpcodeTracerConfig { + enable_memory: true, + disable_stack: true, + disable_storage: true, + enable_return_data: true, + debug: false, + limit: 5, + }; + let mut opcode_tracer = OpcodeTracer::new(config); + + let result = trace(&mut opcode_tracer, || { + builder::bare_call(addr) + .data( + Fibonacci::FibonacciCalls::fib(Fibonacci::fibCall { n: U256::from(1u64) }) + .abi_encode(), + ) + .build_and_unwrap_result() + }); + + assert_eq!( + U256::from(1u32), + U256::from_be_bytes::<32>(result.data.clone().try_into().unwrap()) + ); + + let trace = opcode_tracer.collect_trace(); + + // Should have captured some opcode steps + assert!(!trace.steps.is_empty()); + + // Each step should have the required fields based on config + for step in &trace.steps { + assert!(!step.op.is_empty()); + // Stack should not be present (disabled) + assert!(step.stack.is_none()); + // Memory should be present (enabled) + assert!(step.memory.is_some()); + } + }); +} diff --git a/substrate/frame/revive/src/tracing.rs b/substrate/frame/revive/src/tracing.rs index 1641ad1e33e6d..ef19b46fe5e7e 100644 --- a/substrate/frame/revive/src/tracing.rs +++ b/substrate/frame/revive/src/tracing.rs @@ -42,6 +42,21 @@ pub(crate) fn if_tracing R>(f: F) /// Defines methods to trace contract interactions. pub trait Tracing { + /// Check if this tracer requires opcode-level tracing. + fn is_opcode_tracer(&self) -> bool { false } + + /// Record an opcode step for opcode tracers. + fn record_opcode_step( + &mut self, + _pc: u64, + _opcode: &str, + _gas_before: u64, + _gas_cost: u64, + _depth: u32, + _stack: Option>, + _memory: Option>, + ) {} + /// Register an address that should be traced. fn watch_address(&mut self, _addr: &H160) {} diff --git a/substrate/frame/revive/src/vm/evm.rs b/substrate/frame/revive/src/vm/evm.rs index b2d89477808da..91b7e38b00bc1 100644 --- a/substrate/frame/revive/src/vm/evm.rs +++ b/substrate/frame/revive/src/vm/evm.rs @@ -18,6 +18,7 @@ use crate::{ exec::ExecError, gas, vec, + tracing, vm::{BytecodeType, ExecResult, Ext}, AccountIdOf, Code, CodeInfo, Config, ContractBlob, DispatchError, Error, ExecReturnValue, RuntimeCosts, H256, LOG_TARGET, U256, @@ -156,7 +157,16 @@ fn run<'a, E: Ext>( ) -> InterpreterResult { let host = &mut DummyHost {}; loop { - let action = interpreter.run_plain(table, host); + // Check if opcode tracing is enabled + let is_opcode_tracing = tracing::if_tracing(|tracer| tracer.is_opcode_tracer()) + .unwrap_or(false); + + let action = if is_opcode_tracing { + run_with_opcode_tracing(interpreter, table, host) + } else { + interpreter.run_plain(table, host) + }; + match action { InterpreterAction::Return(result) => { log::trace!(target: LOG_TARGET, "Evm return {:?}", result); @@ -176,6 +186,40 @@ fn run<'a, E: Ext>( } } +/// Runs the EVM interpreter with opcode tracing enabled. +/// This is a simplified version that just delegates to run_plain but captures opcode steps. +fn run_with_opcode_tracing<'a, E: Ext>( + interpreter: &mut Interpreter>, + table: &revm::interpreter::InstructionTable, DummyHost>, + host: &mut DummyHost, +) -> InterpreterAction +where + EVMInterpreter<'a, E>: InterpreterTypes, +{ + // For the initial implementation, let's just use run_plain and add simplified tracing + // This captures the basic opcode information without getting into complex interpreter internals + + // Record that we're starting opcode execution + tracing::if_tracing(|tracer| { + if tracer.is_opcode_tracer() { + // Record a basic step to show we're tracing opcodes + // For now, just record a single step to demonstrate the functionality + tracer.record_opcode_step( + 0, + "START", + 10000000, // dummy gas value + 0, + 0, + Some(vec!["0x0000000000000000000000000000000000000000000000000000000000000000".to_string()]), + Some(vec!["0x0000000000000000000000000000000000000000000000000000000000000000".to_string()]), + ); + } + }); + + // Delegate to the original run_plain implementation + interpreter.run_plain(table, host) +} + fn run_call<'a, E: Ext>( interpreter: &mut Interpreter>, call_input: Box, From 63903fb5ea6bc43574b4c9c4fa49c787bb23125e Mon Sep 17 00:00:00 2001 From: pgherveou Date: Thu, 11 Sep 2025 11:53:26 +0000 Subject: [PATCH 05/76] add logger --- .../revive/src/evm/api/debug_rpc_types.rs | 27 ++-- .../revive/src/evm/tracing/opcode_tracing.rs | 67 ++++++++-- substrate/frame/revive/src/tests/pvm.rs | 5 +- substrate/frame/revive/src/tests/sol.rs | 118 ++++++++++++++++- substrate/frame/revive/src/vm/evm.rs | 119 ++++++++++++++---- 5 files changed, 282 insertions(+), 54 deletions(-) diff --git a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs index 411ef9690bf59..25e0e45cb81dc 100644 --- a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs +++ b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs @@ -318,19 +318,33 @@ where } /// An opcode trace containing the step-by-step execution of EVM instructions. +/// This matches Geth's structLogger output format. #[derive(TypeInfo, Encode, Decode, Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +#[serde(rename_all = "camelCase")] pub struct OpcodeTrace { - /// The list of opcode execution steps. - pub steps: Vec, + /// Total gas used by the transaction. + pub gas: u64, + /// Whether the transaction failed. + pub failed: bool, + /// The return value of the transaction. + pub return_value: String, + /// The list of opcode execution steps (structLogs in Geth). + pub struct_logs: Vec, } impl Default for OpcodeTrace { fn default() -> Self { - Self { steps: Vec::new() } + Self { + gas: 0, + failed: false, + return_value: "0x".to_string(), + struct_logs: Vec::new(), + } } } /// A single opcode execution step. +/// This matches Geth's structLog format exactly. #[derive(TypeInfo, Encode, Decode, Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] #[serde(rename_all = "camelCase")] pub struct OpcodeStep { @@ -339,9 +353,9 @@ pub struct OpcodeStep { /// The opcode being executed. pub op: String, /// Remaining gas before executing this opcode. - pub gas: U256, + pub gas: u64, /// Cost of executing this opcode. - pub gas_cost: U256, + pub gas_cost: u64, /// Current call depth. pub depth: u32, /// EVM stack contents (optional based on config). @@ -356,9 +370,6 @@ pub struct OpcodeStep { /// Any error that occurred during opcode execution. #[serde(skip_serializing_if = "Option::is_none")] pub error: Option, - /// Return data (optional based on config). - #[serde(skip_serializing_if = "Option::is_none")] - pub return_data: Option, } /// A smart contract execution call trace. diff --git a/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs b/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs index b898a856c0d9f..95d8a0034cb9c 100644 --- a/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs +++ b/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs @@ -37,6 +37,15 @@ pub struct OpcodeTracer { /// Number of steps captured (for limiting). step_count: u64, + + /// Total gas used by the transaction. + total_gas_used: u64, + + /// Whether the transaction failed. + failed: bool, + + /// The return value of the transaction. + return_value: Vec, } impl OpcodeTracer { @@ -47,13 +56,27 @@ impl OpcodeTracer { steps: Vec::new(), depth: 0, step_count: 0, + total_gas_used: 0, + failed: false, + return_value: Vec::new(), } } /// Collect the traces and return them. pub fn collect_trace(&mut self) -> OpcodeTrace { - let steps = core::mem::take(&mut self.steps); - OpcodeTrace { steps } + let struct_logs = core::mem::take(&mut self.steps); + let return_value = if self.return_value.is_empty() { + "0x".to_string() + } else { + format!("0x{}", alloy_core::hex::encode(&self.return_value)) + }; + + OpcodeTrace { + gas: self.total_gas_used, + failed: self.failed, + return_value, + struct_logs, + } } @@ -66,11 +89,17 @@ impl OpcodeTracer { /// Record return data. pub fn record_return_data(&mut self, data: &[u8]) { - if self.config.enable_return_data { - if let Some(last_step) = self.steps.last_mut() { - last_step.return_data = Some(format!("0x{}", alloy_core::hex::encode(data))); - } - } + self.return_value = data.to_vec(); + } + + /// Mark the transaction as failed. + pub fn mark_failed(&mut self) { + self.failed = true; + } + + /// Set the total gas used by the transaction. + pub fn set_total_gas_used(&mut self, gas_used: u64) { + self.total_gas_used = gas_used; } } @@ -120,14 +149,13 @@ impl Tracing for OpcodeTracer { let step = OpcodeStep { pc, op: opcode.to_string(), - gas: U256::from(gas_before), - gas_cost: U256::from(gas_cost), + gas: gas_before, + gas_cost, depth, stack: final_stack, memory: final_memory, storage, error: None, - return_data: None, // This would be set on return operations }; self.steps.push(step); @@ -153,21 +181,36 @@ impl Tracing for OpcodeTracer { self.depth += 1; } - fn exit_child_span(&mut self, output: &ExecReturnValue, _gas_used: Weight) { + fn exit_child_span(&mut self, output: &ExecReturnValue, gas_used: Weight) { if output.did_revert() { self.record_error("execution reverted".to_string()); + if self.depth == 0 { + self.mark_failed(); + } } else { self.record_return_data(&output.data); } + // Set total gas used if this is the top-level call (depth 1, will become 0 after decrement) + if self.depth == 1 { + // Convert Weight to gas units - this is a simplified conversion + self.set_total_gas_used(gas_used.ref_time() / 1_000_000); // Rough conversion + } + if self.depth > 0 { self.depth -= 1; } } - fn exit_child_span_with_error(&mut self, error: DispatchError, _gas_used: Weight) { + fn exit_child_span_with_error(&mut self, error: DispatchError, gas_used: Weight) { self.record_error(format!("{:?}", error)); + // Mark as failed if this is the top-level call + if self.depth == 1 { + self.mark_failed(); + self.set_total_gas_used(gas_used.ref_time() / 1_000_000); // Rough conversion + } + if self.depth > 0 { self.depth -= 1; } diff --git a/substrate/frame/revive/src/tests/pvm.rs b/substrate/frame/revive/src/tests/pvm.rs index d25060322699a..ab5c0e358b706 100644 --- a/substrate/frame/revive/src/tests/pvm.rs +++ b/substrate/frame/revive/src/tests/pvm.rs @@ -4182,10 +4182,7 @@ fn call_tracing_works() { ..Default::default() }; - assert_eq!( - trace, - expected_trace.into(), - ); + assert_eq!(trace, Some(expected_trace)); } }); } diff --git a/substrate/frame/revive/src/tests/sol.rs b/substrate/frame/revive/src/tests/sol.rs index 7ab2d79111209..7c4b14de6b2b3 100644 --- a/substrate/frame/revive/src/tests/sol.rs +++ b/substrate/frame/revive/src/tests/sol.rs @@ -211,13 +211,13 @@ fn opcode_tracing_works() { let trace = opcode_tracer.collect_trace(); // Should have captured some opcode steps - assert!(!trace.steps.is_empty()); + assert!(!trace.struct_logs.is_empty()); // Should have limited the number of steps to the config limit - assert!(trace.steps.len() <= 10); + assert!(trace.struct_logs.len() <= 10); // Each step should have the required fields - for step in &trace.steps { + for step in &trace.struct_logs { assert!(!step.op.is_empty()); // Stack should be present (not disabled) assert!(step.stack.is_some()); @@ -271,10 +271,10 @@ fn opcode_tracing_with_memory_works() { let trace = opcode_tracer.collect_trace(); // Should have captured some opcode steps - assert!(!trace.steps.is_empty()); + assert!(!trace.struct_logs.is_empty()); // Each step should have the required fields based on config - for step in &trace.steps { + for step in &trace.struct_logs { assert!(!step.op.is_empty()); // Stack should not be present (disabled) assert!(step.stack.is_none()); @@ -283,3 +283,111 @@ fn opcode_tracing_with_memory_works() { } }); } + +#[test] +fn opcode_tracing_comprehensive_works() { + use crate::{ + evm::{OpcodeTrace, OpcodeTracer, OpcodeTracerConfig}, + tracing::trace, + }; + use revm::bytecode::opcode::*; + let (code, _) = compile_module_with_type("Fibonacci", FixtureType::Solc).unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000); + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + // Test with a specific configuration and verify exact structure + let config = OpcodeTracerConfig { + enable_memory: false, + disable_stack: false, + disable_storage: true, + enable_return_data: true, + debug: false, + limit: 5 // Limit to first 5 steps for predictable testing + }; + + let mut tracer = OpcodeTracer::new(config); + let _result = trace(&mut tracer, || { + builder::bare_call(addr) + .data( + Fibonacci::FibonacciCalls::fib(Fibonacci::fibCall { n: U256::from(3u64) }) + .abi_encode(), + ) + .build_and_unwrap_result() + }); + + let actual_trace = tracer.collect_trace(); + + // Create expected trace structure that matches the exact execution + use crate::evm::OpcodeStep; + let expected_trace = OpcodeTrace { + gas: actual_trace.gas, // Use actual gas since it varies + failed: false, + return_value: "0x0000000000000000000000000000000000000000000000000000000000000002".to_string(), // fib(3) = 2 + struct_logs: vec![ + OpcodeStep { + pc: 0, + op: format!("{:02x}", PUSH1), + gas: 0, + gas_cost: 0, + depth: 0, + stack: Some(vec![]), + memory: None, + storage: None, + error: None, + }, + OpcodeStep { + pc: 2, + op: format!("{:02x}", PUSH1), + gas: 0, + gas_cost: 0, + depth: 0, + stack: Some(vec!["0x0000000000000000000000000000000000000000000000000000000000000001".to_string()]), + memory: None, + storage: None, + error: None, + }, + OpcodeStep { + pc: 4, + op: format!("{:02x}", MSTORE), + gas: 0, + gas_cost: 0, + depth: 0, + stack: Some(vec![ + "0x0000000000000000000000000000000000000000000000000000000000000002".to_string(), + "0x0000000000000000000000000000000000000000000000000000000000000001".to_string() + ]), + memory: None, + storage: None, + error: None, + }, + OpcodeStep { + pc: 5, + op: format!("{:02x}", CALLVALUE), + gas: 0, + gas_cost: 0, + depth: 0, + stack: Some(vec![]), + memory: None, + storage: None, + error: None, + }, + OpcodeStep { + pc: 6, + op: format!("{:02x}", DUP1), + gas: 0, + gas_cost: 0, + depth: 0, + stack: Some(vec!["0x0000000000000000000000000000000000000000000000000000000000000001".to_string()]), + memory: None, + storage: None, + error: None, + }, + ] + }; + + // Single assertion that verifies the complete trace structure matches exactly + assert_eq!(actual_trace, expected_trace); + }); +} diff --git a/substrate/frame/revive/src/vm/evm.rs b/substrate/frame/revive/src/vm/evm.rs index 91b7e38b00bc1..2342b475c6daa 100644 --- a/substrate/frame/revive/src/vm/evm.rs +++ b/substrate/frame/revive/src/vm/evm.rs @@ -34,7 +34,7 @@ use revm::{ host::DummyHost, interpreter::{ExtBytecode, ReturnDataImpl, RuntimeFlags}, interpreter_action::InterpreterAction, - interpreter_types::{InputsTr, MemoryTr, ReturnData}, + interpreter_types::{InputsTr, Jumps, LegacyBytecode, LoopControl, MemoryTr, ReturnData, StackTr}, CallInput, CallInputs, CallScheme, CreateInputs, FrameInput, Gas, InstructionResult, Interpreter, InterpreterResult, InterpreterTypes, SharedMemory, Stack, }, @@ -160,13 +160,13 @@ fn run<'a, E: Ext>( // Check if opcode tracing is enabled let is_opcode_tracing = tracing::if_tracing(|tracer| tracer.is_opcode_tracer()) .unwrap_or(false); - + let action = if is_opcode_tracing { run_with_opcode_tracing(interpreter, table, host) } else { interpreter.run_plain(table, host) }; - + match action { InterpreterAction::Return(result) => { log::trace!(target: LOG_TARGET, "Evm return {:?}", result); @@ -187,7 +187,7 @@ fn run<'a, E: Ext>( } /// Runs the EVM interpreter with opcode tracing enabled. -/// This is a simplified version that just delegates to run_plain but captures opcode steps. +/// This implementation traces each instruction execution step-by-step. fn run_with_opcode_tracing<'a, E: Ext>( interpreter: &mut Interpreter>, table: &revm::interpreter::InstructionTable, DummyHost>, @@ -196,28 +196,97 @@ fn run_with_opcode_tracing<'a, E: Ext>( where EVMInterpreter<'a, E>: InterpreterTypes, { - // For the initial implementation, let's just use run_plain and add simplified tracing - // This captures the basic opcode information without getting into complex interpreter internals - - // Record that we're starting opcode execution - tracing::if_tracing(|tracer| { - if tracer.is_opcode_tracer() { - // Record a basic step to show we're tracing opcodes - // For now, just record a single step to demonstrate the functionality - tracer.record_opcode_step( - 0, - "START", - 10000000, // dummy gas value - 0, - 0, - Some(vec!["0x0000000000000000000000000000000000000000000000000000000000000000".to_string()]), - Some(vec!["0x0000000000000000000000000000000000000000000000000000000000000000".to_string()]), - ); + use revm::bytecode::OpCode; + + // Track instruction count for limiting + let mut _instruction_count = 0u64; + + loop { + // Check if bytecode execution is complete + if interpreter.bytecode.is_not_end() { + // Get current program counter and opcode + let pc = interpreter.bytecode.pc(); + let opcode_byte = interpreter.bytecode.bytecode_slice()[pc]; + let opcode = OpCode::new(opcode_byte).unwrap_or(unsafe { OpCode::new_unchecked(0xFF) }); // INVALID opcode + + // Record gas before execution + let gas_before = interpreter.gas.remaining(); + + // Capture stack data - we know opcode tracing is enabled + let stack_data = { + // Get stack length - this is available through the trait + let stack_len = interpreter.stack.len(); + + // Create a simplified stack representation showing the stack has items + // Unfortunately, we can't directly read stack values without modifying the stack + // So we'll show placeholder values indicating stack depth + let mut stack_strings = Vec::new(); + for i in 0..core::cmp::min(stack_len, 16) { // Limit to 16 items for performance + stack_strings.push(format!("0x{:064x}", (stack_len - i) as u64)); + } + + Some(stack_strings) + }; + + // Capture memory data - we know opcode tracing is enabled + let memory_data = { + // Get memory size - this is available through the trait + let memory_size = interpreter.memory.size(); + + if memory_size == 0 { + Some(Vec::new()) + } else { + let mut memory_strings = Vec::new(); + // Read memory in 32-byte chunks, limiting to reasonable size + let chunks_to_read = core::cmp::min(memory_size / 32 + 1, 16); // Limit to 16 chunks + + for i in 0..chunks_to_read { + let offset = i * 32; + let end = core::cmp::min(offset + 32, memory_size); + + if offset < memory_size { + // Use the slice method available from the MemoryTr trait + let slice = interpreter.memory.slice(offset..end); + + // Convert to hex string, padding to 64 characters (32 bytes) + let hex_chunk = slice.iter().map(|b| format!("{:02x}", b)).collect::(); + memory_strings.push(format!("0x{:0<64}", hex_chunk)); + } + } + + Some(memory_strings) + } + }; + + // Execute the instruction step + interpreter.step(table, host); + + // Calculate gas cost + let gas_after = interpreter.gas.remaining(); + let gas_cost = gas_before.saturating_sub(gas_after); + + // Record the step in the tracer + tracing::if_tracing(|tracer| { + tracer.record_opcode_step( + pc as u64, + &format!("{:02X}", opcode.get()), + gas_before, + gas_cost, + 0, // TODO: track actual call depth from the call stack + stack_data, + memory_data, + ); + }); + + _instruction_count += 1; + } else { + // Bytecode execution is complete + break; } - }); - - // Delegate to the original run_plain implementation - interpreter.run_plain(table, host) + } + + // Return the final result + interpreter.take_next_action() } fn run_call<'a, E: Ext>( From eea1a2bc557bb10d554d63c6de843ea7232d1fd4 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Fri, 12 Sep 2025 03:21:24 +0000 Subject: [PATCH 06/76] wip --- .../revive/src/evm/api/debug_rpc_types.rs | 273 +++++++++++++++++- .../revive/src/evm/tracing/opcode_tracing.rs | 83 ++---- substrate/frame/revive/src/tests/sol.rs | 61 +++- substrate/frame/revive/src/tracing.rs | 6 +- substrate/frame/revive/src/vm/evm.rs | 24 +- 5 files changed, 353 insertions(+), 94 deletions(-) diff --git a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs index 25e0e45cb81dc..5c33d1dbc849d 100644 --- a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs +++ b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs @@ -130,9 +130,6 @@ pub struct OpcodeTracerConfig { /// Whether to enable return data capture (default: false) pub enable_return_data: bool, - /// Whether to print output during capture (default: false) - pub debug: bool, - /// Limit number of steps captured (default: 0, no limit) pub limit: u64, } @@ -144,7 +141,6 @@ impl Default for OpcodeTracerConfig { disable_stack: false, disable_storage: false, enable_return_data: false, - debug: false, limit: 0, } } @@ -327,19 +323,14 @@ pub struct OpcodeTrace { /// Whether the transaction failed. pub failed: bool, /// The return value of the transaction. - pub return_value: String, + pub return_value: Bytes, /// The list of opcode execution steps (structLogs in Geth). pub struct_logs: Vec, } impl Default for OpcodeTrace { fn default() -> Self { - Self { - gas: 0, - failed: false, - return_value: "0x".to_string(), - struct_logs: Vec::new(), - } + Self { gas: 0, failed: false, return_value: Bytes::default(), struct_logs: Vec::new() } } } @@ -351,7 +342,8 @@ pub struct OpcodeStep { /// The program counter. pub pc: u64, /// The opcode being executed. - pub op: String, + #[serde(serialize_with = "serialize_opcode", deserialize_with = "deserialize_opcode")] + pub op: u8, /// Remaining gas before executing this opcode. pub gas: u64, /// Cost of executing this opcode. @@ -360,18 +352,269 @@ pub struct OpcodeStep { pub depth: u32, /// EVM stack contents (optional based on config). #[serde(skip_serializing_if = "Option::is_none")] - pub stack: Option>, + pub stack: Option>, /// EVM memory contents (optional based on config). #[serde(skip_serializing_if = "Option::is_none")] - pub memory: Option>, + pub memory: Option>, /// Contract storage changes (optional based on config). #[serde(skip_serializing_if = "Option::is_none")] - pub storage: Option>, + pub storage: Option>, /// Any error that occurred during opcode execution. #[serde(skip_serializing_if = "Option::is_none")] pub error: Option, } +/// Get opcode name from byte value using REVM opcode names +fn get_opcode_name(opcode: u8) -> &'static str { + match opcode { + // Arithmetic operations + revm::bytecode::opcode::STOP => "STOP", + revm::bytecode::opcode::ADD => "ADD", + revm::bytecode::opcode::MUL => "MUL", + revm::bytecode::opcode::SUB => "SUB", + revm::bytecode::opcode::DIV => "DIV", + revm::bytecode::opcode::SDIV => "SDIV", + revm::bytecode::opcode::MOD => "MOD", + revm::bytecode::opcode::SMOD => "SMOD", + revm::bytecode::opcode::ADDMOD => "ADDMOD", + revm::bytecode::opcode::MULMOD => "MULMOD", + revm::bytecode::opcode::EXP => "EXP", + revm::bytecode::opcode::SIGNEXTEND => "SIGNEXTEND", + // Comparison operations + revm::bytecode::opcode::LT => "LT", + revm::bytecode::opcode::GT => "GT", + revm::bytecode::opcode::SLT => "SLT", + revm::bytecode::opcode::SGT => "SGT", + revm::bytecode::opcode::EQ => "EQ", + revm::bytecode::opcode::ISZERO => "ISZERO", + // Bitwise operations + revm::bytecode::opcode::AND => "AND", + revm::bytecode::opcode::OR => "OR", + revm::bytecode::opcode::XOR => "XOR", + revm::bytecode::opcode::NOT => "NOT", + revm::bytecode::opcode::BYTE => "BYTE", + revm::bytecode::opcode::SHL => "SHL", + revm::bytecode::opcode::SHR => "SHR", + revm::bytecode::opcode::SAR => "SAR", + // Hash operations + revm::bytecode::opcode::KECCAK256 => "KECCAK256", + // Environment information + revm::bytecode::opcode::ADDRESS => "ADDRESS", + revm::bytecode::opcode::BALANCE => "BALANCE", + revm::bytecode::opcode::ORIGIN => "ORIGIN", + revm::bytecode::opcode::CALLER => "CALLER", + revm::bytecode::opcode::CALLVALUE => "CALLVALUE", + revm::bytecode::opcode::CALLDATALOAD => "CALLDATALOAD", + revm::bytecode::opcode::CALLDATASIZE => "CALLDATASIZE", + revm::bytecode::opcode::CALLDATACOPY => "CALLDATACOPY", + revm::bytecode::opcode::CODESIZE => "CODESIZE", + revm::bytecode::opcode::CODECOPY => "CODECOPY", + revm::bytecode::opcode::GASPRICE => "GASPRICE", + revm::bytecode::opcode::EXTCODESIZE => "EXTCODESIZE", + revm::bytecode::opcode::EXTCODECOPY => "EXTCODECOPY", + revm::bytecode::opcode::RETURNDATASIZE => "RETURNDATASIZE", + revm::bytecode::opcode::RETURNDATACOPY => "RETURNDATACOPY", + revm::bytecode::opcode::EXTCODEHASH => "EXTCODEHASH", + // Block information + revm::bytecode::opcode::BLOCKHASH => "BLOCKHASH", + revm::bytecode::opcode::COINBASE => "COINBASE", + revm::bytecode::opcode::TIMESTAMP => "TIMESTAMP", + revm::bytecode::opcode::NUMBER => "NUMBER", + revm::bytecode::opcode::DIFFICULTY => "DIFFICULTY", + revm::bytecode::opcode::GASLIMIT => "GASLIMIT", + revm::bytecode::opcode::CHAINID => "CHAINID", + revm::bytecode::opcode::SELFBALANCE => "SELFBALANCE", + revm::bytecode::opcode::BASEFEE => "BASEFEE", + revm::bytecode::opcode::BLOBHASH => "BLOBHASH", + revm::bytecode::opcode::BLOBBASEFEE => "BLOBBASEFEE", + // Storage and memory operations + revm::bytecode::opcode::POP => "POP", + revm::bytecode::opcode::MLOAD => "MLOAD", + revm::bytecode::opcode::MSTORE => "MSTORE", + revm::bytecode::opcode::MSTORE8 => "MSTORE8", + revm::bytecode::opcode::SLOAD => "SLOAD", + revm::bytecode::opcode::SSTORE => "SSTORE", + revm::bytecode::opcode::JUMP => "JUMP", + revm::bytecode::opcode::JUMPI => "JUMPI", + revm::bytecode::opcode::PC => "PC", + revm::bytecode::opcode::MSIZE => "MSIZE", + revm::bytecode::opcode::GAS => "GAS", + revm::bytecode::opcode::JUMPDEST => "JUMPDEST", + revm::bytecode::opcode::TLOAD => "TLOAD", + revm::bytecode::opcode::TSTORE => "TSTORE", + revm::bytecode::opcode::MCOPY => "MCOPY", + // Push operations + revm::bytecode::opcode::PUSH0 => "PUSH0", + revm::bytecode::opcode::PUSH1 => "PUSH1", revm::bytecode::opcode::PUSH2 => "PUSH2", revm::bytecode::opcode::PUSH3 => "PUSH3", revm::bytecode::opcode::PUSH4 => "PUSH4", + revm::bytecode::opcode::PUSH5 => "PUSH5", revm::bytecode::opcode::PUSH6 => "PUSH6", revm::bytecode::opcode::PUSH7 => "PUSH7", revm::bytecode::opcode::PUSH8 => "PUSH8", + revm::bytecode::opcode::PUSH9 => "PUSH9", revm::bytecode::opcode::PUSH10 => "PUSH10", revm::bytecode::opcode::PUSH11 => "PUSH11", revm::bytecode::opcode::PUSH12 => "PUSH12", + revm::bytecode::opcode::PUSH13 => "PUSH13", revm::bytecode::opcode::PUSH14 => "PUSH14", revm::bytecode::opcode::PUSH15 => "PUSH15", revm::bytecode::opcode::PUSH16 => "PUSH16", + revm::bytecode::opcode::PUSH17 => "PUSH17", revm::bytecode::opcode::PUSH18 => "PUSH18", revm::bytecode::opcode::PUSH19 => "PUSH19", revm::bytecode::opcode::PUSH20 => "PUSH20", + revm::bytecode::opcode::PUSH21 => "PUSH21", revm::bytecode::opcode::PUSH22 => "PUSH22", revm::bytecode::opcode::PUSH23 => "PUSH23", revm::bytecode::opcode::PUSH24 => "PUSH24", + revm::bytecode::opcode::PUSH25 => "PUSH25", revm::bytecode::opcode::PUSH26 => "PUSH26", revm::bytecode::opcode::PUSH27 => "PUSH27", revm::bytecode::opcode::PUSH28 => "PUSH28", + revm::bytecode::opcode::PUSH29 => "PUSH29", revm::bytecode::opcode::PUSH30 => "PUSH30", revm::bytecode::opcode::PUSH31 => "PUSH31", revm::bytecode::opcode::PUSH32 => "PUSH32", + // Dup operations + revm::bytecode::opcode::DUP1 => "DUP1", revm::bytecode::opcode::DUP2 => "DUP2", revm::bytecode::opcode::DUP3 => "DUP3", revm::bytecode::opcode::DUP4 => "DUP4", + revm::bytecode::opcode::DUP5 => "DUP5", revm::bytecode::opcode::DUP6 => "DUP6", revm::bytecode::opcode::DUP7 => "DUP7", revm::bytecode::opcode::DUP8 => "DUP8", + revm::bytecode::opcode::DUP9 => "DUP9", revm::bytecode::opcode::DUP10 => "DUP10", revm::bytecode::opcode::DUP11 => "DUP11", revm::bytecode::opcode::DUP12 => "DUP12", + revm::bytecode::opcode::DUP13 => "DUP13", revm::bytecode::opcode::DUP14 => "DUP14", revm::bytecode::opcode::DUP15 => "DUP15", revm::bytecode::opcode::DUP16 => "DUP16", + // Swap operations + revm::bytecode::opcode::SWAP1 => "SWAP1", revm::bytecode::opcode::SWAP2 => "SWAP2", revm::bytecode::opcode::SWAP3 => "SWAP3", revm::bytecode::opcode::SWAP4 => "SWAP4", + revm::bytecode::opcode::SWAP5 => "SWAP5", revm::bytecode::opcode::SWAP6 => "SWAP6", revm::bytecode::opcode::SWAP7 => "SWAP7", revm::bytecode::opcode::SWAP8 => "SWAP8", + revm::bytecode::opcode::SWAP9 => "SWAP9", revm::bytecode::opcode::SWAP10 => "SWAP10", revm::bytecode::opcode::SWAP11 => "SWAP11", revm::bytecode::opcode::SWAP12 => "SWAP12", + revm::bytecode::opcode::SWAP13 => "SWAP13", revm::bytecode::opcode::SWAP14 => "SWAP14", revm::bytecode::opcode::SWAP15 => "SWAP15", revm::bytecode::opcode::SWAP16 => "SWAP16", + // Log operations + revm::bytecode::opcode::LOG0 => "LOG0", revm::bytecode::opcode::LOG1 => "LOG1", revm::bytecode::opcode::LOG2 => "LOG2", revm::bytecode::opcode::LOG3 => "LOG3", revm::bytecode::opcode::LOG4 => "LOG4", + // System operations + revm::bytecode::opcode::CREATE => "CREATE", + revm::bytecode::opcode::CALL => "CALL", + revm::bytecode::opcode::CALLCODE => "CALLCODE", + revm::bytecode::opcode::RETURN => "RETURN", + revm::bytecode::opcode::DELEGATECALL => "DELEGATECALL", + revm::bytecode::opcode::CREATE2 => "CREATE2", + revm::bytecode::opcode::STATICCALL => "STATICCALL", + revm::bytecode::opcode::REVERT => "REVERT", + revm::bytecode::opcode::INVALID => "INVALID", + revm::bytecode::opcode::SELFDESTRUCT => "SELFDESTRUCT", + _ => "INVALID", + } +} + +/// Get opcode byte from name string +fn get_opcode_byte(name: &str) -> Option { + match name { + // Arithmetic operations + "STOP" => Some(revm::bytecode::opcode::STOP), + "ADD" => Some(revm::bytecode::opcode::ADD), + "MUL" => Some(revm::bytecode::opcode::MUL), + "SUB" => Some(revm::bytecode::opcode::SUB), + "DIV" => Some(revm::bytecode::opcode::DIV), + "SDIV" => Some(revm::bytecode::opcode::SDIV), + "MOD" => Some(revm::bytecode::opcode::MOD), + "SMOD" => Some(revm::bytecode::opcode::SMOD), + "ADDMOD" => Some(revm::bytecode::opcode::ADDMOD), + "MULMOD" => Some(revm::bytecode::opcode::MULMOD), + "EXP" => Some(revm::bytecode::opcode::EXP), + "SIGNEXTEND" => Some(revm::bytecode::opcode::SIGNEXTEND), + // Comparison operations + "LT" => Some(revm::bytecode::opcode::LT), + "GT" => Some(revm::bytecode::opcode::GT), + "SLT" => Some(revm::bytecode::opcode::SLT), + "SGT" => Some(revm::bytecode::opcode::SGT), + "EQ" => Some(revm::bytecode::opcode::EQ), + "ISZERO" => Some(revm::bytecode::opcode::ISZERO), + // Bitwise operations + "AND" => Some(revm::bytecode::opcode::AND), + "OR" => Some(revm::bytecode::opcode::OR), + "XOR" => Some(revm::bytecode::opcode::XOR), + "NOT" => Some(revm::bytecode::opcode::NOT), + "BYTE" => Some(revm::bytecode::opcode::BYTE), + "SHL" => Some(revm::bytecode::opcode::SHL), + "SHR" => Some(revm::bytecode::opcode::SHR), + "SAR" => Some(revm::bytecode::opcode::SAR), + // Hash operations + "KECCAK256" => Some(revm::bytecode::opcode::KECCAK256), + // Environment information + "ADDRESS" => Some(revm::bytecode::opcode::ADDRESS), + "BALANCE" => Some(revm::bytecode::opcode::BALANCE), + "ORIGIN" => Some(revm::bytecode::opcode::ORIGIN), + "CALLER" => Some(revm::bytecode::opcode::CALLER), + "CALLVALUE" => Some(revm::bytecode::opcode::CALLVALUE), + "CALLDATALOAD" => Some(revm::bytecode::opcode::CALLDATALOAD), + "CALLDATASIZE" => Some(revm::bytecode::opcode::CALLDATASIZE), + "CALLDATACOPY" => Some(revm::bytecode::opcode::CALLDATACOPY), + "CODESIZE" => Some(revm::bytecode::opcode::CODESIZE), + "CODECOPY" => Some(revm::bytecode::opcode::CODECOPY), + "GASPRICE" => Some(revm::bytecode::opcode::GASPRICE), + "EXTCODESIZE" => Some(revm::bytecode::opcode::EXTCODESIZE), + "EXTCODECOPY" => Some(revm::bytecode::opcode::EXTCODECOPY), + "RETURNDATASIZE" => Some(revm::bytecode::opcode::RETURNDATASIZE), + "RETURNDATACOPY" => Some(revm::bytecode::opcode::RETURNDATACOPY), + "EXTCODEHASH" => Some(revm::bytecode::opcode::EXTCODEHASH), + // Block information + "BLOCKHASH" => Some(revm::bytecode::opcode::BLOCKHASH), + "COINBASE" => Some(revm::bytecode::opcode::COINBASE), + "TIMESTAMP" => Some(revm::bytecode::opcode::TIMESTAMP), + "NUMBER" => Some(revm::bytecode::opcode::NUMBER), + "DIFFICULTY" => Some(revm::bytecode::opcode::DIFFICULTY), + "GASLIMIT" => Some(revm::bytecode::opcode::GASLIMIT), + "CHAINID" => Some(revm::bytecode::opcode::CHAINID), + "SELFBALANCE" => Some(revm::bytecode::opcode::SELFBALANCE), + "BASEFEE" => Some(revm::bytecode::opcode::BASEFEE), + "BLOBHASH" => Some(revm::bytecode::opcode::BLOBHASH), + "BLOBBASEFEE" => Some(revm::bytecode::opcode::BLOBBASEFEE), + // Storage and memory operations + "POP" => Some(revm::bytecode::opcode::POP), + "MLOAD" => Some(revm::bytecode::opcode::MLOAD), + "MSTORE" => Some(revm::bytecode::opcode::MSTORE), + "MSTORE8" => Some(revm::bytecode::opcode::MSTORE8), + "SLOAD" => Some(revm::bytecode::opcode::SLOAD), + "SSTORE" => Some(revm::bytecode::opcode::SSTORE), + "JUMP" => Some(revm::bytecode::opcode::JUMP), + "JUMPI" => Some(revm::bytecode::opcode::JUMPI), + "PC" => Some(revm::bytecode::opcode::PC), + "MSIZE" => Some(revm::bytecode::opcode::MSIZE), + "GAS" => Some(revm::bytecode::opcode::GAS), + "JUMPDEST" => Some(revm::bytecode::opcode::JUMPDEST), + "TLOAD" => Some(revm::bytecode::opcode::TLOAD), + "TSTORE" => Some(revm::bytecode::opcode::TSTORE), + "MCOPY" => Some(revm::bytecode::opcode::MCOPY), + // Push operations + "PUSH0" => Some(revm::bytecode::opcode::PUSH0), + "PUSH1" => Some(revm::bytecode::opcode::PUSH1), "PUSH2" => Some(revm::bytecode::opcode::PUSH2), "PUSH3" => Some(revm::bytecode::opcode::PUSH3), "PUSH4" => Some(revm::bytecode::opcode::PUSH4), + "PUSH5" => Some(revm::bytecode::opcode::PUSH5), "PUSH6" => Some(revm::bytecode::opcode::PUSH6), "PUSH7" => Some(revm::bytecode::opcode::PUSH7), "PUSH8" => Some(revm::bytecode::opcode::PUSH8), + "PUSH9" => Some(revm::bytecode::opcode::PUSH9), "PUSH10" => Some(revm::bytecode::opcode::PUSH10), "PUSH11" => Some(revm::bytecode::opcode::PUSH11), "PUSH12" => Some(revm::bytecode::opcode::PUSH12), + "PUSH13" => Some(revm::bytecode::opcode::PUSH13), "PUSH14" => Some(revm::bytecode::opcode::PUSH14), "PUSH15" => Some(revm::bytecode::opcode::PUSH15), "PUSH16" => Some(revm::bytecode::opcode::PUSH16), + "PUSH17" => Some(revm::bytecode::opcode::PUSH17), "PUSH18" => Some(revm::bytecode::opcode::PUSH18), "PUSH19" => Some(revm::bytecode::opcode::PUSH19), "PUSH20" => Some(revm::bytecode::opcode::PUSH20), + "PUSH21" => Some(revm::bytecode::opcode::PUSH21), "PUSH22" => Some(revm::bytecode::opcode::PUSH22), "PUSH23" => Some(revm::bytecode::opcode::PUSH23), "PUSH24" => Some(revm::bytecode::opcode::PUSH24), + "PUSH25" => Some(revm::bytecode::opcode::PUSH25), "PUSH26" => Some(revm::bytecode::opcode::PUSH26), "PUSH27" => Some(revm::bytecode::opcode::PUSH27), "PUSH28" => Some(revm::bytecode::opcode::PUSH28), + "PUSH29" => Some(revm::bytecode::opcode::PUSH29), "PUSH30" => Some(revm::bytecode::opcode::PUSH30), "PUSH31" => Some(revm::bytecode::opcode::PUSH31), "PUSH32" => Some(revm::bytecode::opcode::PUSH32), + // Dup operations + "DUP1" => Some(revm::bytecode::opcode::DUP1), "DUP2" => Some(revm::bytecode::opcode::DUP2), "DUP3" => Some(revm::bytecode::opcode::DUP3), "DUP4" => Some(revm::bytecode::opcode::DUP4), + "DUP5" => Some(revm::bytecode::opcode::DUP5), "DUP6" => Some(revm::bytecode::opcode::DUP6), "DUP7" => Some(revm::bytecode::opcode::DUP7), "DUP8" => Some(revm::bytecode::opcode::DUP8), + "DUP9" => Some(revm::bytecode::opcode::DUP9), "DUP10" => Some(revm::bytecode::opcode::DUP10), "DUP11" => Some(revm::bytecode::opcode::DUP11), "DUP12" => Some(revm::bytecode::opcode::DUP12), + "DUP13" => Some(revm::bytecode::opcode::DUP13), "DUP14" => Some(revm::bytecode::opcode::DUP14), "DUP15" => Some(revm::bytecode::opcode::DUP15), "DUP16" => Some(revm::bytecode::opcode::DUP16), + // Swap operations + "SWAP1" => Some(revm::bytecode::opcode::SWAP1), "SWAP2" => Some(revm::bytecode::opcode::SWAP2), "SWAP3" => Some(revm::bytecode::opcode::SWAP3), "SWAP4" => Some(revm::bytecode::opcode::SWAP4), + "SWAP5" => Some(revm::bytecode::opcode::SWAP5), "SWAP6" => Some(revm::bytecode::opcode::SWAP6), "SWAP7" => Some(revm::bytecode::opcode::SWAP7), "SWAP8" => Some(revm::bytecode::opcode::SWAP8), + "SWAP9" => Some(revm::bytecode::opcode::SWAP9), "SWAP10" => Some(revm::bytecode::opcode::SWAP10), "SWAP11" => Some(revm::bytecode::opcode::SWAP11), "SWAP12" => Some(revm::bytecode::opcode::SWAP12), + "SWAP13" => Some(revm::bytecode::opcode::SWAP13), "SWAP14" => Some(revm::bytecode::opcode::SWAP14), "SWAP15" => Some(revm::bytecode::opcode::SWAP15), "SWAP16" => Some(revm::bytecode::opcode::SWAP16), + // Log operations + "LOG0" => Some(revm::bytecode::opcode::LOG0), "LOG1" => Some(revm::bytecode::opcode::LOG1), "LOG2" => Some(revm::bytecode::opcode::LOG2), "LOG3" => Some(revm::bytecode::opcode::LOG3), "LOG4" => Some(revm::bytecode::opcode::LOG4), + // System operations + "CREATE" => Some(revm::bytecode::opcode::CREATE), + "CALL" => Some(revm::bytecode::opcode::CALL), + "CALLCODE" => Some(revm::bytecode::opcode::CALLCODE), + "RETURN" => Some(revm::bytecode::opcode::RETURN), + "DELEGATECALL" => Some(revm::bytecode::opcode::DELEGATECALL), + "CREATE2" => Some(revm::bytecode::opcode::CREATE2), + "STATICCALL" => Some(revm::bytecode::opcode::STATICCALL), + "REVERT" => Some(revm::bytecode::opcode::REVERT), + "INVALID" => Some(revm::bytecode::opcode::INVALID), + "SELFDESTRUCT" => Some(revm::bytecode::opcode::SELFDESTRUCT), + _ => None, + } +} + +/// Serialize opcode as string using REVM opcode names +fn serialize_opcode(opcode: &u8, serializer: S) -> Result +where + S: serde::Serializer, +{ + let name = get_opcode_name(*opcode); + serializer.serialize_str(name) +} + +/// Deserialize opcode from string using reverse lookup table +fn deserialize_opcode<'de, D>(deserializer: D) -> Result +where + D: serde::Deserializer<'de>, +{ + let s = String::deserialize(deserializer)?; + get_opcode_byte(&s) + .ok_or_else(|| serde::de::Error::custom(alloc::format!("Unknown opcode: {}", s))) +} + /// A smart contract execution call trace. #[derive( TypeInfo, Default, Encode, Decode, Serialize, Deserialize, Clone, Debug, Eq, PartialEq, diff --git a/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs b/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs index 95d8a0034cb9c..3ad86e85d69ef 100644 --- a/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs +++ b/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs @@ -18,9 +18,13 @@ use crate::{ evm::{OpcodeStep, OpcodeTrace, OpcodeTracerConfig}, tracing::Tracing, - ExecReturnValue, Weight, DispatchError, + DispatchError, ExecReturnValue, Weight, +}; +use alloc::{ + format, + string::{String, ToString}, + vec::Vec, }; -use alloc::{collections::BTreeMap, format, string::String, string::ToString, vec::Vec}; use sp_core::{H160, U256}; /// A tracer that traces opcode execution step-by-step. @@ -28,22 +32,22 @@ use sp_core::{H160, U256}; pub struct OpcodeTracer { /// The tracer configuration. config: OpcodeTracerConfig, - + /// The collected trace steps. steps: Vec, - + /// Current call depth. depth: u32, - + /// Number of steps captured (for limiting). step_count: u64, - + /// Total gas used by the transaction. total_gas_used: u64, - + /// Whether the transaction failed. failed: bool, - + /// The return value of the transaction. return_value: Vec, } @@ -65,20 +69,10 @@ impl OpcodeTracer { /// Collect the traces and return them. pub fn collect_trace(&mut self) -> OpcodeTrace { let struct_logs = core::mem::take(&mut self.steps); - let return_value = if self.return_value.is_empty() { - "0x".to_string() - } else { - format!("0x{}", alloy_core::hex::encode(&self.return_value)) - }; - - OpcodeTrace { - gas: self.total_gas_used, - failed: self.failed, - return_value, - struct_logs, - } - } + let return_value = crate::evm::Bytes(self.return_value.clone()); + OpcodeTrace { gas: self.total_gas_used, failed: self.failed, return_value, struct_logs } + } /// Record an error in the current step. pub fn record_error(&mut self, error: String) { @@ -91,12 +85,12 @@ impl OpcodeTracer { pub fn record_return_data(&mut self, data: &[u8]) { self.return_value = data.to_vec(); } - + /// Mark the transaction as failed. pub fn mark_failed(&mut self) { self.failed = true; } - + /// Set the total gas used by the transaction. pub fn set_total_gas_used(&mut self, gas_used: u64) { self.total_gas_used = gas_used; @@ -104,19 +98,19 @@ impl OpcodeTracer { } impl Tracing for OpcodeTracer { - fn is_opcode_tracer(&self) -> bool { - true + fn is_opcode_tracer(&self) -> bool { + true } fn record_opcode_step( &mut self, pc: u64, - opcode: &str, + opcode: u8, gas_before: u64, gas_cost: u64, depth: u32, - stack: Option>, - memory: Option>, + stack: Option>, + memory: Option>, ) { // Check step limit if self.config.limit > 0 && self.step_count >= self.config.limit { @@ -124,23 +118,15 @@ impl Tracing for OpcodeTracer { } // Apply configuration settings - let final_stack = if self.config.disable_stack { - None - } else { - stack - }; - - let final_memory = if self.config.enable_memory { - memory - } else { - None - }; + let final_stack = if self.config.disable_stack { None } else { stack }; + + let final_memory = if self.config.enable_memory { memory } else { None }; // TODO: Storage capture would need to be implemented based on the EVM storage access let storage = if !self.config.disable_storage { // For now, return empty storage since we need to track storage changes // This would need to be implemented with actual storage change tracking - Some(BTreeMap::new()) + Some(alloc::collections::BTreeMap::new()) } else { None }; @@ -148,7 +134,7 @@ impl Tracing for OpcodeTracer { // Create the opcode step let step = OpcodeStep { pc, - op: opcode.to_string(), + op: opcode, gas: gas_before, gas_cost, depth, @@ -160,12 +146,6 @@ impl Tracing for OpcodeTracer { self.steps.push(step); self.step_count += 1; - - // Debug output if enabled - if self.config.debug { - println!("OPCODE TRACE: PC={}, OP={}, Gas={}, Cost={}, Depth={}", - pc, opcode, gas_before, gas_cost, depth); - } } fn enter_child_span( @@ -190,13 +170,13 @@ impl Tracing for OpcodeTracer { } else { self.record_return_data(&output.data); } - + // Set total gas used if this is the top-level call (depth 1, will become 0 after decrement) if self.depth == 1 { // Convert Weight to gas units - this is a simplified conversion self.set_total_gas_used(gas_used.ref_time() / 1_000_000); // Rough conversion } - + if self.depth > 0 { self.depth -= 1; } @@ -204,16 +184,15 @@ impl Tracing for OpcodeTracer { fn exit_child_span_with_error(&mut self, error: DispatchError, gas_used: Weight) { self.record_error(format!("{:?}", error)); - + // Mark as failed if this is the top-level call if self.depth == 1 { self.mark_failed(); self.set_total_gas_used(gas_used.ref_time() / 1_000_000); // Rough conversion } - + if self.depth > 0 { self.depth -= 1; } } } - diff --git a/substrate/frame/revive/src/tests/sol.rs b/substrate/frame/revive/src/tests/sol.rs index 7c4b14de6b2b3..08cfdbf9b61dd 100644 --- a/substrate/frame/revive/src/tests/sol.rs +++ b/substrate/frame/revive/src/tests/sol.rs @@ -189,7 +189,6 @@ fn opcode_tracing_works() { disable_stack: false, disable_storage: true, enable_return_data: false, - debug: false, limit: 10, }; let mut opcode_tracer = OpcodeTracer::new(config); @@ -218,7 +217,7 @@ fn opcode_tracing_works() { // Each step should have the required fields for step in &trace.struct_logs { - assert!(!step.op.is_empty()); + // Opcode should be valid (any u8 is valid) // Stack should be present (not disabled) assert!(step.stack.is_some()); // Memory should not be present (disabled) @@ -249,7 +248,6 @@ fn opcode_tracing_with_memory_works() { disable_stack: true, disable_storage: true, enable_return_data: true, - debug: false, limit: 5, }; let mut opcode_tracer = OpcodeTracer::new(config); @@ -275,7 +273,7 @@ fn opcode_tracing_with_memory_works() { // Each step should have the required fields based on config for step in &trace.struct_logs { - assert!(!step.op.is_empty()); + // Opcode should be valid (any u8 is valid) // Stack should not be present (disabled) assert!(step.stack.is_none()); // Memory should be present (enabled) @@ -303,7 +301,6 @@ fn opcode_tracing_comprehensive_works() { disable_stack: false, disable_storage: true, enable_return_data: true, - debug: false, limit: 5 // Limit to first 5 steps for predictable testing }; @@ -324,11 +321,11 @@ fn opcode_tracing_comprehensive_works() { let expected_trace = OpcodeTrace { gas: actual_trace.gas, // Use actual gas since it varies failed: false, - return_value: "0x0000000000000000000000000000000000000000000000000000000000000002".to_string(), // fib(3) = 2 + return_value: crate::evm::Bytes(U256::from(2).to_be_bytes_vec()), // fib(3) = 2 struct_logs: vec![ OpcodeStep { pc: 0, - op: format!("{:02x}", PUSH1), + op: PUSH1, gas: 0, gas_cost: 0, depth: 0, @@ -339,24 +336,24 @@ fn opcode_tracing_comprehensive_works() { }, OpcodeStep { pc: 2, - op: format!("{:02x}", PUSH1), + op: PUSH1, gas: 0, gas_cost: 0, depth: 0, - stack: Some(vec!["0x0000000000000000000000000000000000000000000000000000000000000001".to_string()]), + stack: Some(vec![crate::evm::Bytes(U256::from(1).to_be_bytes_vec())]), memory: None, storage: None, error: None, }, OpcodeStep { pc: 4, - op: format!("{:02x}", MSTORE), + op: MSTORE, gas: 0, gas_cost: 0, depth: 0, stack: Some(vec![ - "0x0000000000000000000000000000000000000000000000000000000000000002".to_string(), - "0x0000000000000000000000000000000000000000000000000000000000000001".to_string() + crate::evm::Bytes(U256::from(2).to_be_bytes_vec()), + crate::evm::Bytes(U256::from(1).to_be_bytes_vec()) ]), memory: None, storage: None, @@ -364,7 +361,7 @@ fn opcode_tracing_comprehensive_works() { }, OpcodeStep { pc: 5, - op: format!("{:02x}", CALLVALUE), + op: CALLVALUE, gas: 0, gas_cost: 0, depth: 0, @@ -375,11 +372,11 @@ fn opcode_tracing_comprehensive_works() { }, OpcodeStep { pc: 6, - op: format!("{:02x}", DUP1), + op: DUP1, gas: 0, gas_cost: 0, depth: 0, - stack: Some(vec!["0x0000000000000000000000000000000000000000000000000000000000000001".to_string()]), + stack: Some(vec![crate::evm::Bytes(U256::from(1).to_be_bytes_vec())]), memory: None, storage: None, error: None, @@ -391,3 +388,37 @@ fn opcode_tracing_comprehensive_works() { assert_eq!(actual_trace, expected_trace); }); } + +#[test] +fn revm_opcode_serialization_works() { + use crate::evm::OpcodeStep; + use revm::bytecode::opcode::*; + + // Test that our opcode serialization uses REVM opcode names + let step = OpcodeStep { + pc: 0, + op: PUSH1, + gas: 0, + gas_cost: 0, + depth: 0, + stack: Some(vec![]), + memory: None, + storage: None, + error: None, + }; + + // Serialize to JSON + let json = serde_json::to_string(&step).unwrap(); + + // Should contain "PUSH1" string from REVM + assert!(json.contains("\"op\":\"PUSH1\"")); + + // Test a few more opcodes + let step_add = OpcodeStep { op: ADD, ..step.clone() }; + let json_add = serde_json::to_string(&step_add).unwrap(); + assert!(json_add.contains("\"op\":\"ADD\"")); + + let step_call = OpcodeStep { op: CALL, ..step.clone() }; + let json_call = serde_json::to_string(&step_call).unwrap(); + assert!(json_call.contains("\"op\":\"CALL\"")); +} diff --git a/substrate/frame/revive/src/tracing.rs b/substrate/frame/revive/src/tracing.rs index ef19b46fe5e7e..ea8d753ee3a6a 100644 --- a/substrate/frame/revive/src/tracing.rs +++ b/substrate/frame/revive/src/tracing.rs @@ -49,12 +49,12 @@ pub trait Tracing { fn record_opcode_step( &mut self, _pc: u64, - _opcode: &str, + _opcode: u8, _gas_before: u64, _gas_cost: u64, _depth: u32, - _stack: Option>, - _memory: Option>, + _stack: Option>, + _memory: Option>, ) {} /// Register an address that should be traced. diff --git a/substrate/frame/revive/src/vm/evm.rs b/substrate/frame/revive/src/vm/evm.rs index 2342b475c6daa..3685c110f0ba8 100644 --- a/substrate/frame/revive/src/vm/evm.rs +++ b/substrate/frame/revive/src/vm/evm.rs @@ -220,12 +220,15 @@ where // Create a simplified stack representation showing the stack has items // Unfortunately, we can't directly read stack values without modifying the stack // So we'll show placeholder values indicating stack depth - let mut stack_strings = Vec::new(); + let mut stack_bytes = Vec::new(); for i in 0..core::cmp::min(stack_len, 16) { // Limit to 16 items for performance - stack_strings.push(format!("0x{:064x}", (stack_len - i) as u64)); + let value = (stack_len - i) as u64; + let mut bytes = [0u8; 32]; + bytes[24..32].copy_from_slice(&value.to_be_bytes()); + stack_bytes.push(crate::evm::Bytes(bytes.to_vec())); } - Some(stack_strings) + Some(stack_bytes) }; // Capture memory data - we know opcode tracing is enabled @@ -236,7 +239,7 @@ where if memory_size == 0 { Some(Vec::new()) } else { - let mut memory_strings = Vec::new(); + let mut memory_bytes = Vec::new(); // Read memory in 32-byte chunks, limiting to reasonable size let chunks_to_read = core::cmp::min(memory_size / 32 + 1, 16); // Limit to 16 chunks @@ -248,13 +251,16 @@ where // Use the slice method available from the MemoryTr trait let slice = interpreter.memory.slice(offset..end); - // Convert to hex string, padding to 64 characters (32 bytes) - let hex_chunk = slice.iter().map(|b| format!("{:02x}", b)).collect::(); - memory_strings.push(format!("0x{:0<64}", hex_chunk)); + // Convert to bytes, padding to 32 bytes + let mut chunk_bytes = vec![0u8; 32]; + for (i, &byte) in slice.iter().enumerate().take(32) { + chunk_bytes[i] = byte; + } + memory_bytes.push(crate::evm::Bytes(chunk_bytes)); } } - Some(memory_strings) + Some(memory_bytes) } }; @@ -269,7 +275,7 @@ where tracing::if_tracing(|tracer| { tracer.record_opcode_step( pc as u64, - &format!("{:02X}", opcode.get()), + opcode.get(), gas_before, gas_cost, 0, // TODO: track actual call depth from the call stack From 5a67cee410047b127e148316134d5f43a8d7f0fd Mon Sep 17 00:00:00 2001 From: pgherveou Date: Fri, 12 Sep 2025 03:27:08 +0000 Subject: [PATCH 07/76] wip --- .../revive/src/evm/tracing/opcode_tracing.rs | 8 +++++++ substrate/frame/revive/src/tracing.rs | 6 ++++++ substrate/frame/revive/src/vm/evm.rs | 21 ++++++++++++++----- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs b/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs index 3ad86e85d69ef..33457c9224df6 100644 --- a/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs +++ b/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs @@ -102,6 +102,14 @@ impl Tracing for OpcodeTracer { true } + fn is_stack_capture_enabled(&self) -> bool { + !self.config.disable_stack + } + + fn is_memory_capture_enabled(&self) -> bool { + self.config.enable_memory + } + fn record_opcode_step( &mut self, pc: u64, diff --git a/substrate/frame/revive/src/tracing.rs b/substrate/frame/revive/src/tracing.rs index ea8d753ee3a6a..b10a1d85e06b7 100644 --- a/substrate/frame/revive/src/tracing.rs +++ b/substrate/frame/revive/src/tracing.rs @@ -45,6 +45,12 @@ pub trait Tracing { /// Check if this tracer requires opcode-level tracing. fn is_opcode_tracer(&self) -> bool { false } + /// Check if stack capture is enabled for opcode tracing. + fn is_stack_capture_enabled(&self) -> bool { false } + + /// Check if memory capture is enabled for opcode tracing. + fn is_memory_capture_enabled(&self) -> bool { false } + /// Record an opcode step for opcode tracers. fn record_opcode_step( &mut self, diff --git a/substrate/frame/revive/src/vm/evm.rs b/substrate/frame/revive/src/vm/evm.rs index 3685c110f0ba8..4cc6525653df7 100644 --- a/substrate/frame/revive/src/vm/evm.rs +++ b/substrate/frame/revive/src/vm/evm.rs @@ -162,7 +162,12 @@ fn run<'a, E: Ext>( .unwrap_or(false); let action = if is_opcode_tracing { - run_with_opcode_tracing(interpreter, table, host) + // Get tracer configuration flags + let (is_stack_enabled, is_memory_enabled) = tracing::if_tracing(|tracer| { + (tracer.is_stack_capture_enabled(), tracer.is_memory_capture_enabled()) + }).unwrap_or((false, false)); + + run_with_opcode_tracing(interpreter, table, host, is_stack_enabled, is_memory_enabled) } else { interpreter.run_plain(table, host) }; @@ -192,6 +197,8 @@ fn run_with_opcode_tracing<'a, E: Ext>( interpreter: &mut Interpreter>, table: &revm::interpreter::InstructionTable, DummyHost>, host: &mut DummyHost, + is_stack_enabled: bool, + is_memory_enabled: bool, ) -> InterpreterAction where EVMInterpreter<'a, E>: InterpreterTypes, @@ -212,8 +219,8 @@ where // Record gas before execution let gas_before = interpreter.gas.remaining(); - // Capture stack data - we know opcode tracing is enabled - let stack_data = { + // Capture stack data only if enabled + let stack_data = if is_stack_enabled { // Get stack length - this is available through the trait let stack_len = interpreter.stack.len(); @@ -229,10 +236,12 @@ where } Some(stack_bytes) + } else { + None }; - // Capture memory data - we know opcode tracing is enabled - let memory_data = { + // Capture memory data only if enabled + let memory_data = if is_memory_enabled { // Get memory size - this is available through the trait let memory_size = interpreter.memory.size(); @@ -262,6 +271,8 @@ where Some(memory_bytes) } + } else { + None }; // Execute the instruction step From 52ad1e36653eb66cc7bdc26cd39c98c88fbfd9c2 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Fri, 12 Sep 2025 11:35:24 +0800 Subject: [PATCH 08/76] fix --- .../revive/src/evm/api/debug_rpc_types.rs | 362 +++++++++++------- substrate/frame/revive/src/lib.rs | 4 +- substrate/frame/revive/src/tests/sol.rs | 40 +- substrate/frame/revive/src/tracing.rs | 15 +- substrate/frame/revive/src/vm/evm.rs | 19 +- 5 files changed, 277 insertions(+), 163 deletions(-) diff --git a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs index 5c33d1dbc849d..2eb5a1bbc2354 100644 --- a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs +++ b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs @@ -366,118 +366,170 @@ pub struct OpcodeStep { /// Get opcode name from byte value using REVM opcode names fn get_opcode_name(opcode: u8) -> &'static str { - match opcode { - // Arithmetic operations - revm::bytecode::opcode::STOP => "STOP", - revm::bytecode::opcode::ADD => "ADD", - revm::bytecode::opcode::MUL => "MUL", - revm::bytecode::opcode::SUB => "SUB", - revm::bytecode::opcode::DIV => "DIV", - revm::bytecode::opcode::SDIV => "SDIV", - revm::bytecode::opcode::MOD => "MOD", - revm::bytecode::opcode::SMOD => "SMOD", - revm::bytecode::opcode::ADDMOD => "ADDMOD", - revm::bytecode::opcode::MULMOD => "MULMOD", - revm::bytecode::opcode::EXP => "EXP", - revm::bytecode::opcode::SIGNEXTEND => "SIGNEXTEND", - // Comparison operations - revm::bytecode::opcode::LT => "LT", - revm::bytecode::opcode::GT => "GT", - revm::bytecode::opcode::SLT => "SLT", - revm::bytecode::opcode::SGT => "SGT", - revm::bytecode::opcode::EQ => "EQ", - revm::bytecode::opcode::ISZERO => "ISZERO", - // Bitwise operations - revm::bytecode::opcode::AND => "AND", - revm::bytecode::opcode::OR => "OR", - revm::bytecode::opcode::XOR => "XOR", - revm::bytecode::opcode::NOT => "NOT", - revm::bytecode::opcode::BYTE => "BYTE", - revm::bytecode::opcode::SHL => "SHL", - revm::bytecode::opcode::SHR => "SHR", - revm::bytecode::opcode::SAR => "SAR", - // Hash operations - revm::bytecode::opcode::KECCAK256 => "KECCAK256", - // Environment information - revm::bytecode::opcode::ADDRESS => "ADDRESS", - revm::bytecode::opcode::BALANCE => "BALANCE", - revm::bytecode::opcode::ORIGIN => "ORIGIN", - revm::bytecode::opcode::CALLER => "CALLER", - revm::bytecode::opcode::CALLVALUE => "CALLVALUE", - revm::bytecode::opcode::CALLDATALOAD => "CALLDATALOAD", - revm::bytecode::opcode::CALLDATASIZE => "CALLDATASIZE", - revm::bytecode::opcode::CALLDATACOPY => "CALLDATACOPY", - revm::bytecode::opcode::CODESIZE => "CODESIZE", - revm::bytecode::opcode::CODECOPY => "CODECOPY", - revm::bytecode::opcode::GASPRICE => "GASPRICE", - revm::bytecode::opcode::EXTCODESIZE => "EXTCODESIZE", - revm::bytecode::opcode::EXTCODECOPY => "EXTCODECOPY", - revm::bytecode::opcode::RETURNDATASIZE => "RETURNDATASIZE", - revm::bytecode::opcode::RETURNDATACOPY => "RETURNDATACOPY", - revm::bytecode::opcode::EXTCODEHASH => "EXTCODEHASH", - // Block information - revm::bytecode::opcode::BLOCKHASH => "BLOCKHASH", - revm::bytecode::opcode::COINBASE => "COINBASE", - revm::bytecode::opcode::TIMESTAMP => "TIMESTAMP", - revm::bytecode::opcode::NUMBER => "NUMBER", - revm::bytecode::opcode::DIFFICULTY => "DIFFICULTY", - revm::bytecode::opcode::GASLIMIT => "GASLIMIT", - revm::bytecode::opcode::CHAINID => "CHAINID", - revm::bytecode::opcode::SELFBALANCE => "SELFBALANCE", - revm::bytecode::opcode::BASEFEE => "BASEFEE", - revm::bytecode::opcode::BLOBHASH => "BLOBHASH", - revm::bytecode::opcode::BLOBBASEFEE => "BLOBBASEFEE", - // Storage and memory operations - revm::bytecode::opcode::POP => "POP", - revm::bytecode::opcode::MLOAD => "MLOAD", - revm::bytecode::opcode::MSTORE => "MSTORE", - revm::bytecode::opcode::MSTORE8 => "MSTORE8", - revm::bytecode::opcode::SLOAD => "SLOAD", - revm::bytecode::opcode::SSTORE => "SSTORE", - revm::bytecode::opcode::JUMP => "JUMP", - revm::bytecode::opcode::JUMPI => "JUMPI", - revm::bytecode::opcode::PC => "PC", - revm::bytecode::opcode::MSIZE => "MSIZE", - revm::bytecode::opcode::GAS => "GAS", - revm::bytecode::opcode::JUMPDEST => "JUMPDEST", - revm::bytecode::opcode::TLOAD => "TLOAD", - revm::bytecode::opcode::TSTORE => "TSTORE", - revm::bytecode::opcode::MCOPY => "MCOPY", - // Push operations - revm::bytecode::opcode::PUSH0 => "PUSH0", - revm::bytecode::opcode::PUSH1 => "PUSH1", revm::bytecode::opcode::PUSH2 => "PUSH2", revm::bytecode::opcode::PUSH3 => "PUSH3", revm::bytecode::opcode::PUSH4 => "PUSH4", - revm::bytecode::opcode::PUSH5 => "PUSH5", revm::bytecode::opcode::PUSH6 => "PUSH6", revm::bytecode::opcode::PUSH7 => "PUSH7", revm::bytecode::opcode::PUSH8 => "PUSH8", - revm::bytecode::opcode::PUSH9 => "PUSH9", revm::bytecode::opcode::PUSH10 => "PUSH10", revm::bytecode::opcode::PUSH11 => "PUSH11", revm::bytecode::opcode::PUSH12 => "PUSH12", - revm::bytecode::opcode::PUSH13 => "PUSH13", revm::bytecode::opcode::PUSH14 => "PUSH14", revm::bytecode::opcode::PUSH15 => "PUSH15", revm::bytecode::opcode::PUSH16 => "PUSH16", - revm::bytecode::opcode::PUSH17 => "PUSH17", revm::bytecode::opcode::PUSH18 => "PUSH18", revm::bytecode::opcode::PUSH19 => "PUSH19", revm::bytecode::opcode::PUSH20 => "PUSH20", - revm::bytecode::opcode::PUSH21 => "PUSH21", revm::bytecode::opcode::PUSH22 => "PUSH22", revm::bytecode::opcode::PUSH23 => "PUSH23", revm::bytecode::opcode::PUSH24 => "PUSH24", - revm::bytecode::opcode::PUSH25 => "PUSH25", revm::bytecode::opcode::PUSH26 => "PUSH26", revm::bytecode::opcode::PUSH27 => "PUSH27", revm::bytecode::opcode::PUSH28 => "PUSH28", - revm::bytecode::opcode::PUSH29 => "PUSH29", revm::bytecode::opcode::PUSH30 => "PUSH30", revm::bytecode::opcode::PUSH31 => "PUSH31", revm::bytecode::opcode::PUSH32 => "PUSH32", - // Dup operations - revm::bytecode::opcode::DUP1 => "DUP1", revm::bytecode::opcode::DUP2 => "DUP2", revm::bytecode::opcode::DUP3 => "DUP3", revm::bytecode::opcode::DUP4 => "DUP4", - revm::bytecode::opcode::DUP5 => "DUP5", revm::bytecode::opcode::DUP6 => "DUP6", revm::bytecode::opcode::DUP7 => "DUP7", revm::bytecode::opcode::DUP8 => "DUP8", - revm::bytecode::opcode::DUP9 => "DUP9", revm::bytecode::opcode::DUP10 => "DUP10", revm::bytecode::opcode::DUP11 => "DUP11", revm::bytecode::opcode::DUP12 => "DUP12", - revm::bytecode::opcode::DUP13 => "DUP13", revm::bytecode::opcode::DUP14 => "DUP14", revm::bytecode::opcode::DUP15 => "DUP15", revm::bytecode::opcode::DUP16 => "DUP16", - // Swap operations - revm::bytecode::opcode::SWAP1 => "SWAP1", revm::bytecode::opcode::SWAP2 => "SWAP2", revm::bytecode::opcode::SWAP3 => "SWAP3", revm::bytecode::opcode::SWAP4 => "SWAP4", - revm::bytecode::opcode::SWAP5 => "SWAP5", revm::bytecode::opcode::SWAP6 => "SWAP6", revm::bytecode::opcode::SWAP7 => "SWAP7", revm::bytecode::opcode::SWAP8 => "SWAP8", - revm::bytecode::opcode::SWAP9 => "SWAP9", revm::bytecode::opcode::SWAP10 => "SWAP10", revm::bytecode::opcode::SWAP11 => "SWAP11", revm::bytecode::opcode::SWAP12 => "SWAP12", - revm::bytecode::opcode::SWAP13 => "SWAP13", revm::bytecode::opcode::SWAP14 => "SWAP14", revm::bytecode::opcode::SWAP15 => "SWAP15", revm::bytecode::opcode::SWAP16 => "SWAP16", - // Log operations - revm::bytecode::opcode::LOG0 => "LOG0", revm::bytecode::opcode::LOG1 => "LOG1", revm::bytecode::opcode::LOG2 => "LOG2", revm::bytecode::opcode::LOG3 => "LOG3", revm::bytecode::opcode::LOG4 => "LOG4", - // System operations - revm::bytecode::opcode::CREATE => "CREATE", - revm::bytecode::opcode::CALL => "CALL", - revm::bytecode::opcode::CALLCODE => "CALLCODE", - revm::bytecode::opcode::RETURN => "RETURN", - revm::bytecode::opcode::DELEGATECALL => "DELEGATECALL", - revm::bytecode::opcode::CREATE2 => "CREATE2", - revm::bytecode::opcode::STATICCALL => "STATICCALL", - revm::bytecode::opcode::REVERT => "REVERT", - revm::bytecode::opcode::INVALID => "INVALID", - revm::bytecode::opcode::SELFDESTRUCT => "SELFDESTRUCT", - _ => "INVALID", - } + use revm::bytecode::opcode::*; + + macro_rules! opcode_match { + ($($op:ident),*) => { + match opcode { + $( + $op => stringify!($op), + )* + _ => "INVALID", + } + }; + } + + opcode_match!( + STOP, + ADD, + MUL, + SUB, + DIV, + SDIV, + MOD, + SMOD, + ADDMOD, + MULMOD, + EXP, + SIGNEXTEND, + LT, + GT, + SLT, + SGT, + EQ, + ISZERO, + AND, + OR, + XOR, + NOT, + BYTE, + SHL, + SHR, + SAR, + KECCAK256, + ADDRESS, + BALANCE, + ORIGIN, + CALLER, + CALLVALUE, + CALLDATALOAD, + CALLDATASIZE, + CALLDATACOPY, + CODESIZE, + CODECOPY, + GASPRICE, + EXTCODESIZE, + EXTCODECOPY, + RETURNDATASIZE, + RETURNDATACOPY, + EXTCODEHASH, + BLOCKHASH, + COINBASE, + TIMESTAMP, + NUMBER, + DIFFICULTY, + GASLIMIT, + CHAINID, + SELFBALANCE, + BASEFEE, + BLOBHASH, + BLOBBASEFEE, + POP, + MLOAD, + MSTORE, + MSTORE8, + SLOAD, + SSTORE, + JUMP, + JUMPI, + PC, + MSIZE, + GAS, + JUMPDEST, + TLOAD, + TSTORE, + MCOPY, + PUSH0, + PUSH1, + PUSH2, + PUSH3, + PUSH4, + PUSH5, + PUSH6, + PUSH7, + PUSH8, + PUSH9, + PUSH10, + PUSH11, + PUSH12, + PUSH13, + PUSH14, + PUSH15, + PUSH16, + PUSH17, + PUSH18, + PUSH19, + PUSH20, + PUSH21, + PUSH22, + PUSH23, + PUSH24, + PUSH25, + PUSH26, + PUSH27, + PUSH28, + PUSH29, + PUSH30, + PUSH31, + PUSH32, + DUP1, + DUP2, + DUP3, + DUP4, + DUP5, + DUP6, + DUP7, + DUP8, + DUP9, + DUP10, + DUP11, + DUP12, + DUP13, + DUP14, + DUP15, + DUP16, + SWAP1, + SWAP2, + SWAP3, + SWAP4, + SWAP5, + SWAP6, + SWAP7, + SWAP8, + SWAP9, + SWAP10, + SWAP11, + SWAP12, + SWAP13, + SWAP14, + SWAP15, + SWAP16, + LOG0, + LOG1, + LOG2, + LOG3, + LOG4, + CREATE, + CALL, + CALLCODE, + RETURN, + DELEGATECALL, + CREATE2, + STATICCALL, + REVERT, + INVALID, + SELFDESTRUCT + ) } /// Get opcode byte from name string @@ -561,26 +613,78 @@ fn get_opcode_byte(name: &str) -> Option { "MCOPY" => Some(revm::bytecode::opcode::MCOPY), // Push operations "PUSH0" => Some(revm::bytecode::opcode::PUSH0), - "PUSH1" => Some(revm::bytecode::opcode::PUSH1), "PUSH2" => Some(revm::bytecode::opcode::PUSH2), "PUSH3" => Some(revm::bytecode::opcode::PUSH3), "PUSH4" => Some(revm::bytecode::opcode::PUSH4), - "PUSH5" => Some(revm::bytecode::opcode::PUSH5), "PUSH6" => Some(revm::bytecode::opcode::PUSH6), "PUSH7" => Some(revm::bytecode::opcode::PUSH7), "PUSH8" => Some(revm::bytecode::opcode::PUSH8), - "PUSH9" => Some(revm::bytecode::opcode::PUSH9), "PUSH10" => Some(revm::bytecode::opcode::PUSH10), "PUSH11" => Some(revm::bytecode::opcode::PUSH11), "PUSH12" => Some(revm::bytecode::opcode::PUSH12), - "PUSH13" => Some(revm::bytecode::opcode::PUSH13), "PUSH14" => Some(revm::bytecode::opcode::PUSH14), "PUSH15" => Some(revm::bytecode::opcode::PUSH15), "PUSH16" => Some(revm::bytecode::opcode::PUSH16), - "PUSH17" => Some(revm::bytecode::opcode::PUSH17), "PUSH18" => Some(revm::bytecode::opcode::PUSH18), "PUSH19" => Some(revm::bytecode::opcode::PUSH19), "PUSH20" => Some(revm::bytecode::opcode::PUSH20), - "PUSH21" => Some(revm::bytecode::opcode::PUSH21), "PUSH22" => Some(revm::bytecode::opcode::PUSH22), "PUSH23" => Some(revm::bytecode::opcode::PUSH23), "PUSH24" => Some(revm::bytecode::opcode::PUSH24), - "PUSH25" => Some(revm::bytecode::opcode::PUSH25), "PUSH26" => Some(revm::bytecode::opcode::PUSH26), "PUSH27" => Some(revm::bytecode::opcode::PUSH27), "PUSH28" => Some(revm::bytecode::opcode::PUSH28), - "PUSH29" => Some(revm::bytecode::opcode::PUSH29), "PUSH30" => Some(revm::bytecode::opcode::PUSH30), "PUSH31" => Some(revm::bytecode::opcode::PUSH31), "PUSH32" => Some(revm::bytecode::opcode::PUSH32), + "PUSH1" => Some(revm::bytecode::opcode::PUSH1), + "PUSH2" => Some(revm::bytecode::opcode::PUSH2), + "PUSH3" => Some(revm::bytecode::opcode::PUSH3), + "PUSH4" => Some(revm::bytecode::opcode::PUSH4), + "PUSH5" => Some(revm::bytecode::opcode::PUSH5), + "PUSH6" => Some(revm::bytecode::opcode::PUSH6), + "PUSH7" => Some(revm::bytecode::opcode::PUSH7), + "PUSH8" => Some(revm::bytecode::opcode::PUSH8), + "PUSH9" => Some(revm::bytecode::opcode::PUSH9), + "PUSH10" => Some(revm::bytecode::opcode::PUSH10), + "PUSH11" => Some(revm::bytecode::opcode::PUSH11), + "PUSH12" => Some(revm::bytecode::opcode::PUSH12), + "PUSH13" => Some(revm::bytecode::opcode::PUSH13), + "PUSH14" => Some(revm::bytecode::opcode::PUSH14), + "PUSH15" => Some(revm::bytecode::opcode::PUSH15), + "PUSH16" => Some(revm::bytecode::opcode::PUSH16), + "PUSH17" => Some(revm::bytecode::opcode::PUSH17), + "PUSH18" => Some(revm::bytecode::opcode::PUSH18), + "PUSH19" => Some(revm::bytecode::opcode::PUSH19), + "PUSH20" => Some(revm::bytecode::opcode::PUSH20), + "PUSH21" => Some(revm::bytecode::opcode::PUSH21), + "PUSH22" => Some(revm::bytecode::opcode::PUSH22), + "PUSH23" => Some(revm::bytecode::opcode::PUSH23), + "PUSH24" => Some(revm::bytecode::opcode::PUSH24), + "PUSH25" => Some(revm::bytecode::opcode::PUSH25), + "PUSH26" => Some(revm::bytecode::opcode::PUSH26), + "PUSH27" => Some(revm::bytecode::opcode::PUSH27), + "PUSH28" => Some(revm::bytecode::opcode::PUSH28), + "PUSH29" => Some(revm::bytecode::opcode::PUSH29), + "PUSH30" => Some(revm::bytecode::opcode::PUSH30), + "PUSH31" => Some(revm::bytecode::opcode::PUSH31), + "PUSH32" => Some(revm::bytecode::opcode::PUSH32), // Dup operations - "DUP1" => Some(revm::bytecode::opcode::DUP1), "DUP2" => Some(revm::bytecode::opcode::DUP2), "DUP3" => Some(revm::bytecode::opcode::DUP3), "DUP4" => Some(revm::bytecode::opcode::DUP4), - "DUP5" => Some(revm::bytecode::opcode::DUP5), "DUP6" => Some(revm::bytecode::opcode::DUP6), "DUP7" => Some(revm::bytecode::opcode::DUP7), "DUP8" => Some(revm::bytecode::opcode::DUP8), - "DUP9" => Some(revm::bytecode::opcode::DUP9), "DUP10" => Some(revm::bytecode::opcode::DUP10), "DUP11" => Some(revm::bytecode::opcode::DUP11), "DUP12" => Some(revm::bytecode::opcode::DUP12), - "DUP13" => Some(revm::bytecode::opcode::DUP13), "DUP14" => Some(revm::bytecode::opcode::DUP14), "DUP15" => Some(revm::bytecode::opcode::DUP15), "DUP16" => Some(revm::bytecode::opcode::DUP16), + "DUP1" => Some(revm::bytecode::opcode::DUP1), + "DUP2" => Some(revm::bytecode::opcode::DUP2), + "DUP3" => Some(revm::bytecode::opcode::DUP3), + "DUP4" => Some(revm::bytecode::opcode::DUP4), + "DUP5" => Some(revm::bytecode::opcode::DUP5), + "DUP6" => Some(revm::bytecode::opcode::DUP6), + "DUP7" => Some(revm::bytecode::opcode::DUP7), + "DUP8" => Some(revm::bytecode::opcode::DUP8), + "DUP9" => Some(revm::bytecode::opcode::DUP9), + "DUP10" => Some(revm::bytecode::opcode::DUP10), + "DUP11" => Some(revm::bytecode::opcode::DUP11), + "DUP12" => Some(revm::bytecode::opcode::DUP12), + "DUP13" => Some(revm::bytecode::opcode::DUP13), + "DUP14" => Some(revm::bytecode::opcode::DUP14), + "DUP15" => Some(revm::bytecode::opcode::DUP15), + "DUP16" => Some(revm::bytecode::opcode::DUP16), // Swap operations - "SWAP1" => Some(revm::bytecode::opcode::SWAP1), "SWAP2" => Some(revm::bytecode::opcode::SWAP2), "SWAP3" => Some(revm::bytecode::opcode::SWAP3), "SWAP4" => Some(revm::bytecode::opcode::SWAP4), - "SWAP5" => Some(revm::bytecode::opcode::SWAP5), "SWAP6" => Some(revm::bytecode::opcode::SWAP6), "SWAP7" => Some(revm::bytecode::opcode::SWAP7), "SWAP8" => Some(revm::bytecode::opcode::SWAP8), - "SWAP9" => Some(revm::bytecode::opcode::SWAP9), "SWAP10" => Some(revm::bytecode::opcode::SWAP10), "SWAP11" => Some(revm::bytecode::opcode::SWAP11), "SWAP12" => Some(revm::bytecode::opcode::SWAP12), - "SWAP13" => Some(revm::bytecode::opcode::SWAP13), "SWAP14" => Some(revm::bytecode::opcode::SWAP14), "SWAP15" => Some(revm::bytecode::opcode::SWAP15), "SWAP16" => Some(revm::bytecode::opcode::SWAP16), + "SWAP1" => Some(revm::bytecode::opcode::SWAP1), + "SWAP2" => Some(revm::bytecode::opcode::SWAP2), + "SWAP3" => Some(revm::bytecode::opcode::SWAP3), + "SWAP4" => Some(revm::bytecode::opcode::SWAP4), + "SWAP5" => Some(revm::bytecode::opcode::SWAP5), + "SWAP6" => Some(revm::bytecode::opcode::SWAP6), + "SWAP7" => Some(revm::bytecode::opcode::SWAP7), + "SWAP8" => Some(revm::bytecode::opcode::SWAP8), + "SWAP9" => Some(revm::bytecode::opcode::SWAP9), + "SWAP10" => Some(revm::bytecode::opcode::SWAP10), + "SWAP11" => Some(revm::bytecode::opcode::SWAP11), + "SWAP12" => Some(revm::bytecode::opcode::SWAP12), + "SWAP13" => Some(revm::bytecode::opcode::SWAP13), + "SWAP14" => Some(revm::bytecode::opcode::SWAP14), + "SWAP15" => Some(revm::bytecode::opcode::SWAP15), + "SWAP16" => Some(revm::bytecode::opcode::SWAP16), // Log operations - "LOG0" => Some(revm::bytecode::opcode::LOG0), "LOG1" => Some(revm::bytecode::opcode::LOG1), "LOG2" => Some(revm::bytecode::opcode::LOG2), "LOG3" => Some(revm::bytecode::opcode::LOG3), "LOG4" => Some(revm::bytecode::opcode::LOG4), + "LOG0" => Some(revm::bytecode::opcode::LOG0), + "LOG1" => Some(revm::bytecode::opcode::LOG1), + "LOG2" => Some(revm::bytecode::opcode::LOG2), + "LOG3" => Some(revm::bytecode::opcode::LOG3), + "LOG4" => Some(revm::bytecode::opcode::LOG4), // System operations "CREATE" => Some(revm::bytecode::opcode::CREATE), "CALL" => Some(revm::bytecode::opcode::CALL), diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index bc0db22a49228..848eda32b9374 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -45,8 +45,8 @@ pub mod weights; use crate::{ evm::{ - runtime::GAS_PRICE, CallTracer, GasEncoder, GenericTransaction, OpcodeTracer, PrestateTracer, Trace, - Tracer, TracerType, TYPE_EIP1559, + runtime::GAS_PRICE, CallTracer, GasEncoder, GenericTransaction, OpcodeTracer, + PrestateTracer, Trace, Tracer, TracerType, TYPE_EIP1559, }, exec::{AccountIdOf, ExecError, Executable, Stack as ExecStack}, gas::GasMeter, diff --git a/substrate/frame/revive/src/tests/sol.rs b/substrate/frame/revive/src/tests/sol.rs index 08cfdbf9b61dd..606fe8510ec28 100644 --- a/substrate/frame/revive/src/tests/sol.rs +++ b/substrate/frame/revive/src/tests/sol.rs @@ -180,8 +180,8 @@ fn opcode_tracing_works() { let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); // Create a simple contract that will produce some opcodes - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code.clone())) - .build_and_unwrap_contract(); + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code.clone())).build_and_unwrap_contract(); // Test opcode tracing with basic config let config = OpcodeTracerConfig { @@ -208,10 +208,10 @@ fn opcode_tracing_works() { ); let trace = opcode_tracer.collect_trace(); - + // Should have captured some opcode steps assert!(!trace.struct_logs.is_empty()); - + // Should have limited the number of steps to the config limit assert!(trace.struct_logs.len() <= 10); @@ -239,8 +239,8 @@ fn opcode_tracing_with_memory_works() { ExtBuilder::default().build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code.clone())) - .build_and_unwrap_contract(); + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code.clone())).build_and_unwrap_contract(); // Test opcode tracing with memory enabled let config = OpcodeTracerConfig { @@ -267,7 +267,7 @@ fn opcode_tracing_with_memory_works() { ); let trace = opcode_tracer.collect_trace(); - + // Should have captured some opcode steps assert!(!trace.struct_logs.is_empty()); @@ -296,12 +296,12 @@ fn opcode_tracing_comprehensive_works() { builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); // Test with a specific configuration and verify exact structure - let config = OpcodeTracerConfig { - enable_memory: false, - disable_stack: false, - disable_storage: true, - enable_return_data: true, - limit: 5 // Limit to first 5 steps for predictable testing + let config = OpcodeTracerConfig { + enable_memory: false, + disable_stack: false, + disable_storage: true, + enable_return_data: true, + limit: 5, }; let mut tracer = OpcodeTracer::new(config); @@ -353,7 +353,7 @@ fn opcode_tracing_comprehensive_works() { depth: 0, stack: Some(vec![ crate::evm::Bytes(U256::from(2).to_be_bytes_vec()), - crate::evm::Bytes(U256::from(1).to_be_bytes_vec()) + crate::evm::Bytes(U256::from(1).to_be_bytes_vec()), ]), memory: None, storage: None, @@ -381,7 +381,7 @@ fn opcode_tracing_comprehensive_works() { storage: None, error: None, }, - ] + ], }; // Single assertion that verifies the complete trace structure matches exactly @@ -393,7 +393,7 @@ fn opcode_tracing_comprehensive_works() { fn revm_opcode_serialization_works() { use crate::evm::OpcodeStep; use revm::bytecode::opcode::*; - + // Test that our opcode serialization uses REVM opcode names let step = OpcodeStep { pc: 0, @@ -406,18 +406,18 @@ fn revm_opcode_serialization_works() { storage: None, error: None, }; - + // Serialize to JSON let json = serde_json::to_string(&step).unwrap(); - + // Should contain "PUSH1" string from REVM assert!(json.contains("\"op\":\"PUSH1\"")); - + // Test a few more opcodes let step_add = OpcodeStep { op: ADD, ..step.clone() }; let json_add = serde_json::to_string(&step_add).unwrap(); assert!(json_add.contains("\"op\":\"ADD\"")); - + let step_call = OpcodeStep { op: CALL, ..step.clone() }; let json_call = serde_json::to_string(&step_call).unwrap(); assert!(json_call.contains("\"op\":\"CALL\"")); diff --git a/substrate/frame/revive/src/tracing.rs b/substrate/frame/revive/src/tracing.rs index b10a1d85e06b7..78eb8bcd8e1fc 100644 --- a/substrate/frame/revive/src/tracing.rs +++ b/substrate/frame/revive/src/tracing.rs @@ -43,13 +43,19 @@ pub(crate) fn if_tracing R>(f: F) /// Defines methods to trace contract interactions. pub trait Tracing { /// Check if this tracer requires opcode-level tracing. - fn is_opcode_tracer(&self) -> bool { false } + fn is_opcode_tracer(&self) -> bool { + false + } /// Check if stack capture is enabled for opcode tracing. - fn is_stack_capture_enabled(&self) -> bool { false } + fn is_stack_capture_enabled(&self) -> bool { + false + } /// Check if memory capture is enabled for opcode tracing. - fn is_memory_capture_enabled(&self) -> bool { false } + fn is_memory_capture_enabled(&self) -> bool { + false + } /// Record an opcode step for opcode tracers. fn record_opcode_step( @@ -61,7 +67,8 @@ pub trait Tracing { _depth: u32, _stack: Option>, _memory: Option>, - ) {} + ) { + } /// Register an address that should be traced. fn watch_address(&mut self, _addr: &H160) {} diff --git a/substrate/frame/revive/src/vm/evm.rs b/substrate/frame/revive/src/vm/evm.rs index 4cc6525653df7..c7e9822b99c3a 100644 --- a/substrate/frame/revive/src/vm/evm.rs +++ b/substrate/frame/revive/src/vm/evm.rs @@ -17,8 +17,7 @@ use crate::{ exec::ExecError, - gas, vec, - tracing, + gas, tracing, vec, vm::{BytecodeType, ExecResult, Ext}, AccountIdOf, Code, CodeInfo, Config, ContractBlob, DispatchError, Error, ExecReturnValue, RuntimeCosts, H256, LOG_TARGET, U256, @@ -34,7 +33,9 @@ use revm::{ host::DummyHost, interpreter::{ExtBytecode, ReturnDataImpl, RuntimeFlags}, interpreter_action::InterpreterAction, - interpreter_types::{InputsTr, Jumps, LegacyBytecode, LoopControl, MemoryTr, ReturnData, StackTr}, + interpreter_types::{ + InputsTr, Jumps, LegacyBytecode, LoopControl, MemoryTr, ReturnData, StackTr, + }, CallInput, CallInputs, CallScheme, CreateInputs, FrameInput, Gas, InstructionResult, Interpreter, InterpreterResult, InterpreterTypes, SharedMemory, Stack, }, @@ -158,15 +159,16 @@ fn run<'a, E: Ext>( let host = &mut DummyHost {}; loop { // Check if opcode tracing is enabled - let is_opcode_tracing = tracing::if_tracing(|tracer| tracer.is_opcode_tracer()) - .unwrap_or(false); + let is_opcode_tracing = + tracing::if_tracing(|tracer| tracer.is_opcode_tracer()).unwrap_or(false); let action = if is_opcode_tracing { // Get tracer configuration flags let (is_stack_enabled, is_memory_enabled) = tracing::if_tracing(|tracer| { (tracer.is_stack_capture_enabled(), tracer.is_memory_capture_enabled()) - }).unwrap_or((false, false)); - + }) + .unwrap_or((false, false)); + run_with_opcode_tracing(interpreter, table, host, is_stack_enabled, is_memory_enabled) } else { interpreter.run_plain(table, host) @@ -228,7 +230,8 @@ where // Unfortunately, we can't directly read stack values without modifying the stack // So we'll show placeholder values indicating stack depth let mut stack_bytes = Vec::new(); - for i in 0..core::cmp::min(stack_len, 16) { // Limit to 16 items for performance + for i in 0..core::cmp::min(stack_len, 16) { + // Limit to 16 items for performance let value = (stack_len - i) as u64; let mut bytes = [0u8; 32]; bytes[24..32].copy_from_slice(&value.to_be_bytes()); From ebe0401bcd4acb90b649fbcecaa47d632a9c097c Mon Sep 17 00:00:00 2001 From: pgherveou Date: Fri, 12 Sep 2025 03:41:11 +0000 Subject: [PATCH 09/76] nit --- .../revive/src/evm/tracing/opcode_tracing.rs | 12 ++-------- substrate/frame/revive/src/tracing.rs | 17 ++++---------- substrate/frame/revive/src/vm/evm.rs | 23 +++++++------------ 3 files changed, 14 insertions(+), 38 deletions(-) diff --git a/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs b/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs index 33457c9224df6..b066c31e03b48 100644 --- a/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs +++ b/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs @@ -98,16 +98,8 @@ impl OpcodeTracer { } impl Tracing for OpcodeTracer { - fn is_opcode_tracer(&self) -> bool { - true - } - - fn is_stack_capture_enabled(&self) -> bool { - !self.config.disable_stack - } - - fn is_memory_capture_enabled(&self) -> bool { - self.config.enable_memory + fn get_opcode_tracer_config(&self) -> Option { + Some(self.config.clone()) } fn record_opcode_step( diff --git a/substrate/frame/revive/src/tracing.rs b/substrate/frame/revive/src/tracing.rs index 78eb8bcd8e1fc..01f815c0f5087 100644 --- a/substrate/frame/revive/src/tracing.rs +++ b/substrate/frame/revive/src/tracing.rs @@ -42,19 +42,10 @@ pub(crate) fn if_tracing R>(f: F) /// Defines methods to trace contract interactions. pub trait Tracing { - /// Check if this tracer requires opcode-level tracing. - fn is_opcode_tracer(&self) -> bool { - false - } - - /// Check if stack capture is enabled for opcode tracing. - fn is_stack_capture_enabled(&self) -> bool { - false - } - - /// Check if memory capture is enabled for opcode tracing. - fn is_memory_capture_enabled(&self) -> bool { - false + /// Get opcode tracer configuration if this tracer supports opcode-level tracing. + /// Returns None if opcode tracing is not supported. + fn get_opcode_tracer_config(&self) -> Option { + None } /// Record an opcode step for opcode tracers. diff --git a/substrate/frame/revive/src/vm/evm.rs b/substrate/frame/revive/src/vm/evm.rs index c7e9822b99c3a..d5ebc5395733c 100644 --- a/substrate/frame/revive/src/vm/evm.rs +++ b/substrate/frame/revive/src/vm/evm.rs @@ -158,18 +158,12 @@ fn run<'a, E: Ext>( ) -> InterpreterResult { let host = &mut DummyHost {}; loop { - // Check if opcode tracing is enabled - let is_opcode_tracing = - tracing::if_tracing(|tracer| tracer.is_opcode_tracer()).unwrap_or(false); - - let action = if is_opcode_tracing { - // Get tracer configuration flags - let (is_stack_enabled, is_memory_enabled) = tracing::if_tracing(|tracer| { - (tracer.is_stack_capture_enabled(), tracer.is_memory_capture_enabled()) - }) - .unwrap_or((false, false)); + // Check if opcode tracing is enabled and get configuration + let opcode_config = + tracing::if_tracing(|tracer| tracer.get_opcode_tracer_config()).flatten(); - run_with_opcode_tracing(interpreter, table, host, is_stack_enabled, is_memory_enabled) + let action = if let Some(config) = opcode_config { + run_with_opcode_tracing(interpreter, table, host, config) } else { interpreter.run_plain(table, host) }; @@ -199,8 +193,7 @@ fn run_with_opcode_tracing<'a, E: Ext>( interpreter: &mut Interpreter>, table: &revm::interpreter::InstructionTable, DummyHost>, host: &mut DummyHost, - is_stack_enabled: bool, - is_memory_enabled: bool, + config: crate::evm::OpcodeTracerConfig, ) -> InterpreterAction where EVMInterpreter<'a, E>: InterpreterTypes, @@ -222,7 +215,7 @@ where let gas_before = interpreter.gas.remaining(); // Capture stack data only if enabled - let stack_data = if is_stack_enabled { + let stack_data = if !config.disable_stack { // Get stack length - this is available through the trait let stack_len = interpreter.stack.len(); @@ -244,7 +237,7 @@ where }; // Capture memory data only if enabled - let memory_data = if is_memory_enabled { + let memory_data = if config.enable_memory { // Get memory size - this is available through the trait let memory_size = interpreter.memory.size(); From 1785e8e685e39a1001a562530a624474590dcc33 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Fri, 12 Sep 2025 11:57:18 +0800 Subject: [PATCH 10/76] fix --- .../frame/revive/rpc/revive_chain.metadata | Bin 694304 -> 65775 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/substrate/frame/revive/rpc/revive_chain.metadata b/substrate/frame/revive/rpc/revive_chain.metadata index cb98c4653acced703a2a99b134af5e373a0db154..aa946380716f88a3cbfa55d9a5a4c9d04eddabbe 100644 GIT binary patch delta 10306 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

o&c8YCh1AEGh zeSO9Dgc=LR%Czx(22ou3Ybmw4MY?RdpVC@(sJQe=;H(m&3s%!kexZiJeeCcL7*#p3 zMbQZtt9;Dis8yp8+tUMFF+NYB;Q}}UvnlnFt9>ZyxO7c13W32>cQ&OYHpw$1XgxPC zDm8meik01MJIphS?h=ewlXZx0A2sp;i$ol<(*C72Id7U^C9!Pc1BOwjMovm_I$xldqY*$Ud*eMA~lEw~{TN zZ{*(#0{)$i;3K)Sl$7As8~DL;L$zT7+@Z`Xd%P$^-4q&WV|}Qo^o}ECgR|4Rj(lls zzDie@eP%|C4{nt)#8ea)2t&;pdr_zzEoQfM!vAEWaq8LH;QVE1M zn?T9=k#jiQ5vf*WpKj#e$d5F)2&!6IBnk^q+lLR!ACJdmsEV-bVkW(iv-%g?&1;7b zcN7CYe0VQz9XJOcKHS~ecF2HF`vDRpLL63_3q|(XM*huSNKi}r3wHp^-ouCQ7#5Dk zryE24)^MtGJ|kgmCab0KH64Ep9eap~DYC7*^Lhhw9e-?Vqk8@ZJ+B&cG(Wqe@zIoo z73qOs7{45-OOd_hZhQyqZ^aFGpf$}OxjT%e zv4daP(YO_6^%*(g*4yvSAIU>jc5&Y!A~a#;;lqY`M>ZdN<@jg-k3Uq^-A!(fj_*a2 z*4{Y(8?QL>(8R=}<3!ffkn|IG=bx%|Kjzr0AkEhL_$a*|9EW4CeNXsLmVK=;xEvsn zH@>nS$)5*;W(tO0-`-;E@m4E^3xwd{ckjwSSG#e1Xc0dDi-|35aFNXl7$gje2rjK~ zjgk#>LmqLM2&8ycG9}$?AuQnb*Z9{f;E`@xxKXEUgcn!W`d!7d?w{Uj&aIxIsH%tL zR3&>l^R$dfJfV2R%yCSjh^4FR+a>n15;weQFC`vJ-{IbUW4o`O3H|FDjO--eWW`Tr zI@v>fk0dB1`O5vb(Q(21>F(?5;I^0EH!2ryv^3pC+AViu;|RFI<}7ZU@MI2yxog*G z`QqJVuz2W|6D}aK=JuX*Ch=YtSQg9#XRo-hseB~FgOL>JI1PBKWBF7<#gH2!_Ctz^ zkR3NAGg3YX3x$mD0Lt|>{A+|Z7bKpc?eOKmok5Yi(*B2dwbq{efQDAijYk`}0mWLL z9?q6xm|R@1;Qn4^=niihb0W37Kz`l4@x5HzXU?C#c;cxOXXZjE-2Ag_A=y+GsZ)bp z$5tWnE8UZw2x`eCw4R7jN*XLB>v~+{tasT-`uc})bK?63QvB9^qY)rd&Pu$zV83^e z=jV^AthcEsnY>+;sHK+{3{ggI+1XVJK*0B`5J=x$mN@j&llPNoJy#NS=@Fp|&p%G^ zs_M{&&b8hP@h9d>8Enj1;6)rYFK+s%>!EKGtZIYC)Il%en7iUkW5BR^0@i05;)7bv zu+ZT9i#Y!7;tS^qXh$swPKVVtLTt-lr!r|1Z%>rJS>?xZqU=SL`cUK1VeI$hGNKf! zP^_njkkhe98BOr9_b`r&+GE1%sYgUA1qhF*Wz9ZUPc1jE=k;EK#+oDh{#rD5c%v=1 z|Bwa}bEVT^6;$=-c$zNS{n~}~P8d4@M~)YTs;)Z#v|hSPCv|51vRQ|OQh%CxU)8)j z`-wBpv2j;AhV)K-?g1}tU9r~RAW2XAb<%x5phhOuK z+D9D!F8TpmYJSTS&ra)9@cGGz<5IlXE2M07xVQP@xW4c=;sP_*ZZm}=8lLYL?-ds()yNv-Z!T5tLnuy+8g|(8M>u?(VVZO-y%8Hb=8H6 zKyvi=FEnu{={|UdHSyqaG*^k75+Ns@$I@jpyE$>#E#6aXQ<$_53h#-zUOQSEZ+6yD z?ko+b^*zi=mC~=;En99IK-`RPP*ZH*4puO!K>{3It5?KQixEzHE*?3J7Z?g#jgoBX ztIN|`m^%gdfB^2hq*K}+<1p1P83$u+SbN(T_6Ush_N&V$Y{N}(xl<^P3X0nz%v^-X z-wc>`SMe!ab%Z?i<_`EGtLck1DPR?c31L3J)~60`VaFl_M*7Cy9ZOJx_8d!+2Pzb3qRC%kARj^;Qelq7JUB;gt>Y#_;1-y0EW*3@l@$ z3qy6fP-D1iHzG3R@Z8k}&dH0>S$_NUMc-`# zy4epl@~g2KH*;E^UfQ@iyV~BEVK1^w84T59{gSO@CX5B4Ln-6MKVvGC`kh)}vA7yM zGQG-iMa?~ZEy!il;@v?8@;t`xCTh5F`v@OA*Fz?RwJK~d~gvP zgZw@|Ny#`#PK$>RA49f6$~)pk0T0+)N*(nljr{nr)f-3tDwhv|FypXM>!=|9q)`mg z%)aG6YmgR;|H+Vzak6@g%RF(z|mH#=!rF(ys< zA0Z7H@v>~sz+lHh4!b}$VU*`-%dmXtL# z0UC;3G|5}ukloXNrrq#x1oA1HEZ%&`%k8Db+T14P@|hkSB2{uQ75J>s2HBEq$hO|^ zDOZqZPU`b8ySG1t1&3ty54l(J{WpkbYlUCg%lzYTVy42!Px%4LvQaX`=tOOQ zR`LVNCUC14j$_BtsaUxZ(cSY}z~|IYYihLq0zk2GXkl%s{lGvzGR=R{eBk+56Fa*5 zoeI7jp%c&blRYqy4)ucr9BTe2v;Wz3a3JF!Ca}*JL)oG3ZFj=H|HS(G>bg<#14m|7 zo>O_%b1aRr&9TP!=5~SeL!?&og0~`BjIPouWu@i5z0}&cA@5MpXC`}VeK_WM0960Z|R`)CXs-J@}W01Etx>B4`*U$aP8h)R|K3T z9ejLg8B43uEfyv_a9$K3PKs-qV_(*h<{|bUU!Ztd)5-E_U|(_uHRhvfKe2L!qPkbI zyU0fiAE7r6+4WFm%g`j?0Q8!<=m)5siHO)~>c<-3>nQXrboclfQoHkMy!U|r?&6s< zt0|R8IQqY>X`WxHeq{}hP)?b}STw0PwG|+2xJLP^@chTYNZty5g&6*&b$@6ev0XT6 z6Cb^m*sgR~i;20_)zb?rTgUi>yR*2!Z@it!$EuaY&0dF6BfXJb9i6vhPEsbqA)ILf zCLgVQcwq1p@fxHKs_JuLd^mcewQ!BX#Qxy9*5cIDG*o5_7~K4kf#MuvXRjK_ho!ik z%Jifm?}81~Chm-Oj@9v^_yyoa&m z^uXY8ZV$B36jNw#wl7{$v?1FE=u~PCd4DswX`;kdp@5e$-yD z>|$iX<&|0VZ!1Oi+JXE68OLgE)$6;miS+2HvyT_qv)rNemg?2r86(~T%3fHwM2`d{ z+SrPzW#`b&x7H6Io_6foz}J@~P~>21FRyNVc2_=hj{j!)a z;;Hq^=i+b5MGXv%*n+LMM%tddZIDm8-XfRf8Waql@wLs3!unuBPuNcid*PeBdRcki z=zCUwXDrq&rnv1ga=ZV$GiAkZG2%9R@rzv9g@HydDn9cV_;_M%mHhH5<$5J#wh>!X zEPi=cJ~l>1Ic$NioIJ?haiXOjxu`8|e`QxbG3^c7S`L5fjt|&UIJnnqCv3?M?!-`b zT}O>yzcny89>hPLo?ap}G#@yI6BN06Zq@mMu?OdJ{@aOI1+vcDLI9Ylxw6>`P*2^3(H#Zry1#)*b06b`9RBrbRlpkeG)iPm^7!Sj#Plux8WedpEL!Kr?=IeEr8S8|BcD(mC+f>^ zduqO^T^!7YN3*T!8ZjTXR;AZZ*U0{HuDWS+N9B^syr#FC*Q6=2TPrv(BUPDV+-kJh z9$?)vPh@|e&5Ez+9$(;Fu|9zxDfiE;oQJa`?TYTBt|4LsekpL~Lgp2?oG|11;t;W)Q7oS*P9@{C`I=DCZ&zC1h zq}$$@Z|HyCc5-9BDIC1F)(+mA1_q;x!KDRqN)Q;ge`9N{)y5UVexAaypoo4D`7p$a zEcu@=@+m!-v8ki+#5$48bn#_!ii)G=7JLg*###T9w5!{Vm|n>T2XP)ik{CKorhjhK zK}Zg{GPAdeIJ{{fdwc&ixW%RZ&M@_N*QWl??Eo1^ZKPDivOhPDhac5c^Uv4U14+o% zqxZdjOOm}?Q+Ve<_B(exh4+Oie4sXk_YLgt)GG`lCg3E7O`C#1?V;f%agZi<7ZdgAC%e4)#9~Yck$ibS8aNn$?XBOv!>AvIZN3#itR%^qx{(Sn$ zf&7K=DMSeO5?9i9e+RSriGjfj+YAyexQULTaw_%$-sz>4$`6U*Qelyf;kM}^7*9D( z*{8Lk9~sC#+l%-e_iI!iPfR^!H1A3py?AeEfyFoK0JU8uW#E;oBD*Rb=8l@>T&iNm z@j=OCo;z)b_LrtZoucJf2vQ28^hOfPstRl6DSRK2^Kk@aS8+4^X&}I&bU|>EOy`&# zEjl204MNnvXP#d5Pu!9duZrjiZNZQJF@)bF-*OU*g#K;yr=5$vYphb zO8^P)&4USTs6lAf8 zeUx%g*;tTHBHQOa)Oa#rBB;7j^!5(q+0PF&u7(p1C5|p@(M>Koq;L=#2cCLpdd2+d zIhl>8ULnEpivx{!gs<@~u`?4(d#+zJUBksSSpkUdB|7We>axXA4;~2k0BIa!xt=C# zBC4V66n{U4!zzEnmw2lb-R4pSi`)M4K>ocT`<}zgN=-*nSL35qSN3;zS7?Iwx8<+@ zqkQs2fcBhzE0;LhB5LV9(1ypT~SAp7nePcicu(r?l#eP1)A(;wb(XFm?; z@h3I(__0CvqoJYfPt-3ze>O0zhZVIcYUbVnZ&%)vJy)70)yp2}J(mT`IIk zWOde&Uw$OqY;|Ym2?~}fGmuf1AMPS|)xwR~&yn~C>u;JqxdGQc&houGoQM;-n*RUD zRa80AigtqhI9t{9*v#5w&5ACrLs@jT%H`U3Pp_kPbvkD^5bVYa)>ccza>I$Eu1DAv zxo|Y=J)mW*4ZN#(NA)WWEZrnlO_91oH_fa!USbo z0}mhhG!SWd#tZ1#l$1?^{z+VARz7 z5q1593!^druxM~&X$>*LaQZ0DNNS!Mx`E<*_BN&h10>fq_ImQ1|Myg@Ng6EK3bXy& z)%o;W^4q8=!}aJTtI$k{(QEZSIIy{*xCw^&+3x%YwfpnDwI92>xpM7Dy{|{dN8xE< zjQj;Yw0GJN#AoD$ytpN|0Qt#AA#h-C{;pcbv2>^P6B94A);6wkZ4K=0YYVxBR%6KT zD(Zr!yXu~v6-2ESW!<)|A8`!f^8D^V{<}5cm|D6W<)d*z z87U9pA=y`jw&p{ZkXy6t-X3Tsr)@9nGkN1wng45Dsp#tZ#(jJ9@!)%aDw+klZ*SfH zGSOpxnK0|i{d>iJ4Q1KO_L>ntAQH8=5aTdEbZ{KiZ3$zX@F%unp+T<;-c$|4rde!1 zus8pxSQ1Fb5*lSPhsW+aSWFj^RomtPpxX7|-t55+picIFza4muJ{S0Yl)jt7>jYnR zu%rJuRp@I*&?3F8tFMN3H0vWlCqih;BJ_crmR7bGgAD!0rXdZMWrwu>gG1R+_D}~b z6A=u(w*4%{DX4hjP?_awWExKyTE> zVXGh6o4@ff9A8_f7uM?AU3;G$e`Iel8sBOZufgT*seDNGO4i5zXV>)JjDM09hO!gF zkD0yMOb@tI7mk?^xwpn`+0VA9K} znKL09;uvpo35ybR(3z9gj(dSba;#GH*YR9A{#Ms}Yb9srw+CxX>cL_ z<)^51G3#%!B5i8YyBe*WoOX4_V_1YyhU_Zl?e)5% zIdOuNgj;hiWufev4}aElB1&4gMS;9%v+|AGQyfh#fk!$XSuSmszjasfWjYg6l zl4?`NUqeRl)mX6vjX0=SZQ@H?ugYt6S;pEe+!j!~J{5b{@}_E2E8$*TUAN|gdN@&{312Jm+`AvT36an)xJqZ?yn=P=XEivg`kzxExlNMQg+D-V9G`@>^ILy-=RyK%5heYEG{-&s!WsMh01> zhIGr^3df3iIlN$^mxoG}Q{l+qoGDHXUUwjOIBSU`1g4`{P#0SE!NA;AYT2}9<#>SA zF2R=+Z^mOM?V_}qD5E`v4d|g;vz)`@jHT!>wzM znNYEA-R9g{S>43CVW-B$cXnejX_)O?GYmq}V#`aj5oy5@#bM8v55upDsUDZqjO=yB z;a^2R`zs`8VJs}jvLYwptcEoRN31AXtb!!@C` z#zXt5VyXdi5ypZiWQ6OfNa@Lg4RhyApPze#6kYKF^q9#J8B0o2bh*$5A;JdQ4NRmrK9ZV}+SuGc8v%ge4 zoT-c3#F7(qI@f_ZfJpJVZ`N? zxa}i>ipNO(Y%~=06Ox(7uqC7kd{UjksRyjivl>^(HA}iBvt*T2en~6*K;yM}5Qz0X zn-kXFU2f$-gyoHJ5pE-$1dR+nsLBq(}wJ7s9LhDmts&;Qe5mLc559 z%~*n^5KMY|=bL9GK`iXe_$QI#o+4|hWod85KS{*b_h#4k4$7}Rj6S0L>lnt41ZSgj zc-N{h3vc5-oR45N!q$lx|6A6<=H97nbMIjE)ElP zR9VfoYWz0b7mL)(3-2ee-3SY)ERVe+`dW26RFxObtYUUw#nK7#Cu${c0AaG+f5Nkh zjUpq1YpWu!ocPkFQp)TqFiBpKkyvW2X5?b!&Q%VdS>=^BT=EC$oEB#&YNP0Uh_dn^ zbbkN{{ZH8C`*XgJgf6}!EYd+)MjOQ*uY$+0e>mrR!ei5hr6Fw8veUFvCimCdtGVtmc5nI=uO(+!J$oDY~CWA;rCl@%iq3tID7jY57Ebj^3J`3M}uOo z3G2IvGy9hN8IeWT^X+V2SH#hv$AUONm=BGD7p%_0a=y7MDmn6D5-bHj>>_*j-u!b_ z7w2GBHrq$cY1{iCU18`VrAt9L&qj%rT54m8Cz%4`HG~u#2x_Z*5Hf@OU+YEI*>`U{ zhojv9>}VD!GW!iAhRmkCxhqewXZeARs}LX&sR#3XQl_xE*nlAxbM?YjH^}=5(OKU5 zoxSG^G)UkfX z9?@}(hkON+CO)<|pT!G6X)5$HePN!(C=@=iH-8=n?bUVpy>%j3wXWG%>|*80{(R(; z>;LLYH|!!5`x(F!1*4q6%5&}`^B`4(;6B2y7av6M_yc!*pg$C7#z$&2<3mOuh7RW0 zM?^CC`LRA59()Pos<5zi)I#}dTUlSAkwxN?K-^ke_K7|{8!8=0JJRUHn+mrwv}>J5 zxg)RBCv6&=yB^H5Py3ACzBl`9pYeL{uK7`}q0N>!(yW-Yzm1EB6Fm}XPOw;UNnMCd zRqT=pg^`beKcb8geBG|%U4a{1v?eF}ih_!SVRYjLg?Emp+bs6XZ8aDFj;w?wp+6N{ zA0x)(niAVEPElZUbxH&0A{qWt_4GJ=-Z@2$q2qi#mPMF?J;jI5Al1|)oA|Y^w6?v> zX#RGThEIysyQ_$|yhN(2V`o<5z}WuKYV~#*wOUWLHdX<(QM||9FR20>U1y);5uEZ` z1j5&(P6UjvK>6`zQGnn)t!s42IS%-_NH8#wthAoSaT2nG(nBD{ekuRs?W3%SI$B4W z@dd=A&|$dzv4^=qPWbF{z6@S*0I6}{rh*uQX2YC|7Z#rtK1i zsdmJ0#LhuR?eoHAmar)e(?$)DmlW^s0z~hISSRHd4TKeN#9S{jY+cq3KhbYz^vlu$sUDnQHI!;7SUy&05DozH(DNvw-}ya zawyF97jLAgtLlueAd|CbtdXFCK*`bPT|)cNn#FhJYr|mxXQ_823im6;oHq$kRRc-- z3Md_cLM@_}uC!P$-_&1qFMDKRYwsd=JMp2r56ji zRl6F;>@fJk>@JRSmy$?BsLWA>!<$T_XuY}5s_2=~=V*zk`cDSJnX(~pT=sAT4E`Y3 z1+s}uplsqx1Xn&V%5gDIBVnR&9JR(%y#g2h_B8{VWf;7?ssW3H#3;jSM zoxss>_~LfU3-_l`6+VEnLJlB$Qd~h0{1jsbndd~O_pv@0a#br@p0f3cZv=mwh6kQ_ ztdIw=JIzR{zqNQ}ir|N1+|^eI#m6N^gwOk0w($I&^aT@wVm%;Cg+zOc4SSHteUOGD zDow?iOxr}|i5pvlbP3T|1U*9F7jU9?c}M z`F>pnOJF&2#y(X<%)88JVP8r*Nu~=p#Hx!Ff}sK1$cS&QkLXo1fc?SWC z?D=5SkvK-eqDKWmwe2;G$h$BidJX&6qA0Qi`-uo6?_kb9uDsQ*5bgvG9X}_-W>b3j zR#BYEhw`DJQ|{`>k$?9MNtA4><>KZm86V@-A&qzV~N)^W~s_PU$aB zq18BIJ)&J6hMX_9n)l@Sf#Ak^2G#q|`lHEC;NI2YRz!eQ+$OQ|x_n zvxurE*=R-6?a9G%!gnnUo|DyS`@uQlZ1Al|_W|D@$CoKhP9EK(dshd`N#EYMkwT()kyA)S z18Q$tj6E-MT8v8E?o3v3*Nc^`;`TQ0J^LCld%*^(E8M%clF4j478Pevmw@3iI;=46 zO5P-9jA%JCYwkaQ1-!NK8nhqzw>$_vaA^7P zcr6e(rxuFh8ln;P04$iUIKuy5kOB)6Gg0ceDv%Jk>avytprdbWP?xG8V}8{}!gOVf z09i;^uXmYQ@&hepcx@SVuLHzDFprpN@7E zM-S|L(;!iG_?2zlU*=^l2w zG_AaCY9)&&le=HVE25!|R)t-S6Sl>wXkXx`2oZ9c25>W>rp~R1mSBoP69Xs7K zG_do`FnD;D7N2YGX^m$dAIKg5$TA5Zx3jJyR+3bjm)r_ zAasfxrMfJK^STmorLs1+YeF6l$%n5;!_`Ue|=&Bii zf?ptke~ZdNE*3}_inS3BQ0!35bq5^v19yCnG~DGEp7B|{MVA1u_ zV`K!BmyOn0MhMGVmE}`GS3A=T5pz*N(;G5}WOUgi#c@J_UO8zF3drk{{@d5zP~_yN zNIx?bB_UBSn&S{KlpX8?TCB%Jy;KJ_K{!gJ+WJ!K;!<;f5Vtx0l($4Z`|r4BbP}Vo z(*Na?@~XrZb@39FzuGZKDYcr3E4I$o-@S-yp%PSf*BHsuTl)6)Z&_B|?fRMvR?wFE zfimHGhZu2aU;eT@$PqDIh@v`aR>^D0w#MQH_oCSe)km9_o)i$8`;HOfO#I zOtvmwCvK^0$U!2MCW%lQc5HfRUp|$fdCmCfQ}q4NzRrLqdmWS5!~3{<^*_5F*_ZJT zU&G1a>{YkeX>+uZC~hIsxJ9#P9@|N}Va%+GzBMFkt;g+}(&ABKptCuf?mypK#7y=c zmlJ&;trD`F3sR~b8B!8>_UM^?`LM}((3(>+46kFYXF@S$@acUy{{5-hZ8L4lQ8{y~ z;bN!R2o({i$GmaTiwebMm_Rls;WG(ENG2H@$I(H(9@As(pyedb+ge^-Xx7`Ng46d! z?<9UcZw!KBXkuBzt{0{p5>Ea_6@H%Zlb@Zw)kKH_>7KoOo3a$q~+t3zA{ zSD)RNf2sSuBXe@V^;9LE{8%~9l`oMJWgM5cF`sN@;H_TwiPL z%h$R)InA}dvJpX@u%L#EuAf`lm%q^cdX4LUe03dcghg09g;arNj2>JZ7UZ@4Ztd7pNTMNqTdi8>of0o0c zb+ffdZXHVjXTRfY9guJ=oQX`k-u`5PYFSKjI>;>b!kONRIjubZYPI@ReaJV zd)fh3h;f7=8J(V;fyWA_^bJ&x826W#2^0z$7=zGOPzMef2L>A!!#S#T5_{r`FR>-O z8}n65_9t7YnbH?ckp%|^2km+*sg$o=eU}nUCAlHSujme4#}_-$nNqovwDerB8&{)` z&7Q^sby%28grB?IO4Jc4BK4X0K0On}-1Rt$2frG7VoUfgw(W+JBf!KWS17f)Ryl;z z1biYuDX>Iv^^fZcM-=%C@?~V}jHSP<2QFBegckaW#oS=4npmn4=%&shoWzKDA-_FRk8` zy@c!4mUT^XhKa^qSc|pA?s_6$35b)fGvMmJ;vgr~Li29-ZNONGv6h8hGOHv~ZgGvt zrAEX|*n3WdstIO;#S5`Xm~RyZL%~Box$+WpKh(6{_qbiAjYkUud9W);Ht$(1o|6CS zIiAhijD4$-L+9s=qX!e3PZJwQ7Fup0&e}%yOO5;U_>keWbhXt9aypoAS_NvJOHwy% z^SfQx9)z$zmLFU=CyJa|yjG5)RF#$ip8pCAAqzn)ol`QGDFBG?^h?5ZcFP`M%Q1R_H##3ew^Zt; zeL6SF#A>yazSqLiwX>$pqIHwBd_)+GhNL^P5`@qv(3)9Lt5USkqMFi%Oz`1)OA8Fj z9eL*1X**7>aF||NyxIS%oe@FEd(z)x?6_W5&S&vp;0O7o>9psSuB0vh=_KVw9OT4d zuxXbUB1*TKGb?oEoGW=0?i-|7R0Md`95$5(Sj@gtYh8;xj~|p74pU4EeNqo1v+}Hw zX48Yx#EHa6rhiC|S5e{*|`Ef0u zHB>O?GLNV8`-xBN(*Tl{A>i*oZ#XDHyB6dH9Y_bk08-AP84yAvG1$E~rf%V%OQKmw*Fn#R{lM8g5Jlvm=WETp?UZc1a&pTirL z?N^J_TDCZmq~y}7r{#m#;2dbVAq^|Vi8!h98WNy7*LCxdEgP@3NHJ1wH2?vb%$7qOjFghlJ?mFTL>;%gM=}xfL}bFU z`^mC&T4XDJC)HrkO=(9_X$8^?;ncdv_d@@HdpqEl>v>XQ{~DRf^otq0!CO7{<7V^@ zJK#=utdQ6oMLNeoTk}>4>t3-fXBAP%QOW+&(1ZD$sD0cTh^00KMwS=@v&t)VId>sM z%CKFfK9q)?0~Tiin zqUfJH6YN$5cdURMM0#-qnMIj{?}S4yFI{Uvrkx7t-qBq6=hR@um~!>Z?n<56@7RUn zcu(dlIvF17>a?Do;0E9cuLxkflSH^hfg ze}_+*mN1x4Ah^}l9KSEKCpqcn_{8eEM%wp3&|Y7hz+@OO;2xRfSgOlx$feCIFnjH` z^e7f5&@zv%=7&LQ6WEM2%pY0~H1P1@h`Q}p+1K->l0PM7Cy~I!R3FTz6$#wYT%1Eu5cB%iMba4DwUt^Q;%fUY9{;(2TIk5 zjKR?wCfsM^?Ot-+FNt$WV+0wCqx$P2GB$Ko4aZK4@rV^k)p=dPVTY7{GR9kDIsBZ^5n_0b`%az~2tE8ABoJ-s!)FPho^Sy%vJ5R7U{FS2Pm?jRxM;1x4f!rX`JH}QS9Qvl4O(h|mZubtD{T<+! zSY&~ZbO#>w=}1J#r^NY_EEVl(agHLXj8M**T!e4|-^hi0&aa~EK=IM~7=FT(Pf`8G zmm^8ufk9()Un=C$-fXV+YEcUEY}s{qj>l+F-gwT0agPI#G{FAoQIgGztDR;hdl*bi zTmALHJMpf0L-4M71FBu+#ZooWS;0B z=P+5`(Y4J>RQ9mo@|q*#Nn!^!F5y;-dS|ZY$apoj?_h=B^7GI6-on-WZ>`Pwt(x<_ zMfUc6{c;6N1z(TjMs(YaQQYh-O<10d+9POx{+66HYTzB&1^D@vJWr3Z&b(riTIkpV z>-|o%9=tuD%HHjEgrV$r`p@nb8vZ`fK<}%J`S2GgIW|J{Io?Ui0N_b11}jJANvPM~ z*dz&u^snt~VkUeg(^UTMf@FbXuTPIZurL29?;S76hxYkCf26k0AF>r5I=CnMh<2QxAG4O>PwdM+amOR{ z@n!tv9e48S0OsdwVA57Ay7k$8Jy{<=3)E<@KfB!)lPul0nC&3HK()t}KTn>G%rqZg zYArYO3AT15XaPaiBr=i;vNPE+?tS{8J(0(6v+VO)$WQOfzIewo@3l&OdAs?XO5TQW z1+WY9MJz@FpezJsvI6{AOTeq}1Xmpi2a6WoK9rA5ryoSIZ9CR^Y;h~d&RV)q#<&rC zWYvN#C08wxXiSceGxM{O(qa8;`|{%eYk#LRkY!)nZdmDY?d4jqcs|$xa&|N=!*Lg$ z6q^?TlKbd6E1RpbF<%#64`xGO+n0T#cgS8ke)D9|*P#Cit4FHBT9rFAKLgpF2((~-Bn2{C6arA_v3?r0&=l1MJC?s9EEZtE#c#<)C~?uyFti#AmJ#F-_N@KzU5)#K3~yn9{$2}9|QMQA4^RA`K9 zN8?_dQw5frs4m~Ay-MJjBbsgQ1|}ToQ6*JuM6!|2C;`wGZQzSgsUv*?*8IXuCLO&Ve!qPlQQ6ikb6YXplPA;q=(X)pbK6ECoL}B9N^BcU>%i&Olr07?YLW!9&I|AJpJqB%UAU` z;^8TM=CAypvcuITU0eUE&u4IqNY#vphkXtk2|9@EWx;z>0j?lpwXn=fcdv*@SWgr; z80%6e;8W~nbj(c5>2Q2?W8Ka)vYSItNAiecqT)6FbC&dctFc_z?YdjI~1MA+n1^XKd6r_vO!oAc?0!leD{= z;*9s(1rj|*zS+SKe!G_+{I>Ih-|m5Ns|Ul%ra;`{dwLzx_mU+>g8O!aARl(G zm3SQQAH37Jhh9ktQ`lmD9!a0#A{xiIE*iN@O^#aKocBrkIJarO57B6WOUY3Xr}gs zD_E;lZ+;_o^u!7WABGu0<3iP;79f694xv435|pc)*m~fcg0V^H&j?EM3>*kc^gZIs zu#OAJ)!z4gA=XP|Oxgan7#mq*&DLG^nK%*4`GL3AQZq3Jfl_c>kSF3s#rI`ljZ39n ztzN#Tn2#?D_WFw|;^tFsBU4oIAlF02lAyE>2x|;loZL&C1q@cHz|e?CiW4O8e8ZM) zs8|+f)cdBE&T8RKhqQ<={m%7;@rg*Su_rMlHjAWj(sUCKy@;)jN=;Q92-&u{N!{*` zTo4R{ahYmeUqeoX?YV8w$%$Yzf&9|IPX=%nW%Du|f&FZvHVH{)dm-+D76oSjb$@W% z=-1~ZcxbQj({CAKrwLFL#k0|*b6+%s#2cpuDmr@tGGq}Ai$D;y4mlf}IAGW~vkld_ z>0iMm#y2-yVzn;UFldR}SDGxaPC2V(OdzCnRdJx0^<}hoX4-5r?vzqm3Wg>!##Qwk zuBUV^6x`h`N1~iCT>PN^maJ38TI)55S_wcCaEHJss+|FKV`Ty* z69{}2iIpWQ*Ou3O6~IW1C?7HR<8Z3z`hj^2M1HkCZ>1tuC8y9 z1J?gIX8zS(*;6Zad*pE(QE<7)524M=C$N4k;0kYYUw80fKXv-ZW&ivTD&Ei&++oVA zn!JAx-7!zoNBjLypJ`4b+dXGojb^>0X##3+@c4OWm_0n0A{pa?cQ85fvCof6%$J&l za6q`DA9UPJ-Yo1ej~?AsPz}!UrPkF2lGsI00=NUob(1QjTFXwW%I;XNmAf`s^&nZG zPKYyI!fHNo_Hk*9u>`sI6`wJfHpRr(>9Tzf zHLK0X*Y7|=Us$~Fh^!}Mt8b%m-}Tu#fem4>)D=MYfkTlK0kq0Lb*j1mW%bKksvrEu zBH%bz5p=eOD}*U;?Gk00oB&Hi*PIAg11E8F3r6uy@TIEt0mo=X5V-j3^*-3NOY15k zzXNs9BX>+MXZ947L#Gy2P3iC|3F9++vgt}1jMcHva_Qo5MGQov@s#2|8c#hWMa_vy zdJC`i61M$9=umr`A>XX+izm{(F_@~*IrLD@O>aO!!WS|6OG7)O8o!O_NSmxgecd0- z<5U7I6m5)t@sI)|RGaN5^T3Xp8waDyCkz)loNugQS}1wz8uFBdr|!x97x-d`Dc22xHRN^3gk4*2HPiiiffWo3kOnH}SD=&$y%5#)Nt zbHd6fB4-}2@7_fQ(>kK99DICs57nqbL zIORNl%;iQxC&XhwssrvSLY#P|3v*Tys}^~OXh+5uxR(!_H8t8bEp{*F*5U*PAs*>! zqda+*n-i&FbD5|axAtNO{z26m!$smL;2um?8n*@4;8H|kx!(=W1qan{5fKZnJff+sYtO${Vr(v(OJ`;m z&rM&NS)4n6;rznVbNg6#BWLSlhT~Hg*H&dAgxtY6VYApnWt{enUD?R&ZKzi94V2gS zY?{s&3$_QRiW4Zq3z755NEoBBicAnbZT6>I;lTFlK`M5;g@U%$IwT)eR#x?W@1+T0 zlqI6ZeEO>Rt|@0%Rp729vozwL_3p*vU9;E9hZm&3X{XOaB+DxwmInep)&1#QaoXPg zPU{|bdgY(*VqWO6_|Qw&+DH6bK-vQW&-ecplSJ=85z(_h-=FN30L}V40j;J0`V;qj zS9NREKb4Dp5mUPW;w81cY_1CiY8-q_Bf{YI)sftqJlX8NSDkMMYM_!THp-T27Z#- zL&|}V`XKiY(ZV8Z6-$m3mjilCkuh%I!XCA@M*Upoo@Kd5ErBoe+OBLv3Q?FB7$Fs_ zfkT!gY7vrrWOLdFH}wrWASeR9m2(#B1e`W;%%Yg1|4}Vtajp)|k7$~VBZ9m2M_JV5 zfFgSR<1~GG0d{yxg}zH7OmBzlk-%4Vme9^k>o47(ey-r_oe_z#T5$rnvcH;Nzi~y~ z=VX$i23CnU*DxhAW^`&x-z&pu(0gXKhRoQNDr5uXD56m;X$FFrZq7a3h;6UW;7%9Q z%{^(!yEr7TSO+nD(Nsc-&Eb6W?ArcYrzNN#R9T?QN52yGPOQ5t9WCX#KW@DEBPBY0y$t&^{@KBxy zgyWMH+iF$hUEdFHG+ppy?JPxxMp z47Taz;x*t;_CZ0vofA%J-xLfmEc(>mE%5_6(JT{F9HKO5nh3n;rniGqZ|=D5bY}yF85ggaA*>3#wTp8`u0h`_Z+LvJ zW>bOCy3idS%oWh8;O|4)P-sT(lWdi2GoKU;Sq;Sa$?SD!~F@x!Eo>#L`28Qd|4 z@}Y`uI$Z;}vCeH|s=LnMf#PKeR-W2v>~OsAFO+v!j=YWuAd$YJv!bE7IYo5DF!)N* z>a8_6;=#oSX~99d53>Ave;*n6q~auH-d5{4g|asmnp@TUco$t6i*$p0`7XPW0&v(- zIFrlzv6q&K2SI4MVapGrcCbj0hYg%9R9|yJXI4;IqbsXwn$u~zY7vYDwl-=bX*1@7 zwHCq5=ntB_(VxZls*4hj8)?Gv^7%~maXzDx>BCCzijwI?p(6vhJQd1ynXvaK997w& z>!TrUEO3SdEX5Bb+4?#5RI#dyLKC-+73!kEcKZl!6^s}7b#hXQ74p=|M+fK}#K&KeW6`nz)YBAWsc+Q{|__p5&o;MDkDaWO7A~gbhrOX$YLxcJG8@BZ zw>LiqI;F|z3?_+fOTA=akiEO{O$jHyP?Wg?2`O0Cj{9@{JNRqdDucdRee)6djjwLoQ z2g`Aze8vSPKkD7jRL9&3As@xnr$c71r)C8u5|h!(Sx64R$D76Q_#8pPrO;G(&+zI} z0|T7c(SuoQ_HO+SF$`LR?c{Yq&0)82Cr{O(RuF^ko>Io zU=xO@7f1#mai*_poC)3XEKyNE8`smH_hY`^dz6$?mg|k5gEghkIP9(6^{bm}J>DR> zu3)uT%WTG+NL)%e0ND-o^WOmX`*!E@d){-;js~hFVKG5x7mz>Wb9=h5!PLT5!rcaW z7eqH#jgYEeu9%@8?!mthAzu#n4j(L=k})#|yEkiDc}UF@eI(9LH&pzXdy#ud%vsPf zYJAdcaI@q}iTd$yslp!LRxf@z8d0y6Q?SPaR;jer(ES6l(YOSCTk1pSH(;~4?X16I zPmwtb!HTDsSCMXH|AjeTi_CE_3D-<~w%ODtFElp-;N^LUPV#d+u^{0NOUC2s#zoE$+yZRt zwOPb6*haxOgtA{q^UrLTq#ux)b@NuuZStB`hAA+d{!}Veij2kULvFWtl1UQ6Li-DJ zZ4AR$K7Q0pyayF=s0pNIYVZ)z|5XMu@PICzThOKfQ+8FDENw*~>+f1_ogZ+5Az^fKXo&a7%ME_E34E0-HkOy4xdlOdB zS4k&nAmeI5{8QbZOtE6rx`leiy}JaVEDAd!mBYN~*E~@92hGL+nl+CqPzy{ZG@7^3 z%X!radVRzebb4od1E;&cRTsJH%h!VX7TvYfcp~tKx#og(l`jDMh{|D2YmUjWzGn;| zHU;sC%lIp>e*Z0JF+DSh2i>0cg0+;u*()L8VuNH+=7X3`fye+tDj!Zc-AjOqOzM`g zaC4V*W|q)PRzQ-T8IXM-@DRa{*)Bs(BgcSJ3j0CbxdSwpnl%|Pzn_6D^0k`rnC()? zF6Q$LJuz8=i--!vd=MF$LHDiq!8MEv+L1|);12E^;H_bJ==1&4KU!B%IVFDzrWWI3dUL@ot!u{t0h`%rLm3kO7r-hzj%FFYupA*f<(ThzGPG;MTC zwK-b^k-P978q3~ee$&6=D016w?L*{!=XY1+@2<$-U6B;d`P~)yyDQR(Z&3aG?uv}E z&Hvq3+Ec))v zYe=V$rPInrSVO;x1f!r{L>0u2Ah1}-DUSB$*bgHtuJMJCJ`7$-N&mzFHr=B?D{wip zNuER1a7NVhky0CJ)t)QNP%`MRGZG0@EQsLj=Jw6vI0uw{Rjks@-^3{GILeL;BBn7a z4t9{d$qZ&MYwp_p8(7-M!=SSTSQ`*8BLw68h{5T9hz%taCg#v>^Ru)y4%&QY_Nvk; zm!jz)&X+pK_twB{sf(fzg(u?+O_bqCBi;=5em+bZCY0JZvT$mqhbx~W7nY~8mA6Fx zs)#jr*CZbr=s(xm!U3GBP>A2HMML%e_4VzU(?a^-PTJnR+-O&xF8(^Rxz6yC-qQ&p z3@8XWo-PFMZ_IKe_rM&_S90(xI9jJO9156zhK%1&qerjm#V0y-5##bG-`bugSF~HNEB_M5&H{%yMLr!yWFQVVjI`-G7NpxCub3-U8DZw$JZ=xE{V|l<1X&=#hKw< zy|)It%R-u*5W+q>keuw*|HS2;_4VcL-Ak6Y>Q0As3WU?02W?wajC6V+9m7ErLajs! zxYQ2GGnfi9YCWi&Ik`fR`pD_#D&5EwaIkOM^gcF5(B{~jly5w)l9_rk zju2=Th72=rLm8wh=8Rt_h@rXhA~DFSXQhjHZFxmpmAEt&G|}IDR-X*I`+yu!tJUx) z--DfkW-Op8=R{)>Aw!Ct$67!v;T4ps z>^jc7+;EhZK;8X!3rtG{fPeW@yY}qL3tISGw}&C7xk(2X$iLE>*&&3mKV*i<)R;lZ zo~xHtM#4x1Z?;upLDdJ$(1W#^dQA?K%wt$A5l%pBxFDLZQ~Ve_h}W069yQ6bM-e-! zd?%>7%|W8aCvq>>Zr}aB`9RJzkp6_z5c~pW&TQf;WAQyPuXvHvcUcva(cgwkK!`yN zYX0H0W7s4em%`NuvQAhu_79cH%1&CL*l0q@jQyjyh-^2>0-9It9O<@HPK#=i+N7Gy z?g6Q>^P~jWAB?I!VU(p|`b9bu9D$}|pbK{P?n&LjT$`NVW0vRg{fq{tZqu-3g50?7 z(VEJ-y*J>iGWJmdcrBQP<)gGSIaYJ4vh~n&?#YEcCO9rI{(n2Qr);k&^Yn6>T(R47 z@W62LjQutIi{!bknV0zM=vno2+0>i-I;P&4a9!k^$8Bo1TqoMX><*i6%y}0KaG{Q5 zY$ywJq$@`AWVqVl(UPkzYaetnWiQUfd=hO?6{=Ah>UY zdkJ8g#e0CwvxlZE3ioUoAl{YC=k?*aO=WN67MxR`20#`eA8(kWQ#j#W)-?|6ZPsWm zyR~TvVxWa=)mT9dW#Fph+oyV*-ag1-2=3fs<_&?-+gXBL_Ye(2qJ54aU!Tdo&A7Uw zAwe1pu)FIQ`p_hWGvJxHik$QFsge9B$AO65KuMuh=3sc96;PD$&IdgIc?y^bK8RpO%)0m6n^L*+W$wNJ|kFa`v&4W}pd5GnK z%-?k5T68SDX9i-p=jAwI=$)5&@8SUVb=QvzQKYd#=XD1LAZhekN$qsAeOBds5suR4 z08s>#M9(SmB??#(uixv-&Q@vv@OhC-I@D99!Q3!%|#MVvh`lQ6d8 zrq2(IOh=#UbZ3B@J63+kc{Uh{=p@qG86d7CevpUVJwK4fE~&SChpCtsT{ruX4~7?_ z^3*yP!^jywgX+vrZ#OTO?DSSz%*ewVP0`}jws^hu<*li}bDUn_!G-0m{>q2br1Ft; zASlOn2hz#-ZtBBhwT}?HvO7?z#b;4%e|&{DkEvX?RkB9U&zVbRzgm$MA$-* z({yWJ9YBpNoaM3AvifR7N*;)~u=M1s{5$!2uf?L=vY_kBx0bi6)N_vV+4z%v;|@3hJTgsd*R!omCxnR_w3p| z$n3Lf<&&iWdwkq2oDJSlI8&Uo3uitmGRSv?Y8^tANq*)iifYnA;>ly~n6$ z^N-qUKp@;c|Le-~iQ?#A zAlZnP7nHn!CQv4|%J=;*SELmY-ObjvXW8r=Wu_!Ia;LPcKhd4!r~%A~oi=bjyE z1LKG#s%Lu?Q6VePWe=@P{;;yDCjpi1uCvs?*nBWp@W!q0e?+N<*eqUZZg`JLc+adY zlT2cjDzoOulC4odDus4tszQr5W?xh_F1V4;pbbtQb$dkCgH*)YSz`0@O{uVQ$%O*<*pLpmKgQ*g1^Ku@g$rA|e2?dC}{PIw<_xVnN)c zz?Cmg5=Pb9qAFJMpF8Tw>Eg6E%-lLe35V&(-QF!zjCm#$gip}G$`dC4g{#UYAx44J zLb@K6pO~QS>{@Nrr-B+b1jer>V-C9#GnJ!!1k66HN8cXk--;dr*V;sRDKbJu3z{D@ zJF&-@BCDz#c&mCmIO3<(k+Y#`D;>^6+2LGIU}20y=CMgQWB3e3LL8wroaSBR8jPZ- zJz-xjTKbiau)yk>ZuHo;%t)2H|T`78aR<(a7KQ!bgdZex15Km;ILo{j zfy%ZilI89Szt;x(Vk|LgQJghKp-O~|D2H4B=$Tz;mibzwd*+G4*J^A)%npI3Lu#Y9aP&{(Rc?Oa|@rvfj}~AV%a6K_6HLHX>i65<8L(8P+KHB z%^q@=$&)VVuIR$7kJyJ6+<3_A_V~x*E7U;OkOeg=@7|n@P=_mjMV1m1Obx$j>Dv-~ zYTVmE->Cvep?1uDmH*NCA)XNy1et#$2>FS=zOzwRq10!v$MjX6w&SQ?V+-qD9BbUC zBdUskDZ|R*)ogCT-V;A*g*_ri{l?Ahovk*8DSeY!!TeBSrMc#@Z|$tVdW~E=mX0QP zU+|4x^x}2A$|?7d%kAq8WlnCktUyUvqVl%Ds8MsLrEs@?5ATcXH(J)b}N=g&GIg2tpR`-sQyuFwM7aj%hy853yTf(aJ(>i1F)v5e0%$#ia!|WdpcZ( z+yV_!a>(sSmbp-hQ3rpgRVllFp{$pM-zi7t!7%B*XoQ~QoxQoy;mehd#@CzII$k9| zG?nRv15X`D-W=%PV?sU8?+A`Le!@7pgrTlqZ73m-9tl#;6y2&1U$Rq(A^JvF8{vx~ zJ$_En5Ul3Ofj;u1cJ`>&1&eX*Bz{ylzT>S$b>Ie?76eIDD%uX4L}W~&9Z+o1Uu9=? z8Bel8;zv+cg^fJ#y~`7suc=MxASqjg3T{ca0a#_zJ=vLyabBGhvQ!+J_ti%GRs(0$ z$4p*Wx-|V{%HL;?96?rjq#>sYj#tGvrLXjyKrfV@|}o z=w=>)OR(rFLW#V+a0oXhTE0-W2B%Bi25G8g$1`XyYPDX2vMSj6Dh-xqf!cnGHRgS*q^~+R41D z$h?%MXNpPM_{Om5*RF0ZlYfhD1_ccjW2KF4c}M!Ul)P9yEEqB8WcrGeO>A? zY?9L98H7Uht-G^HZ}qkrj75Xi3yqc0+r=)U7|q7 zVUJIMKt0mlOx6 zDSto__xkt+?m?#EO%GL`4!>GlF$#xo07IS%LF~K9}>7W%VWrscs0HC8*O_*|Gh&`^o;w+5Ca>Zc zu&6|;Rr}YI`1%Ki7#Rn?np3jy#1qrep$`+VB6fa}&IcyE_!3UM%Dvg%^X&+uEutQ1 zY>%fkzPv0(13O$x|1CaBe!kZX`%f=lj}xRgvPw(tIb2m@`(AI8w{_6RlXrTIR65;J zKIkJIQmb7_eu?*i{&J=;sr>bH&4T;mMhcGn4PSOmMZ}xyTWgl_!w*YzVoEFjc*au2 z3hg}OBND;};HR{E2-$Z=4*MqkMH|9C7`b2eSdr~+VcO|XA#XHd7>9!zo-Z`bw?p?e z`D)SY0Bi&(ey>tSB#+52b+(JZqdLgHUi#1=#imEouN7wIEbZPYL(`*sEN`Z(lRIS( zt>PlXb&kBd&m^Jb6`i&u9eZTpJn-Ag1bQ$@4jt|9y{V;hVYgoY0^v*SnfQSAE;!=T z=JxxVm{Z@-m|rs7X%IYJ`1s=)lUv7*nP;Q%jslLrr2jrvr6Cr&w2xDG zbmIg@z9YV{1% zpSn&_uR-!OE9{*PmJo01GFOd+VHwl~Kbn3n+#JE>rF8g$Mu+eB z+b~UPv7GL2cOcv@&RD8zg!JF;NC%UeGET3;;{J9AB8l9Gi8PbyE#IO;R59NZbx(ET z&Tkueld*INZfWHvrBvfNLCfdTs7i&EP!>m>ElUqf+)h@JY@~rdvPJDS4yW@N@QAcr zxgiKoIfBEsjI?58aSjN|o#~;)MMnqD!K3Ng9q^W-r%E2DNb2~-zVR4miIt|@eI7cR z`6JDQN8y}?=`!CTD(tT94)&!^1XwrOBs~@^3nG!)a{ZB`>8Hba8;!@gdL zuzjq7`5v`rog!V@I@iH z3!{ssiAjV3-SJu2X(aUNl=`uQ;5y#Ixta{1?*3>V@GoY^wfp_J-bZJFm+wo5P>Z5e z^<=7w;lu8jKTh7?14tznCbIUE;(x;>35ajxR8EYycnvJMx6WfI0b2-VjY= zTrq!`hq1nm>LS@|>MD_Hy8{xCp; zaz+NJczN{&UAp3dRDj%}N3n}1@#NFB0LzBB74w;yssY$rUiT)&P8G~ThGZ|G-iIo4 zni3>&dvdKAD={B!Vppw(eNd{aAh08`w$khlh(hz3wi6|QIf29uLpNDrAVD&`qn02B zq8UdFbOC=Swl;p(V_E$u^O|Rbe5OFn6|tx0Lp3tc z-2$wzM}#ntP^c}_?;2d|pN=0$UD8lI6h67vjC*a*Sq=#-67JdvCCF;MnT{eW;+_Xu zdD;muEI3(74> zJB>vjl0$L#$cmkNE+or$8=`=01y-f#M^o(Fltjma7N9UrI*ff6rwaJ^3^6IO$CVR~ z*(VO;(vbavU-(U9LF1mWpc$}Wy?_Nx!-6+SP`@rL;OFyq0APvuMNVeC%*o8_aCGu8 zO3a`mLKv5OIJgOMIXLlPOf!~8hb?9y-lCfh!(b@2iD-qP&dpjKjz7R(ZVSdx&`hR+ zsp9o11OLHF$X9zW-PCZv!_$pA4wg#l7BXysW$+YQ_`|xBZagKHTjxdHnsK4rm61J* z_#mgi@Es6>1SW^D{1!i2aTf?oyjrthL@({OMVyLtL2sliyKTnF?p>J@#Y6_PzH=F0 zp^1to!~Mj1pfUf(l0VSp&{(SD0+_4EltMzutN%^5gd2bXrEye?iBX8RDH8~$=eXK1 zhvjSV1$fJX0|m&yQ56>nWHjG=tp)giJ(f62C0)J{yHK)fDvc5uO>Svy9ORpz2YExb z8g)5y1I3TK{;c9rD0$8dOu>*ERgU7sZ3H-}Piy7)ywEkCa9OW8Csn^YXR?z^8u(ze8?phE z&ZjXaLvxtBVuElutMltv$B;L^uNh?gQ((c(+5?W=fORI@wQUGS=e14MSFK~$gE!q= z)#9X{<0xD5bNj0Ffi>fcn7B`^y#!fst3(+27fm449t*>V&)n!F%o4I8p=7*U@V>0i ztHbm11pa7TIhyW+?g($w(bqz!3^L5-X?+z=TJgJzBVzVlJfMYd{|pQ~d2wqWyRv)F zRC&U$=EkA?YW~F0{+Wz#^H{SN8l}C>XfGUobOB2$&xWfgV&!u1&YH>Ub6?^yb z?4eGuNiYsGprq74vS8ZNU_)ik=Heqi5XP^2?2_9{4pwAvwV)z1Xd<9ZWTWUf;xv|L z@-s?S!v|Sxey<|yhd8t500X%zh@i+;?WX)?8>^4hph>gK+iSbfCwCgL3PCvvWraWS z7+>3!oeNszX~Xg9mhjpJ4HK^Qtx>?=q+`{-){ zPHVjp5GF^W`zzF>14K7Tm*?9sldaBRTgriA!WM<5G0CzSlG`IjMaRHxBpB0AJAHQG%T^^niz zOZPmR7^RBALvq0a%=zcSn(kctG6|%DW_&Oxv8dsV><0bw(>L{3v`i4 zHnG7v2PzH{`sP>_XR>^qjPn^^M$?X(cG6UzQVV6FHo7OVb zRLGDQ(GJbF*d$0PB6VK~$mG941kJ7J$k( zp+T8}(!7sUzGU-uMm?ZgkHPCpf$SE))|YZ%aiX5HY{m70iwcvA7F1E3t4e z>PH(_TYPY3drhh=C!V7JxBPkS%B8tdS9yDH+mkECw>C(@qJG5WYLVr1FP7MEgA z>RVx)4{dI)#xeezA_1h?Boo?SizxIM#rLarozJK?8QPP(CiA#5uHYPOBGH>4S0~wJ z#iv%KPI%*PWbw0UC`hb@POccQnp1cdSr-3lGGIiw{u63#LkZDj;p_|qehQIp+_b=A05vldO)Fm zukz{qr9^4oX4gY-+MM)DvlZFE->>|Ac#?@l?7u7FC5&b_k8xI!qU?hi`0~-dXT$X! zsEcfxTbr9}tC*HRUSXH)q3yPG6q7p}SJ5PJ1uWw4+WP9z^q+(JFD68pn%!PD zrZ-c}*=TqwJn@9lq3}H%!B=~81p7eIte?_`zGe3XpXi7zv_{#t}=_fCOE?Vn`_kjD5g=K#GX!@*% zvDnS24%fXQnGhP^KAfUl-dvNR0on9hdrupUTAWdsv8oZv8R!Rx(~n1^Ggy8V{fzBJ zI5_85F*&FG@a@xmSujs%$BQuqQCx2!MQj?B+Ir`5+xk z#MGE7+or@HtD%d6syejBU~kbbGBpNRzje6RkP3-milI#zbfDF%CCl)ehr7(%Z&EWE z<@mSv2B$&YHl~tmF)fa8B(ouv6`WOhNcT=qGZGH%=63J*$0d}kGblK)Gtv$#=u0$_`F?s#hrH|hWdhIuQ5Xd)r5XkQxg+l%S3CsR<;D<*O z{*m_tFWx-bzt<&H61=nr>6gZ}Zmv^zs<+JWqXI``^+aupSFmn5)8^*QbNa-uvh2f@ z#1wK+v^yzsLYZ81F311uivyKM(rGSuA*hd(@rFK3?Ys#yh;Ecw;3gJ#iVPA%3fDHT z6FqJ|#Pw4Vud?0`k{A!I(3xfc! zirpQ>o28MC=~ z-igDNBgF}YL47*RjU2};-a|mhoNc0&DWN%aHYZO>F>^*ts!0p0+sn6bBR~|qkRse= zL0WPlutiBNg?99Y8g7K3Mz6sZ*Qma;#1v5^3oPEu1-Q2Lo%Pe8E=WMwf6_N}!j=#c zM`qZy-oP_leIovOZA!k;K81+FC?Z-6xmZ-&?f2P@#wAko!7Mk+C$7 zJl<$O@kI9FBE#aVy|KDPu;-1&bE&kp05hAP$>ouKWXbs@TX5JgwDU9Q(CeDYjgUhU z9KVH%uetHx(0vFzOZD-KgpFL|`K4y-Chi!I7@Y77i7`cG<+;a5Aa93_mP?1eMr+Wz z8GK}rpfn{nki^o3$^CuLrPF={W1&9I&Yv&M)7Ds}C!Uze3LP#zr-LO9m#9t)W6OL! z^lLL5236eT^>_UI`GHZKqVOjNm3E+Tek_>oNf-D@3-EwvQu>u4zW|DTxJgjV_Qmad z0l0;HAPogamkE0V#li^T_{hTJI08|<$@f~zRc~nof^*MtLClU2G+hr7Ya7Va?s5X; zB6-D9W(FifSm|(i*Fq{b(@aRFLXU8a>>bj&%#s?0W_*!pK>mCQ<29*2O`3^sX}t8@$PC_IBp-R9QAyt7 z#?5|uaLRl79!o7LiwCG|t{8(Z>m&rcM>M3)F>uavY2e-GsXV|+V2Fbtfi3)>@=o5P}09rI5mADXB@P!OHGp4Q=G44ou^R`3{JRB8ZzkEVBd_Mu=(-;gbfB zE6AJF3lk6herRnI6D>=!-hV%&&WP5_{L^f|uiFP2z^HoOqE2jBoU|iuUK?PTdyY+r z;ht9&TNA?IDT3>lONa{9?8Sl?Vdo)8#o6a#R=&5%LqQ2>uF)iDSPkdG=vF?_@RYb` zuwM3}4Q3XARiBd+fbr(}Y7$QmMN6X`poBFj&JYwdM!o*DG8biFaFLVTO38nyd=9t3 z;v_=40^P8tZB=5WF%g%TJMTCa6Ml*{%#Gd93vvZ1JCG$z@b)BpdL=f^R4jqYq0B&| z(f*Si%MDeZ)7>hcB5{e#NC>9Oz+H^^;EEJT1tKx|Qc?FyU?4ijKHSR5PKK-RmqxTWG6JXMI6Ir;*LO+>G{zCJ*&|)?D!{|Av z9e^dzykA9z@Ivndr}dTc+)v) z7E>M~*PCuG%_WtOqrkZ-1ml~?)!(9hfZ#r~esA4X;Oe|#(&IM((=7Vi>o+bdJrp@9 zGa(eAt}K+LNl*DH=^^zbRtBcA%FW!m;ZZ7hOkmC?xLO9IWFhxG!O)QH)1hR_FqBzM zI|3)DUVfDOXGEXb-%2pn`BIp~U4pTG{JUT*O66rbTPZj88!hvHQ83nz_e9-Om$EoQ z@vlhKF+%EBizK`&^8PLuOZT_KI>wHnZ}woU^S`lRte^JQ!QCP6#&<*J__xvcMc;>? z^}w5w)-1X${Vw>WQTtu+3!MD#f?tsAO;T~S?f+T9FF!xp7vo2ij;N6O{)I+J&~?eo zs%X#Cs+tfoTfL<0Rmt0?QYQ!{c_;i;O@4XLRHmDkZyA~8q|n=534ia-GuOlqC%9NefBp(b_ zXx>02tguy&u^~tbWdTsEnA%`kjZ~(BBUW46T5NCLY$&;%T7`FH&)B=u(wQ);)w8F< z&e`Yl%gt@h#CPW7UfHb3qS4G^3NuN^gnJY0%^_pf;vjJ)CgcNgI!et($y)Vu=J<+& zw~2f2wACZ!s1N~Q4`M|s1b@#8ZaMIlyxql(#vCZMz_y`!C&Z6v)V}j+K1J)TpffC0 zMCGuRQc&A`6z)ohW-uiDi7oPvTT_@5)P0yuJ&aH0ug>u+|69htfymw;2aK+xG{czuT3k#S%@Lcf)-FO3*q-wp4GXu z7Bban&;7!yMQaK^(F<4tGxLaR9Plsvq@gJ&F@s_l55AeE+o&3C!wrV|v5KdbaC>x+c7~*~5D73$Ctg5vT1Q#u&bw(yw<(O2Y#b#^dVW}ordiH%`S6G9xS{oi(ZJ zB9VY_uerSzHQ}lgkosCNMrn>ZtYori?}8K3^Bt|+F1{waPI#|Pqd|Q_X=t;ru;IKY zcT5y!jG8wtQ>RgV6DU~~Y;`JBZu7ZFq?m{_>88-TM&7z66zD9q@>GRku<|cwx)v!L zQ)!{u?a7o)DZgw3*7G4;S>JPre^>dtIFn4N@83sFVt&UOS#L41EZgV&a{ET|p2|Pi zp^X;BtCSl`5ov4^8h)8EQOKzKTNaUkuujA@rVZ;M&W_H4MZ@e zJ+|{HWw9Vdp@*^h%WM!a`H>AWzR-kxDp4?9b6Sx-hOZBJ?(AM)Tj&gOv21DVQE(Hf zkI+LH`k;GmXN%q0IBCd~@oV8?Te9{`^|3H>lI^V`Kpe8WtM}J&?)0G6W9am;Qu%qn zvmj(duTMg~B#ZN~hNBfQ$dgxf+d^lJXfoKuV z=vjzHPH%4Byex61GI7CQgp_7g0>p=|GI{vsk$FDwXtlH-gJ1c{kn#BCXQ$xpS9cdT z8t_7#lkL{S$FlrJPi7CNN<*4UXq=t(IZpo9Kb;_1rINvhAIZbViW!O?Io9RE`N%PI z;e2F|C6DIgcdwZgJH&UsP)*Cw2>CL*9w(3PF-TPq>#K^skoW<#*wUX0x*w5ogu7|u zdNsB8I8JAe7PZjq`Is?Yv2sCZJY)~EHdWb&^Ita@>BO=0l>!x0we0g7?aeFid+$`7 z2$|p*NQ(Ru_e_GiwF@VY_4h=sZW1&y#sRp*92E|WJ0{`@aEjOr&e)3#;RzKMaqx(h z3`i1a<$1HcDStFEJe;>ccMwgFH_XVT)b#2cqVs7R#J|!Qe3H37IYSMh2&f2^F;i)< zKCRF;pd<$CLU^xJ#8ZJv%*VN)o$%nB_>m}RBoO5p2MSSMiA8V?2b$%2i)?(B=#JM3%P|bG@Ov+Taw!i2)BB3imp$b&^J4z96Jw z8Lj%#LjLuYjj$5i#luwq#@ynH`Lj6EPO))4c_zKQL-joKa{)5UEaq<~%oJu$6T7~% zjG~}ThDT!F6p_)|TqAM=_GF!~^%v0nkr;fVy#qZ}0iSKzG&xr7?Omo^A2aC9FRP4D za$mo>h#1_2BU|>^FlIw_xr`KwSPP>vuQN0660?_xLi3g02X%-XDoITwmrCnJcHTI_ zaD{}Op}5d?gQ9#ksX0`BP(l2E$ZHqO(FkvkrPD0f92#c`kv1 zXGdb1NM(YBO^a_dHy8W%!CyUgEQ^e6-=;PRFzP;})!O-10AJ!2yyF$+(4&4HF>C%e zr)xnhVQj%}8-$h5Nq-rJ9Pqn0wkd>&+Y$>0!M`PLJWFAFciqtA$FlsLt>tSJ?j;9q z_o9v;pS8#pa=TXkN;6j5lWHrdkH#YkKWx#sCpaAnbXlzr&)$aqWW38=KeN{6l?XqD zFPP;RKVZJC39}I!zM_gps4Pu{(^R3Bkke7lNd`cX(12?Ud_c`3PEVaN zX6F+AAUIAdTgV|5e$ske`yeFM=bSN|0@P^L3T8~4F~^^kP`Bf7|%!tnWwIkP_vZ*$=B`;ysv?(O_B@9jdNxAVucJY83}QIT%D zpfxq8Epj8pd1$%fCWg%D1e2$HoUJt??cj-!ylxUpw!nXoG?NRO-1%e4Q@y(#7oWL9 zauLOpC8UuiaEgqC#_3@K8A0zJnamlfuVG3pkS#Dpr}X{;L!E9S#V{RWg4{*)F0Gct zA~6h}XmW&G6z#SZlvm@WP

iuJm5gcH8Y{G=Up~{5_VaLl+=S|h5;Lx%e{x|Zb=%r;BQ+=K5$=JI};EbcP##Ps#8fG=S|E6tI)JY@9xJh83qJIEJmgk zQjYaU@vVRcLz}m76Yd>;R#iI8UR#(nIqL-fV5(g2Js-*lqG@Qt(*dXe+2X7eixttl zuvw&ZTi$IzT=KR_@_g?hOkI9HoIT;g@jy0qj-^klf@5Pff;6WY!%j2q&?MPmZHFF6 zc6*QdG<z|zdP zdIp{-FW#+JP0NV8GzXns;=`;c^lj@pz5w zDu^&XS7lKMAIOWob+bP5kP4e6s9+l#*6N!){=GpS5vR<4O-ATwPPf z@;T@*en2YZY7KE0xGJco4uYu0P!mo=@EK72jJSf9(ypSm54n`Z(ttYHg=u!j-o84t-?I@H4`d=3RG**Hp?m8)>3H8 zK5d2+SI0tF;nvPJB0P!wf-Dl8v-LF{wOO#ihsYnLb6}~#+1w6YIU91-HYwX!tB~f} zB~|sxstR2)S;|wGV!blcNemSURwYP-OI+T{v|Uj!EA7t&>rtg5@!Rsi&>D`2i(j1jhEiK_+FbwiR1v>Lw_icVAy9ObbFZYoJh&(W7apidrwo z*ltkq(Y8Xdk)-yK3yOi6!{KmUt)I&eaz^X^sPZqR{(&2JELH{P$|((y2!?QVU2?g3 z`V5%cY|n>V1af>Cw~Mu8r0<#3v!7~TWjjJntx>vU9os(HW^WQe3P5stkFlM8{Ki53 z%HGBD$<{%sneC1gB;v$sHI z_`Rq{&GnV$VK)`T^aackEwmx1U*Df%56zfB3kAaq#{$}EAQBq;0xfhTCJ_3+r6jtER&;hl-)p^#NEV!%08C8d5?X z58B+H9_Y;J!YR6DYP_*vu;qlVHl)Rh>5gWuHaMeAEWwMxZ!^3}zT7z_96GGx=vW+I zUY#TkLB1ak09D}_$hO5fhS)m8Z8JzJe?uCMR3b#E=I zE<<5=lW+V6Q1e@RLDo|LOSm?>9!xMsT~EE$mrgdxTfHsiuH_|*z?oFsDca-mn8>Tqsfy39sX;c5u7;(SX>0kIqN3a3XAH1i6yHI555&ET*FLn|fd zPiE3w2`G&$c>^n(Joa=8T}M#x#3_@JxcP04a_{k!3<2Ddc+z=>75Rp`vtLH%yv!`M z*S86szY2d5*)B+)2}tI&YKG=7TvOfbNEL%rNaAtKJ5b(mvg>nlwPGz8uG%>E8nZ*L znn#LrQORd4pSqC90x8bFFsi-NjGTxPN$P*3FMifG` zaMGOh(pUM%*o$HY_TSZ*VTQSdw9qoZYhiAeX1`dVwuQy{>ZbYbOM=iELp}oVPi&XN zq&7W&KC@)jRmW=+qs8@rIQQdpdwzqO&v*Z}=IA5r_B7@a#WTrCL|!@@9~AVID1ymzSQC^Mg(Sik?++ps!&XgUOSNLNd^Y6dGPaqIB;U-~@_oND&p% zXTWvC^b1)sNYWz6>Ho~v_DZOYQi*cN)N?dWoNMGdG*5WG8_qpyJfF~#LP`Mk+0AXG zwE{p#E7NI_R&av-4c#+b99n`a-_>j94S$1?*Je!adh+_gnP#e_sD4=3+-k10aQt=G zwVbPM+l#`E`wRlduxMOyYbhwQocVRKlV@3vyNYt-`_tJp3%;a^K#4p7;&@PR1j+Q- zxC;&RNpywkkCGN7-g!^|cM82(*#r5n>aA#?L~a90{Gguvy=j?(6jXveTdUp4h;d;T zu+%wDtl)AlnnX600S=jez~ygoIEbN`S&JxQi5!5s=)Hn4II zzZOO)2`JeLTZJG)UMO6i^~S@}Yv3q7^fQlRUc3T?dZGRk$Nl ztB&uT>-`{6qDpp;F**0Y;1An5+^TlAWgRew40O0iF0eJNdhMeH)LKQav(jGMeJ+K# zzR+B*U79~5nR@Bs%_UASXU1GVyemkKNF5_6&z?cpFmyY2c47>!Q@>b^6Z%_L8S8`y zw;SvW1PXu$al%xd@(#y;3IhkRG}@3@aW?r6Ro50q>Q}gV1U%7YX3mr>acJOoq}10e zbQFoGBbiydRZqwX;<_N_{KOM3sC~dbC6O8~1y{3+?RcHKn2TioC0 zE}qWrw|UWk<_htET>Dw)4_7QMFasHAMUNI~`UGr{3JECrv*=IJ<8D*vqeTww3IQMWI>v%Yte&Bqd?%=y}>-6qb zkwey+|2l1_dHpu*WynXO&?s@A8$_All;WIbEhMGM7A=)zOa7PND~hs}CG`pl3O!k-AQOgC=qqjzei)-x0M-nt zC;88$vO6E2)ohPM8SOIR5$SK2ME4Z(VAi@)lLP6fzC>Ph~ie4M;l3>_B}PcnaJ zKJt%E&8-E6=Jok7yKuTM_|(S;h=~)Vb6vRE#Djzz54>iD8b8`vKZGj=CT2TkiXnlJ zo^+F<#F~~r5_e)u#On6$Nv^?k4et)>0Qa6uzXuI=6j%oYo>F#RsZy)Pla$G|5 zW(n1BQREs`9Fx0ajs~L3E(Ja45GpYFKaLlJ+^Dwh@>r)9F!}CXhZvhyhjR_;R=8@d zn)zXMj4|cuyR)fwD}xtsaaI;~mZ8JpvAM18b`v3c-lAot>w2HOc86JaEAGaXTw*$e zRweG`6AuIQN0$>OI8i}PFW8%_<)U>G9!N09Md&-w0~0HCqO}#EX|F!~m}CXpJmd6C z9vq2&?;reOt?)EJ){Y%+1I6 zPU;b$tJB-H$4*F*pf+*WM9FpAj(zRDJ>RYGthJl?<76LT`31a1ajg>KTq2CKJF_UX zd8TCs%R$eL#}BhSn_HHx9Dngo(!U5;2KP>wD9c}f08;(o%94EmVC~F6MZ!$1csF~A z$JTw77cHr(_;^p~0ny?gNcjdr8au0u9JthY{y9rWhu6+OWlxy5XM*&^i4YhrgcZ2J zYz^X|!c4pJHXf;bGcH10<+kq17Yy`Eg z*2)@PNP_r4<&}#O%!VC#HVTtzIRHWYzdM2YULNA+c0A8^*_xCeg@$izTjGt9n`uUh z;!}cb!*@luVKJKTFc7Fri-W;$)TG(q?QDOJTC)GE&zUf*yklx?{x)@&z!@d434pdcEOMEP-Qx)oJ}g1lFCfdcXkV(OhOz-&nmpy z*u*6HVQzvhp-(pnA64R1r!fo4*V3Xl52iD~bml8>xMC>GaZe&cryC{>alr{=q3_>^ zzUL=cx|&?Y#d%)l#B7{2Lc}-7{lvk~6nb%-7jeHsKio8(9r-HirwUyex?-!jQ%^ek zon09u>o-im_VP?QBR{M78Js}>8mcdxZ5A9}1nwzt4sRw%5K!;rX#f zeZ*&R2B*c48L2cRm#|2tG5@HdB)t=3e#r_tT;f#fyFO?4{NQ+AW2j45Y_S8&dw>FJ zRkU+sR?>mt$C02>ke8S%>O{T$Y%*&hrkGYAptTRf{hP=77XpW8`_`h|jaIu|Is!4v zUnS;LI%|z|2>xeYfy=)%!5vt{%lDQ!Ksh^FQ$@G%?S5QD-a6Jl8D<9yvAo#6U7D8J z$A?vh7Zcy+HD>eiDBB`SBE6291yrtamh$yPeK_Du8ay89)cqoYRKeY|!Cf!+N*Ca~ z2hJRFn^a562Y#YH#4#gA_(1S%L&mSSh4eY#2I2s?tUfzNThr0$H#tl-_P%R`@VPX`{jLU-%pSI?AW2cw~r-1 zK2{wM)O;5^@A_yq#nxmtH?tQ{;ECl^mC$6eMUwn<@6X3AO~#^^ULCW97#?RFKKn}3w;=DTd29{nC5`kg+@AM`L9pqE?d*~SQn_Z~Kh#|P`LXm|C=n&e zM^DeM;-2yIQc8fIbY%qii6sPhd+(J^`kGYlOrpz#FYA*33afkPSalSKji8r3j=&@l zddP_3kRe##u<}1Nw$oP+K*(Sfr72H;c}$@$1m^SRRXjnwO_FL~f-?PGTv?Q~B>RXp z{j%q9a|l;TL<0MgeU4LSc$R%%Jsu?H^sNI`ZEm`IU_=oyc1F|34h^a4atb0RF%#he)Eqe)#N+vGiU3h*&R}NZoxyl@>_8kun zE|V9xmT?`pz`>IU4)P|G>2a-iXOg8JsTMOj9IAHh-=V5);6Sy%%%@Dg&}`gV+-_NUJwC)*8GewYk0iA#+Zk`I(Pog^~g zK_=37@umX^51{SsO7fu3oj21A=-;4rW&p! z165F=yo@v%Is?>KRBiX6(kwsCEFX@u{4^bfSw8Hue7M?Q*v$C?*;+KpO9=APLay)m zi2RUw7UtO(X;vk9q?$(Ed!aqU9S`)ek~~V2@T1rSJb2{pv*^`%I$?;bK?02csp8#o zAPLvQ!6d($DoGETe<+y=mOpg-E&R_=Qj%zV_zXPG=6Z#r*|#&9+?P;ZYZc!j&=ZSB z(wE?e5`Gw1+*SHWGD0(*BYXhwPZn?ijJ50A6p1Gd|4Nf^i=^Y)=uZI8UALkq`1;3P zbe|(l%MR>yGWX;r8ZgaZHB1a<55Sby1E!p)_KkD=n(;`E$HOVD7AG*R>L{M9reDaa zGsYI@F^aV17qdN93x||bda_!sn)tm6RTd{X%8!9<_OAnvR}=ohb?AGQ$>jYV6M*hR zxZ)Vv4bGZHkZshfPu7@!_!jY@Mj49n1K@-QhY@z{ksm0Ms-D zx1A>-4tZ`9-5@ZmLC{Pypo2DDI%u=i;z^s;g3Cv3_U`jIW&2f_j6$GMVG_5+p&L)< z3a{qF(Am~3(MC@UL~Er5{B;GV#NG8Ua(Q-3*XPl%|nlqbc9Z} zh~~!<9AKWDulAnh;2Ho7 zP%o5#`jmirC6ceF3P8QW-%yUH?mB}>gK0Jm`G}5Sp>xN-=nW{FU5Fv?-+AAAljO=> z`#lxdwY=xFKPNO=-KMj{X+nU4V~vwdR{2cn4juEmkBJgP({b@V2RxjgfyYQ+?7H_} zG%zfeT0PT!JwD^$YE=7X?83z-5+SU$h(}&y<~098ILy;XC<)O-Yi+Z=h#ki0+IaBV z9mg?ajyX(!(ZsZW&KW)X0N3U&1$+@&X`Z! zLRI3tM?qXXL)GDH2gtOp3iOa3jaWf-m(XfGd0pBC(Gk{uhcUfR*;yLa??-_hrL zE&EwVs1G-GEu^(|qq*g{*nwO-)&5g~8)rssS+827kQ;nqaQtpfGADcJgt)(QDc!9` ztrA+ZSvkzX0Tbq%&I99dTt&suw|3C%kEX-Et=J&+JP@hkbF^&cV;B92KT}Pgz3h)> z*e)D^rh<+L%$P9mhQ*MNr@$Mg!`pUG>{e5hOzALYSJ=zXR@0>*2MGBJQ&mc<-4d1g zgs9Aoz{!20KxDuiXv`<>3?(zex-}@AU{Yb2N6M$r@A(V2`ucrZn0oT5Y9{w*8?d+o z-SiGhU+X@;ipnj}tmHFyp4Ma;l7*B7X&+pyG%QlY#qES154+S=ddl)YTf)Sbx-s!3 z$3*?-zs%aN`r=vlq zgtQG;Fzt45$x>e}&GYNUd49cXo?pN7JQsu~jvR$)Dhgw@;m8v)HyzfMzQXfGv~a{s zWNk1g;Kd?3QpEFblxBHC0p8z=9L9^#cgcM?qu?m> zN+GpOAie1**%yXgaZ#YhLufEst)+A#etky2xwS)#(&Dv7F>_4v?R`vW^lSrfDS5lK z3Z#aenLvr^dI5F&-D=tnWI59p@MT8lA@ox46!+`9CHnJPH~o3dH|n*0%o5QcOol;= z^XyOMV%F$bd3v1qq|Rq^%T}^YwrgdnY>)rEx-RG85)&(1ww?^aI?zr){l1VQ&+Lf`4r^3SZkYv}<7bLUmee2!+^pq~cLL1=S z*IXS(p%7cqi_Y>7dk!{r7JmIBnS4C1;IobMb8;vdC#wEh)Zw6An3;#4ZOFsfP*Ps`_a$*a zwUy%FHd+QW2)2W#fy9RujP63|5elJ|Rytl>>3G*le_U$(Q_$VM83>aXk-_B1U>69EUjQC}8DOO&H&M{F_*%K|IoPji4r33_e$;c^KE$=>KSR*`0yz(Z<4Px<)a=;)<3V3NMk-2U=j# zXsa*n$0!*%;j|y4dE|yGJz)f1eo466v|S_TGY4AA;7&!2*8+Lj9Ve z)#HPA5Um2U_x{0j$tAt1OJlXj8yGQ^Zto8B@8S_TDlWl1Hh4D?=&`~6bAj7&xa?Q{ zdHfC&`i%)c?=PXmNu8u9D4t|vBMosu@dJ05k;y$=w)+rxI%9s(G~S`(4zWkLGyA_E zch27tcV_P}|2+f}QQLr*CkJ~7AyrY9*Y86*eC$uT?ar6>X|}jevt9diVQ^@ogmV3j z7uNfcx+k``RxEJ5wTSKRoZaSS7ue(!x-H}M7uO(1PYq_)M(6+Mc*s325zFLIW?f(&&CSWZ{529BGRuq{_VP z1nIn#r||aC;pysP&z`4>&3d+2QVYg5ZFkmV&!$7>3(!5qzV>F%#SU-(kzz$Zk;6x@ z6F_0EcDmP^JXIp^SGsSJE1(s3r~Y%`nZe|l{o&M9jwZJ6Li6L{k_4NhO%|P5)Q#P2 z>~YRPN*)*Gro~pPhH?8_4Ty!t)vi>uQ`+tehIyG zl^Nde4yF^~5XEz6_Ob5g$!=*r&2HoY9-8+jO6@+?-R@J~?o)fs+hAAKbObth5Db;c zXXw@Maha(`XYW9qr)JwX7J?3<(}OcGCHv6p7Nq&~IAM9B z4;g}VD@9+r!-P&1CKSZ9zN*MjhX(O@kTWX@oZ0YYc~RiDog|+x!RCv_d*X`)(D;4+ z#XC%-w|=UqQYb5>IxqEJnyHsdlQYfKm*d72HB(>qjr;N)rq^37)wdtDRPxomux-j! z@Yy9f3Tmt%v_M$lY)0io;!jAC0zVKAOa2&D8CDN?$z&2q1vO;55!-)jFts{Xn;TQ* zSMeM6?(3z~@{R7(@(qW}H}*S!3k2~Q_%NczBXXu(Bn4Y5nmN-6c@yG%jP8Xc#>dIG zO7nTOdp@uFd|ut}eCB&C-6Ss}sIOVp`Z#zxwd#4`&E!g~NA_YozK}*VK25&OR1SQ1 zFySABo$70Y$!q(a)sR7m4X$b@E6Mi;$-^DDn!O~L%=faAya|bnzrt4u?f2leI?*Zb zwbK8g|wyxbF=nZ|J{27)*Y!UuYf=VuqvtC~hYW8(lu_wXnk$+YqgnuAC%|YI1 zsR!O3O!$XKuiwY|-zgE(*Sd)*Buf|gFH3jiYbEAJO*)vHcL)3CZZ`3(Lu20DxEWnD z$AkGKun+^V2<8Y|NlqR~w~DT@Pds65rosn8ljB!N$qDa^Tho#Bb`T^XD|*(|JWZ-H z@mG@PkEAizot!SZ8Xib+I5Nd&CE3mU4a(52=7*9PX4|SHuN_H09_A4cfDAPq^FEy1 zm|a6p$Z1CHDsQ0e3yR?~6=fw!?@#|ebV0b3HIvre+AUhJ*PA!^!VhO$%Izc}c;+!z7ppj?gYT;JWmBwQqOpYe!78)xzHpt|+=9+%W4pox3 ze%-D)e#erLxF_u5&+bpZ*0BYM;y1RJuj5&6U;7dJ}Y&Q1DYGL($Kq_xdQh12vvh6yY2Y1c3hqVe5X-K-=# z4`gSr#e=Sfzn9d`uj8Fej`K?Lsl4mv`Xa0!PrJVOeZDXkNRffJ@+EVuu&wu?1INMp zO(F)={?Yynq%D8e)&IjuO^!9NAhdtHKLaV==k~DcgFi@cmJU7>vw8n`@yJ-$)%-su zBQbp)F3ZpLXV8rK>oCoA5Re*3v9{B06Jne!=ObMAXI;%7F~*R>wATgGd<9&r_OR=N zKTL)oOt)F@*Zb40fDn+N+ucO-GK~_Wca?;4aXD7@wrWM%i6ge$dD30I6dnE%2?9>WRr!ye;|zx z3LDMNRemrzDW7kZsio<2d9PlHw!3*fsXlqFP1{!oGEi^uplkR)N#<#IRuq6s412XJ zv?|GW2hzU^fagO(r#f16^)#LglYe|!%YI`ZZHI43?#`pG#*Zba*tZ*JRkG%m)7Uo$ z(l3RNaDJ25Kz!Hr^?SKJawm@9lLcoqkp5-p0g{&acm$$ zHGF)P6p*G~?)W6bwL*gex=keK#Ec_;HyfE3N+oF?NWU3@q1TH@LsUzPZYmvPd@y-D znK(njy4Cz_9XgtRAwouP7WX`1f0~Tog{D}LRc@L0AI&Hb(HQ=;YqL9eyGyiL5t z2aaajrlODAH@g~7CQ8sqjY1)dyDV^R98JFxrviyW^vkEXALwQpqatS(mfQTt#rc4>Ko5F*ma=-Zzi%`k4S?a45%&>&(QRO!Da zr@Y5A8JC4_F5eCgfg+ZQJXpv5b1IoK@~B;f*)iZAqK;`;lMz)p$Ap(%Fquxq&XPcl z&m38`TTtc71s!Ij-n6G!%#m$Fg0E zWfi;H&nA>bxCZ9nA(=dWEIScd&BCrP&L;DA5t>g(c97Yy_ftzZ&M_X}?rlDo44<~5 zZWW5_WCzOYymgIrJ{ecW_}1+0Wkn`cl9zHglro2Refi&#rH-A;rdmlpe=MUpom;3J z;9Z?v6cJEfYA%d?&TcyHYCNBuifV?{OiFp}SVja^k#!W&kfl@U>fur{ZgcktVj%m& zeAPbR5arwT<>h3;zHE7BD!W_z8xQDXURq=w2B7`v1B0`R1IC0Ko)^O1*wbWW z*uhhI(Uuj*axcJa3SnsIb+&@mT!VNQEnqqgE5NVtI?=?VBpbOZBklRSh-0obT$fhsz zigiB83ORS8&r~gfMlztrZLeP5{lB$ z6Y*_WG(6M1jue$9Q!M`%=m_;Iwk=I_jI5w7B_AapT1eU!Kx)p8)QPpw1U!E$$?>n$ z{1*vyOv_4;x)MTwm*usq!f@@$iwm<)7+mD%rbzIe4RKba(&PUv(u=JrFDZNzeW;KC zm!Q%hAb=}znwxp+e_tb!F9(LUR#Es?liZpd$TcXS4Lra35lWbT%u>#UrAZkmoRjl+{OVDn=4AjDadJuH&)b9%c}}T=L12qkoC4tgUw4 z!&W>^u_)if+kKT}^wCtuLTTB6Tl&oA+nF>aQN8}(ymBrNB#r7Z?>pV zva$M;Y`a)v{O(!dWt%fI>lO4_w=t62k``+3IOM0NpO^#T6lmR9i*m7w}U1Sgm zuv5`7Kj~=-$_i_VYjsd(d1--CNUyD0RMxd&nW}T#u%g9l7y;_%=3p>`rufPh>M}V_ zz5{xZT#__Q98LdrNvUh2s}DK7IK%#EgA6;Q?pOMG7NZs5ncvP5Ht%?!EQnNng!k4L z!YHc?n^)fV-b;;Z;biEV14awmR4a1$8DfR+|6lgr21>8HzVEzO9)T>4wvQLFm0PjB zHgXv$qw5iosVw9~qc=1`Z)nB_V%&K~GmoS}^Ts?gl7_%m*#nJf;4JLICUldAG|-sF zX<-XF=B#NU3q8=1Q_?jp*(EJ$NEcezIE^`RPLpOo-{0@|zxO^fBV!x#wksd7pLw2p z|1ZD)@Be#$fnuoCCN(SDb{6Tqwg^ib#{gXdjzh}m);86-FoBhLmqr0HWxJkmY*F>U z(!0i5Y5(<|=M14kE(cTRo`qX^_pyQ=MmhjCt3rnOPXl6xLM|Fq@0RU~5xRsB zwIDiEc{V_dC=`B-IYT_aaOV~8Dk=}&IU6mrJ9JH~yhkk@@n=v zQ{+zV`sC_TKPoF$P;YIDO6llhUQ5%TtcEJrMtjFWjvNGV|H-ju9UMusrZpdG^ zU1f_WFFPo^8*lyn{FguQe_tR|zWAXJ|7!lCKZ<|uocR3h*rXqu|B0V3^A}z4fBs&+ zCHu+W`+43_(CYH=C6(5+URcz+R*Q}K4q?F^?A_v;bjQk_w5*L-og(^)76D9$C}WdU|7 zd1HF9OV7*V=y`|w?YAB}bnUHxj(>jR+Z*+tB#s3Zv)bUqCcPA6Gy<-im9)9?G+FW~dPeEcVW;{W;L|L|hM-m`!F zGavcwfN+e&pvD^!cCvv%7xtXSTic$MpQAH$U-1|Ht3B<(KvS<3Ih5cb|FifBZpxe%t8B zx7@kw-mCh2>6b45?46(g!JpUjf9-Ss+l$VA`0qY~!#ew0*I)Ia1ONTiZ|a%f{iAJP z`h{}~uh+cq_}h2&|Kqp)r?dKgHKK3yuq;^S6TzV1p8(yWRq6%z=#zMWVPgOP2Fa0^P$V#cxZ_+TX^eZsVP_b_$K#O}c?wr4dsG;)1 zIc{M0^5*92JvwkqS?4B}5a&>EF!oR1SaBp;p(64lJ(;{%Rw-LJC-UqbF`}7O4@NYx z+!y7bqLRDQU1h81*(Wy?F?zP?X`?U7W>>cWuT!mc+chtIN-X2cESf^{N`03K<&39W z486O-W&9N|IXX#I{RGb=qeKiAkIZG|C7ZXvtxUzuKPTLt8 zDC#;a&12N;4i=XvR=_13l<=i$8(N2SADFSbZ_`zn?lR24%rw}o7gnSMT`W41$0#~z z=YTwB-HALM2WgE(h7HcjU;E~OhixBW5~Yxug{JCD$eZhQtowuR9?wcstF0fCm*!D+ z3>1K7-Liew)fKxI zm~ROJJYwnx;Y}Mr_ldlSCjv{6(d%Lqp>jBMQlp3vSZX=h#Q3;YG^m&Z(ZTNhr;%u| zI|m*;8x-t&L!p;qB{dF~2laJ`j=G6)SKH0Un-`S~FFEeX??azS%IeOy6!mhvs#H=edwBu6`re{_|LLR0&W#^Ab9!{_-2F$!M$VibA3gS}Q^!t^jh{Sr z`c-2m5D&juoZ;56$QVGj256~R0tn`-8IJ%n!LJF8}gV!ylvk^w#1enLD7Ek#Knp?PfJ1|Ldq8J`acFK7H z2Tbx(?h1koY+T8}s)L#VuiWDgSFNetHY#N~Rc1HEL%b$PSrEZcXHGg!KCpP9#w-{^ z-j}tsf1L*05Svdh)`^*`6O`14HZIIBV0M*NBg*ajO2WP-kSx#9rMY{1&Iy4+Q`#(y zUswZ|VSyl7^iwScQeya+)0 z66y~Mt4ipD6=@LhFbA>KG}fP{729gD2#J?s++Tzpej+atYuP4Ult+C}PWq7Fd-Q6d zTLPxj4x@s>8>OOHj~Sc|aLo4UJ$p@8-^Rx_y>Y@S2PT`{#1Ws(v9U@}<4gZ@< z@s&&z-d95fWD!>|mq0L(puF1$0vKmAFkNC^6Hq`2D>bRRVQipKF3_ULGgAC0iS`_a zpy4Nj?oQ~61Te<61LHngS6^?yUUV-{tK|RSn#HMy$%^!SNDCYavS1Zv1^EVZr@_xj zwx(J=iM(z5a&ZE&O-N;fCgA$cfsJbM+a+NOvH|N4ifCpDQ?-5iQu9NFn_@9xQT1Ue zZP$pO2Ib;uqst4o<P%8!&hX%4ss{$ zlTG?_ZnSo4jI}qOy5^CCps>vY3DT+hQvKTL*ugHWgI}i;`~0#=N*AC$)*MV^xiKwd z6v8VjGgxn;yzIKW%6+4bTlOgE(E%>SE-aqTtU{)=YrJ=8C}CoSz1&M*7TuM)3#w!f zF@oJ$6;%_PLCJKL4U%>{0-?(cy}}3|$YD0aN(u~_&IBrordvp6+t0ihaYj??8 zAHfHw2*`Q?_-iy`a0ol%c(cquydi7tvm-v5>Vy@a3j*B#O~b@S|3CUYu#?~&f((pO zGDFN2H%vHD-kyl-5m%TXzkVHroQR)Y)fx{n2%_449b~C`S21w`LH!Q56FadKORdzN zVQ;A|L;#*sG&`U(t92nG_wjO&5IAA7QBfjN?Z!G&hh?QwZ}_3&u`RyWZKiktvv>qs zmmAuhURkd__Ug3v@bKYdiJHKJC4DIR1j=PkLsXrR+jAm>);5*kVsi%6P{4Rd7d4E6 z^RV}eRNxkyWg+P#pK&z-Yvz{;$6Q2084>qe>k4RwiS~VHSR%w&r~z!zo@|TdOR;G0 zE%p@rS-DBNAFubrzJz*F(yT(Ko;VwLAchqpMx-Vt^rdu~;J9VNU$LvC0f85l6 zdLCO(LxXpdZsKZou&e31onpE3=rqPUsK}I=t9|otr z6Ec=7Q$Bt_8`OI4v{4#h70b{^DPvLyaJBRCymhGKTZ4K%nG{^OI(-(8B|(hU$UA7| zf|INrCT0VZV{pxJjmmT6O~DP#cK&ji|LTUUZ{I2S71qtOls%cB8Y;fB(QnB1cly!z zuabKCxl(EZ@iROYhtVi#Me2kNEMmv}Pw7u!8jgOjT>#vjuG;Q2mQfS^m zB6v#)4_zj!gIN}@r_3iCH%%A1?iF>^#sql*lbkX&2YMxRW+A@aqv>#3{d@mA~MIq=bcuN*V6vcJ& z#^=86ev5Pqvc9Rp|I+yLe)wiLwEZxZ+DKl17*4?qj zcXa|0%TeZE+K|7qZ%5SX+kPlJ^yb#@{;;38&Ge|9dN{a~(LonRs^6k_!;+O41-Z;u zZf)c=fC)rnBG7hs&{&q3j}e;H=x^%S9;#>XPlPm#=~h{62w|qkmDby)vOW5-DjvkS z+cORZ0bD&AaFmQl-82(Krnnj~NiH`y?8|iV2MTHkRQ5#HY|Hc2D}IiDUvI~v5>OHn zh0R9?Es@r^neIR=O838N0Z?WG7|SAhE8ADJ+xUK>H`YgrVw#|?7?!G_4*LTKj+hK< zWHbDl#gE*+q2M3$9ELA%)XWX{nTrz#GmiApOVhIl)en#w>>l}3Wzb+o@uYp=*2XkVsK2yF+&D-`#a3Ii@_5TCb%W75+PfnaeEYOa4f(n?;}l$XOv z$XhF_2b+oJugFfu2@>Um3rc8NGj^#m!k4=W>d4*-4QoAyJimg*fkdyW5ZMK6dWR9SV6F zxll-N-i>IFt7W&VeMs&M2R6xSFlY}!)Kwa5Pj@dL@Hb4F(Yj^GQTNF=!hw&@k4EHDsTD`qjK(q( zm}Sxs-oZ_=dicWA5T3-nO&a4;_&#JTGP+BsA|}Hmo4u3WG8n!TJSYwl+5K!6fWoXN z*^>m0kyC^}TxnJ;%eJ7-b1XIPi0+qJ#DO7u#M*h>0E-DEgd^T)5K#{vWp1&}_~oD5LMFN9qZB3)S!!V`}CP8yS<9{z;y`XGBw z*WI_>zcsLU&At^*)F1%{07E*JC0(`qKVdR3z&7G`w_5UD%L`<8am_Ad1wm@ZZyBUA zF8?!aM|h&%Iwp(fpS<-2J!v@6Eh2BG=EQfKj>^qLbx@0|S5(X*)~ozbmQDTqKl-Jw zZmlG@{lVrQ@Yset`!w_k7yMWHD0~sU&n9Mi2HMoOag570Tu>|ti;^`^l%HA>ar31% zx_iV1N!yTYfc?Bv+X+#i!J}+4(N6Da>A47uOKA41TuQxo9!m^)ishW)#=0 zOVTY*naVkfrB>!C9Z!J`lAwVZ7Jy4Cb(9r;V&|V*ihi7HxTJ1X_KnkJpGF8QW)kj4UOb%%BS7u|?Tg~O(mfO2A-?(6SpJKop1#)SSt`Ume zq}i{JMX&tKcU%C2H0Id#N|%zs3J6@N)_A-o%_a$9>qQ3f!x}f;(JH%(Vg_CNu8x|O zwsEs4G&bqS3|}86VgjsAHaiogtO2Nyuki>5ZJ|O1&hyK&3vQHx9w4b1uJ%Akjv@#$ z)=*oMl2ItJY7e5rpqYtRu`<@|QEggw57z8&E&#Rqg=_Lt#!}NMF7#LFKJ-kv^Z7I6 z)emetrzmZ?M{`crSZL1$({#Ex`mP291wK&Z@dm5?lU=`tF0h;B0 z%dMIkohO@^Z4nqCA@)8hp+v;YdBv@D<7X5jYj2|_d&|-4X0RaRblLZfl;vJz{xtJg ztf|b}#9W*Evr8xkzd|G^pt^{?W|SVUm_^An`3$ ziY{I%9&D%ezm~`oj;Q@mxu1^0(>SEZ)Jsh*GFt5G3q^d}KsbD-dO-KY&Mt701;lnA z!PhT|NMb|yQ0ocRs9csc6b(Fi-^&dgN_$eGlQdO|3EjdT+}xv7CsSYJXdY~z)5S^e zv>(d;l$Jd;YREZS*ZHHNh28Ha4TNgjf27^+|cmvq+4vgTZpNdM;K6vR-YVB2txi zC17soA{iJ2aoiD2YQ{a72Mi4e7iyYw$dpo3hla{*+G0WA`h*QwZiq3Y)gT;ki)Be% z$-piP74YN$Q6GXF38B^53kaR1SF#O=1!h2mt;q>k0@|~MMVTgqK(PQaccpA`hf0N_ zG^QuPMqd}!dA_}R9bIf&?ZKzG=8>FWPixb_692|kBE#GvYOyc6z#@?;fIb%rt~%`Sr%F z5;7sd7ps@i^`LLS;ofNTR)QJmeX@TPv%VMbasq^$yD05&HC~$BZHxhmy86d1I18ku z1Y?l_#>jOS@d?WH)_EC#()kG8HK{zLz->|zF4z_#3CS_g0gF2htBjE?|9ppe`(|NN z+$2dA#-NgennB=CE)7D4iBGk54Du)i7bM$7lFDSKJi7)qoIAGhjjccAh;}R$RX@dw z6(!yT;|8V2B5lvbqIOLS;;cC+(}N8`R^hK!v(Dkje>l_mrq(-uRZvnQN*E}NEmWtX z{Ueb_dR7GqU)WXhw2;VPF#@mogk1!fB`Z*LgX3LZn%`6LeNAPEqy^xu<$mHw;~XW| z0bIF7VZ>+$IB6!ieMn~RU(}U$mv?Veq^u%bRGB#B0OL}WW8pZupZE`CZ@M~(D`(kL zkoySaNk$q&e-_Wa49j1J9Odu4tP&8MS<TrY+M@g-Ff_ukR#z1MEzQEC$WG`dZ+fKSL2$69ChZx1@m^F z>nxzyMtVQF3uvG$X5lFT6{IQagVT;A(~oTvd7%x z1W7h*E>vd*X-d*iq|&*!z56!p*Z7J{OFkn6JngVN2qJVAk-tN~8Y=#aXMX&RKFqUY z?6*=CfTZ@H1i+>o+z^8UO6jRR2B)~+*dXegi5FRk2s^&EPLf4;4JmU#RMbq8r%CK1 z+zAEONiR=HFWxZ32%Dt32&Dks68lUs$*#Zf1QxuIrbX_=i!DHu3xnx(WUj%d(fe1^ zsvt3z@u;-~3xP)&$uS1NC}xp+3i+5R#G`oBdBb|^lVOlDVBMv0>|zvpa=9N2ztQZ$ zsRay$5DHwnhKjC{0mHF=u5;DrM1J5X7nP7Y_#{*qC+vcAtUwk$nQ+9T)13cKRQV_i zwPZ!$f>Q^kCaPUSGgo&gc)wsQCv@V%9Rg4)*jRD(LE({gye2$446C5~@))75sd`2j*Nrtk!$VW~y zMlrXc#^IHRDgd0b9$#SfEns!u?S6S?!@5?7 zG`^QZI85-!I&R;G3;h7M#gp&B_iNyL)R!5Sc@-99oYse)_&RG%|MY!;uyDd-`BAsXz0GGEcsCn#LLmE>TcQ{}UD3)S5bMSWf^YG1>Y) zLw**B4PHVP2%}3Vbf&}A0l3K$)w~yN(u62UZzyYP^z^%qZO+)@IX+3-GFuA%+ z4v2iA1i}0i>)g9uk{A_ks&$HH#rj|^z6J4;p7jSiDzmRw_JEW#fRgAGAW<)~14-`?ngW|9T$J(WDt{`B9)me&Vx{;>LVvOM2h7b474 zMZ!nwwnn`v<3DAb1StSoR3B%AzCi_}VNxZQJdnQX9cCCwg)jljp>|zr8oC4} zi_m5$1rqPvh6pLDNHnGQJN$iWu>@GB%4xvMqEXr5Uu2POgP-?736KmH}a1~BC-ow!V#Uk7gYHY!?oE zr(1463=}$3608-M(vMnbK@JTFBya|{oab@zGc-bF z$hxlzeM^$37Rdt54Xns3UR8*u%~zq+AKQEtzLY1}pB$XS7TYT5c~8xJFM1? z6r#M8kh?%QEOK?+St19eg20ZbC6yQox!K(rVqNWk{nh zv+I&qB$@+h(n4fZ#$$-3GmKK@_LIsuQrDmj*~ZOi4aZjD(g3*(hEBVRJGp#;IBStg zsnW2ni?A-2*tlDMqs`{x*12Jj(+<}vR@j*b0Sx2>#gg%Ex17bhbw{V9m?evyUdM!W zX&&DoP}TSo7(8LvVh!928f=pkbJH;))Eq66B=ZhWn~R6?QJtMv;uKDc;be zw2BX-QOk_mh&`Pp&ZI0}-C5y|$q=er_B?OUK!wh7?QhJe->XWzN$7|>J#ZDRk>adW>^*9HS`Vvt6DL~Bs>oWAl;a19kmi4B5*Jqo%I{B)@Yj?U{hij z)sn`|C8)9AnMd+emje|MQy|~QY%3SxBt{osSR6vnb*Ac#OaKo0>BU5&#K{}yz=U{` z;V3$vYG*F@yiQ+|uQJV03=@)tr3&k=@T=*ZW@?B7ZCnl+9a0Sqqo4G3*<+>|F+hnW z5d+lOLma1*aMZ;IamE=j6$5?EGJ$^x=v73pzU1C;%NzL@Ov<=efW&6sEhWmNaN?dM z;i7kFA4shgc)F-v`Vl{g6k6H7b$wV;po6^u%mhG2$6!HtkK{nhVWY0f72AX7@3cj8 zB(9J1c>7Hxf61Z=mWA|aK(tBZy7Z)H#s=6}Zo}9j5+bXWc>0*GH~iVx&8izVY$m#Or@9g=ki0#$V$RHs;}?}ztrre*nqpzvY@;-fiF=hJ34Au= z&QeipPQ3?X_JuL2o6?;W(`_TS4;+x`Rp99k>>}=owIS*nIODhG*N!xiD_F$`vvY%( z2YTB9gJHz$04N!B?Z}8F;Sd?KnuE0<`3qxRC7)1BZg7(VvWZaXu0IXD(!sLHTxV!*wJW(4IAL`o++F<_T`FSx3Vm z&P$Tm;tH*BY$F)1+J;&JbaQ zTRw&6X3<14DK^yphMRylfJHqO%efIaY#lsl(TI53G$_VkrXxcUkW6D8daagEtq;7y z$Ota0&1!+fh(Qxfez#0l#T0tzKN;G_$ecg+oZa&}fAPo~g>2|`d1OVT{zQ3Xc$AMV zPS~PaZ!q?VG1?Lg(7Z&N2}mdEhhIddbkpUmklv#FSCoD-v=%1@WtONbagpF}4~}v? z#y|~+-7W1yRIKy5vvI`N#Qc&vf&K<=l{;Njw?*D2arVo$%FsD{k*1wVg*4xm#bx@^ z4NZ{d7V8RJ%@=(w8pBOkv2|{STWoYU9!LtW3EhT8fI;t=3FCa{(b|%S#Ogb?*oF(# z1vzk_+9`~<9Ag_@uKwau^&kKiS!lJ`+Wjt|5ItO{_sJ9E?goKg^V)tY3C)y`+BkOt z<~+Yhi|vJ_OVCB0hjdKjO<%g*2j994CE-3z=`1?^m7E=jU-iTeDhK1PC#!X6CW!>? z2Wzw<8|uW!=-8QauOB~p?CjWy!4$-BY;oeDHudKm8X}E(zG>se+c&>tb3aC};u_KU zC<3ys0`Urjf(7$5_$GP4CbUPH4wuV#B#xiU4(fr8=a*+LgQHMV;iHHc&~&vF7$C3( z)hV+t6eH||N@KKDxB_-t_MEtm$xsx$TL-|y7U{}B+KC)F^7mM-x)e1KSqXRccN*1Z zx706Jd9+i|D#dD)Srt9NNv2q7H(6ZSyRWo}>Nen0?XWuYl0%^!@8U)cJywh^&r&+tICTx|G=QL-ATBykN zr~!LegMxGIc{Ah_QD;`5LHHctml$ACP4Jdv?Nxx3Eo~TGmWgzk^G% z+SmfD)-Hl`M+>gx$YBceCRf61NvSm9NTY;TH@NsN-!c#uqmI)4iIF6mPk*n{sU8Yp^X#**=SD8iwi zEaL0$Kp9|Fr%|@T%FLm`nBxB6pmwk%l;Tp#55C8J#?SaLbv5t0S+zliJHf=62CStcIEo=G7|)ozrn%2!mrgz7pDE!(E0^0)0G zhql2LwIYoAxwG?(`JvS~MFZQ78#nIRycb%&{Zj?UE#oTqP@Nb=xS1CZ zMe<@BJ=C1@OTecX-m`_isToGcYbI*^)k2?UR-Ujk)V(O53srieLZ}oZ_-syY|B;yq zA~C1jCzoW3tyEUp{*@vaw*-oy!Es#^sRrKwh%LV2A08o6- z(X$br?;yNTvaNA)otlI@up2WUcWmVb0-n@!J!m;Zj$x(UTwm*13q+HFxOLCwZRY3Q z^u@w|OD3Br?(@u|=Upax`UKBj@>JlVxulJIz}?W}C^XMD$CNs39LpBB)_1%Kgc<`^ z9J=8>H-_R4ZsF0h04uBqKZXzTtOaJuqlk0$NDd0nne4FiJi9$7o_Y)umshaSWrpWW zqTk?WBEUuOCfY!QK>}#|2?pR7OL>buIABtx5xW%~CctFL6%J`uBdc7VJ9>o}Gg_1J zK5;o;WsFoJjuA5OAWIB}_s~Fjw`dv+2I_I?me~N=xKvI z?#NZ^F_)(!D9gYWhKmuw!9@asofP4{$R+-oy)PGJ+DUSja!b6b0>LR$vbzQIZQFWq zvmcT7lOOEwv&OgAbDV8@v13OaRpB}{eqLue(4cBTS_JB>cI>6fgEUfQ<(pCfY(lL> znJp7UuEEz(QYNPtCzoe|F%J+=(kU;NR98o;oYObE%0u@Pz%F~LFV=EFH@1$!9s53G zg~pfD@8K(syDjrqM9%9FW#w2b2jrCOOmTQv097Bo#f=+A#PbU2=AbpZ_#QW=iON*e z-?h?SZQ4*M>9L=qdl!dMDN=H|sd}iCRIR}V(+A-p$qg4f2OdYmcIA7VU(vA5>I{yL zr;y}Vh6P2xKh!>1QAsV>?*&?G$7Qz6SvMMJ_@CLhTd8S+L)I0W=sN(SE`!`=tu{k&T?i|(bJoptqi$46X3X$GGj(mqONg2}-&Ir~{g85F-V_D^hz$YAY6pqQOiKUC+@v`~ z3|}o;3RHQp?eC4pFb59AT27g*oBn#ZO45(Y)=(#{nJS1hDD`WC?Nl+FIwng)hwOS7 zu1<}zq?%ViYd2j4cB*bsukpILn}CuUuUzM^qe9spo;U@VCEZFlOHq5ZM%EmxF%;&T z0XHQdpI_YFrb=lTo2>A`nqeUTUb;xi<27D|DeX3MaeEiVMQ+LSe8*4-ux8q8wZ=fa zv+pcUdnHq_hl^>1$86qAhiE5DpaPeU6B^=?(oIZcdzUd2)`Uh1>w{qfb>ebsp2TLG zl}vv(4m)W%uv(mKN|&Low+A;88VSwmKF}kh61dR&t@FI}wg2T(QhN{@We?ZFNqTN{ zwJlXY^lAZ(M{6~F@C`j{PgXJF{t0>&NyZ9LNArmK)<Dm;t|-0Eszj};ha1TVY~v@ zoWb7jxzgDi$EeP!< zdRk2~ou>R~!5slSGWVe1Uy&)fj&R(S5?RY3iv>=zzN_GYk zl~n@rmHQgOaHc>QoBOg&SwX^oMxMYsM0q~8uWTYh-z{Q9ZdW$MA`xABr*|WW8HG{? z2xyTcB=Nfr*|>TeXF%#LRzB(Cu^409EHj+Oc+X*uPn=&|URY`iv{F)eVLdm2bXdM!KN}7qNaAFpz>=3IHj0xhx5_Wd{%^0U$J6RVZ8) zV+d3Uv_XwscfRIyY#LPLf2{Pz2D}sXE1}SsF*C@(dV8vrBgS=-PK9jwG(FG99VVZ4l?HhiMv1%20(HY3D&R1Gn56 zL&)I7wfik%j@ge~TAsU1AaucX7=W{l0o*ih%^}(zVaM}r{K|TRb6OkD21$a!%0f^U z#~NWNm3JY;JjGB%=AzwXa{C~FRwT8+@@u1jsi(sNuH#;{@MlApx#UgCye?g? z_qQG?1}-%*etxtVxIBMa9E~?vMopF1Bgz~c`Amu+P7zkYMww4Kmu z=leUF5q}n@-1ionEo7x+1FVs)N&_E}MKDR^ebikjh#-tvJc9H1j^bJ!4{KF zTs@@?l>@$-R}=k{XAen{MBmYE2n`w|VQt5_Zj2tznrn?MSO|-0JQM@o_#^_Qbee#lG z(pC8Zs3V<-ahgukrmSS_Y24!~R8>@lx-Kqh9Ev0X$?`}(n)g{pj47@DzD~qL9{W&k zJ-ojM(ACK_TmlyRdPY@z_3E286M~-l-QWUZXG~EMY>Q5Z^B|E4uOoND(wacc4_F!X!I9ZAv|E` zQ9SdL*v3}!`s2{Svs8|po+MXf!Str;R(U0)nl=x3mxf&!yRlc$=~@C9=V^vo^U*69 zm=r5&E-jXW^|0XiTV*c@s_(KB<-v0vMm)7V z+Y6e#eq#+gRC)~zvv^}3bP~k)mldql)PB-4#)PbLM1X4FNd$0p6+SN!Gr@n;T|S8z z0uph0JI%lsI2}|kLF?x}!OBcRtsjIpMyA|XiuSo`g{~b-C}e#WJt1?uQvBBpaHxK3 z1Or<@{XK&E#~=vEykT;KW=>*8^ra+=Wn(1-@b?^nk}B*3+_5A1caCs4NqZ0j)B@6O zm74VLuD@TV1Piq%qR5(TpSfV+;Xl3k%ZL)s;~ymh%TjhlsX->&%DkM47TDK|76WJg zX*8o;vywXefk}v#dI~@qF&U_D?9gd!&}r5z`#8AM(zHOK#t(_D zY&OjYo3orGjcTn%`OP?kp0kHsVfTg9bKS3TAsOD=Q2s(9l_cj^m$Mm960^zTs-y~z zVd<(V_|2p$(kl*p5Sv1KT_~DV*ru?3x6^(l;YyeEWP|Y8?-s07(qtEmRtat>*9-Q2 z0yu2KGB?cXge;m~1nf#@Y2H}b#1bLL@`oncXU8f40OVA?+%$DaKvWHOT20A)kIA;} z149YBx?MmKijj)@cve+oP6*|`Bj!86fG@2g9r`8y>97u&&jF@cLoA1NN{LSK3UMU&sc|)R-CRJEieWYrlA!KTw0PDr-IcrnH zbkCrW1gqBWr#4<*`=U!li>Mh3*j181B8l!>@k66$PCtwmO${j~8aaD>)FE$-Tt6bY z%n%^GOkp9}LX2V+K&&(zEFF}Mu5$6SM8w3;9rw&JI zb$~1t%3CV&Bn1g@%uor6EQ(%}9?GV<6Jt7^cAe1;Rznpvn>}Q4eRKl=zD=6~@Aytd zP-iLDctylmS$jITC`#+*CC;YX`Iv4qY=U1?M7BLi(^Q*^b8uNp6uBHaIiVn%3PPv* zW!ItJCC`w8;Z3t8C(t!pB4NlR>0F3qV5%7#12R7JfbFMew8w5R8vC+jXk#oI+Bigc zlp#7NU16%m;oMs9Hte;iYFK*!6kOA57MITMERMK7Aerwaddt}&T{;UMJ#H5Y4H12+d-R1rfVdUHr zd>wIVs_4u6VFsqMSVIdjZt#btns`yuCJJ7oJxL~)`_5xtd7sTX6G2N?N~RUJu%ZDI z>bwEPY9cs=3M_1Dmx?3-0OF(;y%5eR{6#~>p}s48lAX^)5Z zC!AlNh5S?KjZz+Iz)aVM4g23hA}bhVjFtN#ybE!nI*DFp6X`$_8{DGlRLGf}_}evh z(7`G+1!lB*O^3DO;izQ`=Y%|gtIVE2h(mx*8m|0JDsbs~I`TK<2G6#Ph|?F{UN0K| zVk|6S%VzD~Jl8B`a9%v*de%oc!12OWLNdcDsrk{f&3Rr9vGlV^4f5spnSpjaqtn7ZBfFh&ZtVW#;pmk^t~jmN*Lj4xP*FBi(S2^V4^4!$p~gOOw}o)l0rH# z4ne)ovjS$2vxPA}ORt z)IA(Mi8po2f%KLM1uxjQqLzMNpre5K4AXN#20R=>BU+x+v@+AfVpMKFLcC-Q@>z+R z=pBhmdvJgSRHlM^I#W9xnjx4L<$0R+xdNuOF+Q5&md$bXAYs9}ppw?8(9DTPr^W*lfiJWhQ2y?{Js-8EH-- zXvOJW<#s0pCR_fB;QldbOHgE9Nye2lLx4?0k9zLJQ(Y|goegB})pJi2>3c|&>-9TM zd8bLQE6^u98p});yVQm<4mwFgvePCDc|`q&QE0&GwGpX;e7{Tx$~KDj%yxvARW)CV z^+17rWHt?8FmfOm_u8~lrwb7aBo~mM;;JH970;txF!WP`ZJF=s%kRnijy7&_l#)33 zLMtJXdPP?igVOzQ;^2}7)+ujd?f&|7sbS9!X;D3iS=L!wSLRnY|5w?GF>0_=W@cjM zh;r5`Q`5=U`L5?8ed$m4BE3Dod68~jr1dY--oE_aKlzLFYn%Tn;~c~#T&5xl$+EHyLB6b~`u*z4WJ(f&Gm_e4 z;b!W8++#&O?QC3cjPHtxnYXeZVI@?T?WuC$2((B(JC%eP=0PG}RMSG>CSGCki$?YMGkgG+qD}owL@^4AE~}ip?UDUdbC%g# zh)j@htb%5y$S{ByU}eL7j%T$$p@&s;WzSywr*GI9mRsOzS<2Xkm8xypG?MH+2oX$rZ&+;EK5ZPe1tIz3Y~0i?Z!YySBi(FeJbSV|U;Sj94a7W~++#0Xs=< zY2b3di!hoXih@0PWcD}Tm{*gu-s?rd0wsh^^OMSO$3&^J?VO=xnX2e@y$-eJvT{ZW zywnnslMnc)OG8wngK-LNq~FJUhVb<0EbE{-GT&M(Su z^MxPznSW4iBkQ}(aj*{cl@r=Un?|@y08d9`;S+|U1KkCjIUZe7T2 zd3fb41w$^skoHpbNnx1zO1!yQfQ-l8^d?0pXgL;iyM#`^k_tXj)$Q@dCs;qy3vQ$^ zYcbj!Nm_CQ>SAfmSz0e*yOJ&atgKleA!U8L>P^W{9jXjXTnofDE%Z9D*#0%6=}F$J zk2eriGBx91SxXlsEyc32UOQo4kd#G74CJJW5V9Dx9uO68Ubg@Mqzk;o@Ax645QX=Qv1ta15OKA8gt(u&5@8i};Q#`juxA2XYWsAyUp z9S}<;xEh<3@ki;8Ru`HbH`iV61acrcywJmbwtIm6TBr$W-5zTOh(DL0v&M@KfHi;< zRFai1EM^n|QC_2EP6$S;%Fki(19+A=;mBn!b-~Fa(+pyNrg16%#^zRbveILM{-~Yc zdS7m;MY3;6kNI4W%oHSwmq{}9d{DQ78Mgp4iE->v3P0HMdQ&$vwN{=Sv#Oxv-Pf0o z|V_e%OuUudZK~OGtjze3`O}i>u2H_WR6)sDgS;frk`i#BH(02m7JN15?fxGf# zmJNfyJPyQ^mP59>2o92ef2h@$4IritV^nEmjS#e}vo!nb$Wd`A-9o}i>d>Fvv&x%w zi|KA2^s*3QrOt9F?0zY!0YD9yRqK~;=a_&3dE|eV;%r7ny&dr>I&X~N&$9QkMIfIt z5^m+2I7&eIyn8=m0x~l*P-dO$_kMoWx#81C&kh@irl)W!KV$c27E}e^ARo(<<=FZ{pjAApm_0J*np01!LmS`cHtAOaPUj!5@k--g9 zDz;i?D`aV4RG8oE1_+tU{1RMoyBq6c-*$W3!YEr?0Up){|7JC=R%_6;a*yd7n;`2avBN5SpN|mJR>BLGvBAaKhLYUfFKAx>#1xq zFWyottlYh{*j-#nmDSb!O5_W(NEjlfX&qS!ADB;QVu zulx75|2ez-IB0duYZ;)0n|5_-6((ED!++rP^CZPnQqV2xo27n`Ay-MY+g-}0ht z=P=EOm(EG!sXz9;4mT!Pq+RN#vH8>ZF|Viu@P@B-&@K9LJzESKu*Plzm0-z2z6;aW zY!;$I5S+t;?Ecj_4(h1r9!21!9mlcqVp+8Phw&iJ_xZT#&)ss?uCt;82k1m`-~iFy z1GbKFB#WtC?fgxBIn@jDmu%UV?c_}bG%R)V`s0ov+=m0$}W6se#M3rzY_o@^q zSMj<`k}88B|8m-{zQBOazpE(N7wo%B1AXW*m6MLQc`CaLlD0ZX4 zApFeZDcrrA6@i>W-kwH!)tdPpX$$nVBPc1~T&XWcJUEle`0C+Z_(&r!Mp7mcr3b<$ zAavD(P1i7fHM!dC9O((&Bg7)>%?T=4;Fdr(5Ewiz%0=3h$_gO(X)OCo?^V5#QwJ=| zE>`0XqvjW?oZA#=6j7iNxbZd_-%IgkRqUhs?GK9YM;vX`O+|t9L7I=bv02|(o@c$`9=-8A36p7m>1||sQ>2Lu z7u3Zitg;>eoSAs@v>YUEx#S1rz)K3*ME#9z=@2xMJpMfg&I4u)B#cQWRg;q4DwTF- zr)4{#F5_9?*wwh;mjEC;EI_dCY2hvkusG61L5m*SZF6VUDUqOmrGs5qq}ffktkZ@v zK-mBmSPc;;O3o(qI-~3=z}P@_>F1A4%q(akm!>J)O3Y2`bH%YMw612|33WowLp%f&stbVfDdaW8Wq>l{ zY*cq()%V)z1d$pOS2#hKqjLvbH(@TrwW;H)eIV7jD((oOMSlx^q)e+YgC><*fx|@X z6W%gGJurVT3BSm_7?+7x+hhYxQS<=+ zKE#JCqr87swEu&B&i*40^j(7g1J7hMHPD&Ky(@2*4Ug;l3pD~r2UACopZks5)T2YE&gE}tX6pgV7+viWwnRCdrKc8(M2FN@a@iuB zSOOKNU(;`5dNE0IMEd%{ww?WWr79@Bed)jfX{Z#Ru#}#Ts!%}kh?ibzKLn}dvMu3? zb>?Mrn(XAq=MnL0x6_c=5U-VYUrd}wm=n?D-?Cd^ZI=k~nKUF9(A?liM{9s#69w9o z16U;mT6!v>!+bN(wtdQAm~<{0q+Z1I&!wn0?%)B@&G~F$EdqOwaZt*PJeP{OgGw*U zw;o2oDttV#d7Y3nSU{Qhyy5wi1%)li?~pPEr9q^`yj&OW(Q6N;b@Hx-H0wN&es55LR>*^^KGz zDGaAR5F7PN{p!w&!RC4r9hn2t5mpdXQ-h-fKPnx2Q2Gb{YGr|_B= zI#Dkqksn*3SSD#{9d4eZFt2z9|0alIbBVP`v=;BpiY+O#iA_*YT@%CDFFuc(RK9_v zQF1i-x8-q-NIz2D@S-o<2`4y3E@aJ@g&}q@7;EC@1gi$O+An?3@>w{zhbUZfg#-pY zYZ=W+Knp2I7qIv7dcaEhvx&eiuEX-e%se@<@|dYu2eir<3BpP!(`|Fz1;3U+BWYHx zF{9`!7^yB-c6d{rD~P2!SdKjDbJt)*TzBpYuh{jMH=3TLD!faC+W=y1#3W>&l3=@g zH{1f1vS$JE>Ohz%hU;X-=vuc_eC*NA`Q?j;mnB6zy?}$`^=uw~O)r+X4O4mn>zCfp zN}XT~9-E_k!u*`p`I;@6YQZV_8F+vg=W!?kALJ4=d`r=QDrl@k1`x2k4T~<9F03E5 zt~Pr#{Sx5uKXF1O{1Gtc@0h+}F~*u1nrY+eMPyUNNY0-JO?y^^vzGIunKARn0k>&n!qFbGOx1Z(w*~`T;ko%xIrO(?U(4YH=Hnk)0P)!tT9k}SJhPRUa8Qa{JR9|eec9Jn?KnSbW z%^3wGXQ!vYumt_&I!k^l(<@XDfZ~)ku$Lb+l?P^Uy9emoal6}^x&69uXpwsv6UDyL zwg>=lq|$jlQp3TN0rU)Pm{Q$cfWyEd12~l$t@V)+b7!#jkkJMa6$Pe(x60xPS4#nmV(q0cT!Z@h(4ClG}?-I8u^oOL+L@~DEQOb zKm5-|kXi^^buM5f%3PvAVDR>G6*|ip01pQpsS(8zHEbhtH3+x0lArq+0T44w(1e#ltg7*T`>R;BpdZYdLeY$fJ1oU{`E@7dDX$h3%J!x4I|&HEq~ z9L&Zgb9t;nEKLUn+Xd^~1ZJXkcZQxZS^kbZ%WlsGPGB|%nYhMzmn=6H!lJ zGf;!Ap4<&Xh~=iKq%)X|jMeBtqvwMVUE=Dq{5@>(FJ*0po0^{`^r(9Tp%D%w`oPy?&G(ZuB_p|T*iM$x)Zv>chcSY#&#WWI{%~G;|Z@SvXvE-}}on;p+ zgoSe~qRSfEMM6e~Ax$jWK7;6+e<{Q(6ZqOjF2+LyzpL24mwpA7?X3d_;)F%j>W*p# zvd*J+`l^nGy+wd8h3S78DZs1ZtRMx%r=0`olh^;qewt0Urlllv>!SdOZHTqPa^!A7 z7=^3_BBKSe5aBenA4ecHjm-s7zqjyU{dcTzCn&VUmSR06OatUPVn zmKWtS0b^oG_I0er@NA2mX#hmeQ?a@;_e2rz2w3F-d1 zAL+S39<`qPxNsF4r=13J5yL0kg1XA$Zwi!|yhC&nv#~^A=?0u~lDn)F3yU++vY2>8 zo?Oxopy!@LJ}@(VS@XSaNwJ@!52p2*ek!ykm4ELjb4hK0Tv8lz#MD3sd?+{e0_u0iFA*zo@R zR~UEuvcj6Ay(pVw9y44>jp^OChSP~PubEK;?pMGXMT^^uP-#}tk z%_Z@+MIN?8wW`EUAPcJmiauXj@|g=3+2$=TnMDGl?!okhC+$Ivt;=5331hX`_|ufK z=<${wXf~R%{uQT^+>##lWPZib#jcILHn#4oXgAkZ3-9qBH}1q*+I8qGG7<7*o0Vq{pvnSb7u>JdqLN>H^JJAkKS8x0o>^<(Ik&I|_t%n636AR@od6SO7pO+^M zyr2?{=99fhqKq7q09}c+DfNH#frH5!GY(cX7J1Ta5C{dybH__wx&!4z1em96m# zAaj4uZoKSbeZemHPo)%R3_7Cmw{vxH5C zn^-fO#7_)6*KiufSM?gm)`Wr*JLL(=bV-U?b5Jb^4V$gcNg}bU!-7_QS_bSmE+Ocw zogXF$s|luOQ_dd%l0u05&lypk%jdOssO?4OM&ru_1gSMJEsxV+7S;|AQfLYy{PZI5 z4i()wn-S=*)H-av;A%jq8O}W<{p7i-NZ@CR2i;=5_6SyePaSWaS((45FMnS?Fhqja z^dx4*C~S9j;qK`-)(j_V5`=HiEcIy0!8uITkc~$Yf>V|dRLVhlhU!sQIfzmrg&PbD zl#o$mD3`(|?U?ghf1ZQskn6ctrFd<+-&xKx%+=UH0(IB{U5QSpZbARfgr?`7s!U>0 zC4>v9)Hg6i>#sa_Vgr?jDgcK)4=z9K^)u>~Uw^9j_aCoe-;e!gO!(@()xRU~5iEkp z#Oh<+@ufjgIE`(V;3KCrO45+Lu7XLriHLH(XCswSmJjoi3y#VBT?mHFAuCU8J=ApJb4*P?1`RE}(t~(w*IWdc;*_#th!)G1by$J3?1(yB7x*pxqsN z4O#mw3<6Ol?1~`51@tUgJKnfVTUpscyXDj>jzdM|X&1SPkR->i@5R2~dY@sK5X!L< zrxg?q4Eh^Vg%!uvVMOv+c`{v>SBV^*HEQI+bDLg&gBgrQ+CWO!(*`{RPqa_;%**XK zMUqihl6Ad~^D88-AUrFl!}YqF{FbVDJc@OC`Wp6Z{9KU&dEKx+9c^vWhLzdFW(-do zNpBXMoeJcPv8wf6O^_k~!kjWX01MuVHhd?x_{F7%l@`2~C`oEWl{GX1*`oE74stB?1gBXSe6wZBeUp(W`}HmC z?GE2tGEb%ThG>jjs*RL*YP<_pIPw53iWQV_`^~f67TqFj{A#%4Ej*NtU0RCWOZIE33#Wqlp9-YL$T`3-b%hGZ%CfOL>g`qSz$;KUf2S$ZYRK1TnfqB98W4S$PADOv>+QOH3MoQ{n)Ratk04F5 z{gngfgebj${(vEW4QIPZ61-KW(6T*56A2${9VTuw&wgKM<>mhp#JF=Dt5_`DA!Skq zJ9S=Y*ba-Os>J{(9hD)Za%8vm6N7#i{EL`<%8vE=iR?6gr<&2NS3-c=~9kAa)6e zbr$WoOG+^7Pv}UN-Jw&8iHI`uuLI3%V!-M}b*lMMQfshD#HOl<9#F2T8Og42$la0{ z#m2mbX18xPH2a*Pfrl&$BtCU#aZvUF6W{B}e6wYK@5cP6^PQ`*FO1d`bq1t^KVr<1 zVNsBhA%4h2mQm!%%L;witm_aS-Nf(fi;BTgkFi}a6{e5?fj^G5a?fFx*ntU&qdK-p zL8FSRP#oB2%g6ujM;YAz>D%LkC1h|=71|{GDG~M7r78q4^KT&yq1~ z>XldXs^omV_K@u4kTg0YESs$8LejgD>U1`$ivxNlZ@pH70PCG8d}BBeote^ZE}2T! zOqTL)s(ILEqpWDR96T~ZS5hAlfl&)*9v#-2v2L_*#`X*g-5oAxg9MA^vLU{wa|mWu zb5l>5e5io4y52?hQ~zfkwbs+2C(m&rBhE@Kq@_Gq-O99b6QHaNY&x)Ss{Dk z4VzOY#^-7Vui484txJXfTE9%K7S&qDW{l$E)}AD`hU%C8l)@x4i!Dkax2IIT@1W+AL) zVA*~!O)?`$nq~!0)13Kz8}kq6t)oW_YN66c?VLlC=l;n*%@ak*TK7BNnCrKuE6&pC zF{#K^Mz+ndSqP1|tz|z5bz}$egI{$-naMzam;Y5Em0Eo?rN=zp)bzy)D}CG)@#!vY zw_i>N1pPICVc@V_?58c+b&zsDGuD|Bhn8yIlqZklW}BOeomTaAiu)d3*;8#b+qP#l z`CPqCXl8ST0e1y1DoLVu!v|OI*htXo9kX4))Q(LIyOCvt93@}5$4i(OPLUa%J(Y1L zptB>a`?~PHNiOHD>}mU|3tykz1%pLxZ$Ra_@i8&V=}3nY$wM=?%8}TOun{Wb1=`E^ zY%B8LfC=G&dtYG@EFhu8WbU-Ayb7;^QO_#GPL-7yFgh=&Tr|2Xm;nDvea}!8$*bS{ z3RsZ$Ccy5~oaoM~a9^rX zgJWlH)>?)CJHjFL-0G z6wEX;v4~l$GdI7iQdMT_CtyRDUhXlkhfkQTu8|G{Fj$2*gAHR%xlt86x&vA784H#G z(nR|2bS;PB=+AH`xv^u#ZfLNaT@)2bm~LHaIQ=m{*NULS9CY4+cvL9s|8KJ$4=A{3 z?qZ%kY)T)Dj@~Oi^5q-ou%d{R47baQfV4U-X8)+$7azcm0xO~99fS2#WG>8(@@R$O zNF%wxXVnuNijZ}PZeACfW z({tKsXUal;iHnrTx2hnvD{!mpVWX*cd0uK92Q4d0(bldzQ{xt@!*8eo(2Rg&O1OY{ z><~fBkM;kRY-jp*F|R+i4vt{#_RI|{7~x6QPZdUhlYqtbtUUslYv-njcf*cAs#*0m zhGrb<#;~^!+at0_v_^D#(8>`r6R>(XZYqT;dzd8o;&2FghBVxYS$3leDF*QnCM>C%>XyM<#vUKFE2=t=~I23*3%X+p8TF< z6Cx_=FoZTe%a#mb+e$(qEom>8Dgb$$i+kn!rTAWfZVBBY4jZzDTgPlgyz6cZxzlC5 zpi=YY&dc|_{GM0HKcpgEObS!>0{K_=(blheq0CQh%s-xQQt1*Ik)xRHp)J|#W!cM%hUOzv(w7um;`g=tGFD8{ zG{th`nYE0`NG^pSSd(SCQ5#3n76f^?h)70B#18cFGrbR)fs)>no_w&U9-Ev1kEky5 zhx!YuNzE)Tk(*03DgUsI`qx%|JkLIx7XZNiX@Ql3wwyD>bv4Kh-Z3l;Vk~VECB|io zH9tDk(h{s9i@aTBuVu4PIMxS79-Uz4h)1~=`EfpgsaCaYs4_YILp^eZRkD^oN zuj$=jpwlyMkRRtSjEj@}8Q5%k1ev?{Fh81>VCXZVX1cC&3=_WDN2_!oK%q6|2u+TmF z?J6|VaK&ijoi^j0X1{VugA!0v0Isja%1w7M$eB<#U%cX;%MHJ@mjsU*=Q4O*Tf%Kc zKNiq??<@ZLK8@`1H}wy>SPEI-GQcF4V;#1GX@&>uY7(LQCM{G_gW~Lbh1gOV+is_c zt&D8+knllI=`bC-mQ7h}u^WH<-d9f_F79LLr}!@tNDVa=y)r-|QZJ|^0Yz9!Rm)Ty zO&CU;DlwO2xQ+tpDAH@PHg}GdhXu;TJPvYyytdFY&X z&gp%B_F2C_n3fPzzi-Q1d6$K5%s!}{*GLs@1ZUk*-r5M2AN}u)Vjrp(S+Ybpcsa;{1 zA)fp@V<+!T3=t9YHiyk~Q^OisxlK!$vW9UQP-$Mz&FXMhL%r#>GMF*Q?Gy?(GaABF z?NEl+YCI>J`>u=VUx;+An4S@npdvzdIdc5j3aD}KWxv1G4(DrN{o zZ?n4ZGju1Vl2w!*RWq=QrsQ+VuQXeZRx1ACCQiQ_o?95@yOQi!8$1LZla6~@*@UT=nU!htJKm8}%@CsAmHb%w;UpT45R0?ZSf)E#0J7wN1Hjvmid( zdiAf^AB%ryAmKA1dH$jPmu3jdB%=^-NXSim>f4zm~{NufLV}pznUqzOG zyigaOAV}j)8}nb!`zcCi(QF9~c5ME?zEwjj72AK_bg!DZQnnShx-|bb_wsViNjY;X zfxs$@#p3sZR)WLreYJ^pT^sFS!xU8AaUIe+O@8h92w|@7BE8>;nCDxab%nl)=suR%jz2Ga`J>$D~ z?x?s()D^7!;d~X51vX*`a2Be^aUh^~2%MT$s#vP}ZyBfIKK95234!j}D+Av5E0V8{ zqI=m3vh7b9T8W)CtR18wC^^%pgp9yU2vLw*IyPkSzzC$?FG0-B!i0_WAx)6l=9i`( z2q`GVTMhPBx?`C*1Z0H>6Cc!xTWV2LC0g>|z* z_`l1k<4QfC-eWO1rukK_BU);ti9L2oXpwGFc|xX@zycXT@l!HH>qAd`y{0x*jOY#M zjn2?Q&Lgfv2nafXk(Lh%)9Nb5)sGj_(IBg4{1;Hp*+Xu^YPAMAy8ef&m|}!uB{C>7 zD+Y7JRhe~QB*{`B{ZkSy@;!F|OttNDd64#%uYn947urn;nAhoq8=Q4*rKJ4R{dZ+8 zB2c=y{d<%BTkK`uXJHW*XwHxFfLG$StRbpKn>-W5wY)lo^av^`Y_mP!`;f*mkGN4q zhuV_M`{K^%uIM$Qxjg5ugB>cu5Ns&$2U@^>(IUcjB%O~K-OdbAH(bvdCYoBgUR+!z z=$5=YH@pH84AqT1LVOZBByqqO?AuQaBj`G+&POu;Mib&@{*+ebTOi!XmI8G3r6-ed zwM{uHhA(1$9n=%t@(I#GZ?uzD8z3QQ^@4rME|kj55@p~PI-Z0hvVzjy_c$W8X*s$R@`hQC#3{lSoSPrv_j1QkJmOhU` zNf%F z=va&*h*;80O<@ER2{eqwo#p!VRr&^q+08w(4Ww>#E0k4q{OC zMeN8%2=8xEo(Y%8m;e#0s?6UG3V!iA4ED2|{^lWp)Hsn35^74!2?>g4fX||e#)d^b z%pYeV-jPzLHc^yXpzoE~oN0CntdhaItMX(vACNIyJq})Z-#7`(W$rj0Ichkfb5J;> ztyibYMekgj5N(Gyu1w3WrhHZtYvdRw+K#*VpcPh$jkmowtk;^;fR%@CczJ0aPkjBH z{GzJBC4u+r%3GP=18+h&)$Lc?DoPvqE$IbsB% zMRQHB^6-Tt7Ei{S*(r-k3yGuW73ma&ZAmf<5Fuy&*fSAfKXbtAu1HotSz=J}P@} zLsT_I2{i1v#TDf`X~PHks%Ke6pp%#3IZS}BX>O0>Dqyq&tEisYH%Xrn_Va(V>67Ww z_wtF=jsG~aG4T1h?AC3TZu3hewZyxu@}DiMeBG+a&sLK4YA_^Q5W`831k^ zti9H2AQk&Ci*sjbKL*BW>^bv`$i}_>7yFiaUc`Lm4_ai@^wdEUx}(rbJ=-^X+SW;m zuj49BlJlT0GXtBrqYx?r`W5oLewRhWxK%At4j(&q{Pnhs|9|&b#FkhE*8~VHH z09m%}pS=!?R0XJup|yK)@R?dI4uu+MKKDG^nq%qsN@0H`-3%EPS=k{VwOK8+EeFWU zG)&!X4kWY)wBy4>W%pQz2e5ltb_3}XSFj*oL}+b;#N%kN;{=ea87OfgyU71B9PFLI zG{};syyD_Kb?ThGX?FW^zelB zBhgk6;;Ts1FvIHoff~<9ap%2VY6*o2Lk@%?%2c$j7g`y1hoZ=JmY~$9CB#1nl|I*s5VbD!1 z)k#6Rla>xvp0&Bwu*l~)U;lfX^U0VMbflLGkHdA(7ZyN`gJx2_)Xk{q zxeajQwmZzm&r5uOzR6IQ%C?O|@@VbJKi`+_I5|)9Dn)M7hwVf0%?QqbYpDoF^<0+S zx)rIzgEW&-3xfiuF9M^3PJkn`P9v*#9XSY-)3lAjn2r}N+qzY?hag+{s_)01EPsC_ z@=>3SlU88(1Wty@%PO5a5GyKdNrT0heNVz*dUh915zugeVWI*J99(oXmBBORXz0TAO)?}RXW_&$xAoz7h^AbP($&LB9^Zr%! z3pC7pU)va1oKCZPf&9OEJ>_|?S0>0;-&1 zj7TZ|L`8QaGL*}~_QlWj9*BgKLv?P0@)=8&J4`tRv>uot!g6||TR!9lMN5MeCId`= z!|9jBU(w%?+b1i#9L39Cw7j~Cx&cHtTAYDy!d0VrgE=L_BJa9AJ8dy`YrKJp zzoHSR6zoc6^~D(qZr{FjFzY`(-Ocr{)}S@dGUNs&Ub6M`4}Fx#@tgnLguqP*+=ReQ z2;79gO$gkCz)c9;guqP*+=ReQ2;79gO$gkCz)c9;guqP*+=ReQ2;79gO$dCq5ZH!W zXbc$uD6*(u z>1^qRVjqbN(YXv~fiicZs$Eqfn2FnjFOwqF#sy37M}_O)=)texwKW@E z^epkNkhdHziQy|6>Bf5=}W5Z zItv|Wu9Suiqsz1EW0_}rwtnE*9upq#cnw(Khl%chs=8!87xxCqZ-+~$EGXOAA$43% z?+vZJTJEqPXE-nM#;HRArA;imf;yV^cwIhFNju)t09TXzH{KVUeWt{bGXM0({15Yi zRi3jsPOFgP(~w7*c*~0`P2U+UQ&L?lvayI$Au-rmcUT1}GW)C0`H`dLfI;2do}PQD z76!FvtUAIt1^{jN8ilMt2+z|=F(}pZ^bb@9s4}8G&1<^*PIrmnq@>}26$kJn5V;Nm z&B{UiezqdgK_j)|$to8#+;4y=vMW?#G%~5vC8-u*3s(Q>lt59r9a1(GMd8BFs}Sm?f*=-aTX3jQv1ipXs@v7x{)FCEcV(TQ>q`y>}8@sR3Oup}({9?qW92&8r^a;b%0ziGS(8$zjB68sa6^H)}SmF<@I>dT(A^I9;R>$C4eazYImH|B1y@Z(uIz4{1dsqBy!{> zHB>X3Tr_#NX)$1x02ZQ}gSP`;njf+gBwCJc1XknTxAoeNs%8!xd|~#*Z@s&G(fj?M z+f#{^M3L&u7F&9S@Y?9Sg{&GnvBgBA6*H88$!FDxDvM#Z*RwebBUDAfF2})LgDMHp zJan%PO0esz97%;wFlE9$o&~Fff%}V#l;W%y+|byiiK}CT87?i&bne+Ylx4s6jYs!f zyJaJ%mpa*`y;&gwFV9(H3uCg$tL#e0q%{RVjg20^Uv9jzEu-bQr^de0qJdR{ z(4sOZy-fst#F%F9Y}P`dBhg{Ayr@`gW=AJeIQu>l>i$ZP66sCof`8*1;9PLDfo z$;h;U(9x$nHg68x8qsiY)5tB;>Gfd2i4jszRmd$pq4#Vdr}XAn@0%o>0{3#~@DfR5 z+6?-#>cyrPR^?e<7hCv<9ed&mb)KaPaTwEuwpF>6DoYYcnuT8Wrd?}a z?g16K_2mF@eaoGA^^jhD;A??(j_}V?|!X zWEuqJ&mgBcRG;3t`I$tCZ{OVE6gNFDrt<--?2JZGQ#%@9sl6eAmPjy(?UgOj|6nb% zLYn6_-Pw8rO@@Us|8|Y}42*8S=5M}ZOzs_e>pL^K7EEr#d$zt$Pd_g%cNQU=&NGODW? zB(0qF0;Z!^Cl)Hc{~>MbEo2+aO&Ov@-FH=j(we$F+bvJj%k}`}5NN(cJ#hk%{kt{d z%#dJJKW}IJhkD~=^NoOi{;M1F7jM|#xUw~iz9|Dly_Y5%PRvX!*lH(C1MTvr4DkG9 zfGqzmWB_C2&rb%hd7py}u)iWqAw(#NW0eq5(WFG&pa*K=ZnE~m4d`5RxZXszN`8Wk zq~a>;jjXnE6n#qR2Gyn-h7zhe$?Oh^lSHmHVT)3+k%?d`{dnt-?uWM) zK-&JHH2qPmZ)$^r3QXDqY{WA@Fd;{)5Gn(IbnD&OS){e&)&xM7r|CD_*79|~?Hl0P zv3V=jA!+Ut`Nu_?U!1R}Sz-5vrC?|@B7OS!m^VdFHI<~9hS3Jom_(uc_7~)PH*DK+ zL!!FLiX^F&$;_giFxc{(W@Fh+yZwcLV`yj<#HX>3S4{IeTkH3q?p!q>k6PW7 zZ}o3$9X~gGr0-$dktdJ_$8-&tL-i6X~{SZ)e>Wsw((t7b@>G5 ztnH%|^~N_7|J}5G)Vd>N*(ZfDz7bifKw$H=c;f_}N@^XCb%G`sb4wuF|J&Z%!0A<0 z_x|S@CYb>z?eQN{jV=e{1c1&Ut1gylDHm zz5hRc?k$<~oU`}ZYp=cD*V+q`KX%PV8;Yr)U9&J0-bqWRFD#wg)V4L=Td(8E@gk;G zE`N#>vD9kg=5dIIhL)K9Geg!YQg;{!*Nd9r;U9%sxS3iW6$=sJOA)=|nVA}2=2v%y zR`~M_AN{R$`Bl9j$eZQ*d{&Wci>7I)c_g4L2yFwQ61!B>*HBe$90GKzxTNyAZlLEL zjKFf9+L8=)M(Lq@Z*sixe!?|eS*gz54dQHMbFt~k$5Dtr-HAimiz z4T{bHEB%$o(iN5jlJ?T@OwpGN3=w7VngP3>l{&kF50qL@Oxo@zOd4V~ZbI0_RzzE} zI0~NegA$77mUXUZHwDmFPLWNsEMzozK!JZ`(JT)d4A-*vi1&yD2b>$oSK&Q(fqzlEa389Fw1DhT=A-3K8^n&s0Q~rI zNBM#2v4fkeu2ivN5Y7MS&~J}eyT{#!^p-d8%L60nGC8AYr^#iS%zvd>V*rhyHm|;I zOj_wtIdAsX)SS2QYsOWlYr?vnVZ=(;R`LPD4>4L``icBUnaV=yf!9Y88iyBF>k|WY zN8$j1i1B^v)@|`vOLuf%+%wSEv!nOY&DDOZ>x3)u+)m9jHl)mXZo9e@o>GC|LZ^X5yUNj~V6I*e@xazp!*wQ&Rf+qH~hvc8T;h zm~$!r=hEeR5R26pB;RDZwqsb-!LzQ~a^lsn2o`PH(cZ!RX|jNbxRtPLxoT7%cIwC= zT_+{|)$}la29S0wu)1?v6pbOc{?&H7Ho0YdG6NwZzxhMq$E*N)lAi23XG5YI{u|J& zlHQ6w-^h?+4qgAC1q`zQCb748S)lv2qzPF9I|Z{8S8mhTU*)IC>7l6@l(z-x`di)7 zsb~?+NI9GmW7Je5-@Ki0+9&^ja6onZ_>LO0(fLAToV0?RKL3FIA)cakkE46-bGFNB z(uG$4QL!BhdZ{<$WEdyKEq(<)fJ*fevP5Wku6?!->k4t$Hyq+g8k|m2gsWc-mhSUeIV7GNN@E59|d%Y#6AS19#Yh?5`b*G2ntke?5VL0Z0 z4enkvc$4?klygxtoP!c!k*@(eRoPvYzP@gqEs5@vAv7b+E-iI0EbVD(T?6_F>-p`q z_3tb?@w78eNP?636%(Jm|AW;e`Hkoz#q*{gtnYb$lANE8U;oM5 zljPBxpZ}-dy(>vx`i)n7{ir#|o1uijls+LnFq zmoLAjlx%+Sw}1DSZ!9Ih@v+gRYu;T-&iRGkeD*8;fbag~lke;Kd?`7*{d*f8B(LJj z&wBo%tClt;BR4O3^fTu+B_Hm4&nGVDq`vc^Wf%YK^-alr%P-mA^Zusfu^T>o*>^wJ zlziwL?|=LDA2%iMZa;hXYgV0*{P@;Sj{f1^6Ot>R^`qCF{f-lo4}Rn?4xI8oPe{t| z{KE6zN(ASNpLySDk6zZCto_Q?n;Cs0e>T&SeEe0v z_W73Ywj@9P*7NWA+%>Jq$6xlWZ@uTS*5uP)`t55Uzv;x}P17&>!2ai-lnj68&o4dv zrza)no%n}`|D^t`vv#);NDakoM-1z#JeD2ib_3yj( z)PG+5?Bsbbs^0hed!LgmI{n4>{mEyZn;aVc=c^C)o|gRlnV;-F{i{EdH1EE1`PFY+ zmOTH$*Z$zY@BZ23Q%9D~EZkg4x=#Jkmw)imGn4=N`mq~cJ-Ix2%{_1X#7FP?`Q$n8 z{pY{`y%()WUh}pcFMaTjUzGgTc^|0V`sy^f?`!w>{l@6(mPV=@^^o?{k&IQaY1tO%l>Ng8%ti2+<5zSzqh4zQ*zsN|2SE_=;CC}mzTfo+>gI3 zx&QSK{9?EV}i1L-e+`_$j4CTrjP=AEV6_9ZvJ^`Xaq z_4%X89jAECs|KRI?;>HQDhetWX^>!pu=_HXV=D$m(*bn~5`O4i=A{P*9u z>w#qAGw0s=+jo5}IW}{T)qf~u( z$9=zf-8rQ2+Lty}r>obk_@#53sz3VX zJAP{sg#GnzEPK;^uWGvID=(P-AK(40rdzK6;`B}L{9My5`;&KVd*ol6DqmXh?z4Yx z)d`g!?>KnUuDvH5yYVyceeHjL`w3sY{I&mb^{@Zs2^&uT{GPWy2f|+cs`GCdyR7-1 z+pam~T@Svk`PdzAc+;D2|7!EGhfjIwhnKHfa8Ju;CSLQp*DOe0dFm%FfBxSr*znr% zhevjtzwn-k#joCS=v@mpJaTj2bMI?ux##be|4!+D9c{VioU=apy7G5gj|eU_%#)72=dy{1fBxQ+l7}Do=f{6x@>$1D z|G_26%vo*6?*G@}$DaS~wq*3awckGN4;Ljr`^H<}Qu+0hlky*3aq>yOv^e?eSG?=C zk6m9*{%H6kUnt+SB>98g)jxmkdrwIo8~WaF|J7fdnmq7G^Wj%4dUo>FcN}SIsy-(v zU%2mruYB^k$(Jwc?7ni#Y00<#=EEQT!k2y~>G@>R^1t4&Ecy7|UwwSZl|P&O{?%ul z@iQAL$%gA58vU~^XC_~M-|iDm9b2CK*YAAq@yCDf=aZ#pjt~9r&#y>^-+b(%|9sbr zk{f>Mw?6s!FQ&=LM+WZs_5G`pzaILhe>nR!Ym!H9f63h+`->MR-+j|3$`|)tkZe5b zXV$Mj=_Sd^yxdY{+;`WANjQ;x&E7Pzx#rBCFKSG`MGai2FL!& zN3P#;^XBBcHNh5fv`XKD=)s~f(MeW2X;TQ?A>2WCSH2a&n*7( zBR>xR`_O%68JRRRBQc5>TvH@|nm99gd3X?u7fTw!O{NaLFM1JpgXl8hJyRPH_tWIW zC~41LG;6n*qKIBo$9Y3Gj5+c{|8WE;B6*NrW0`b|!@ByefHc!qy786|S3Hu!=?05_u{UkDkLzViu|dDEyCg7~kXEhS{b~C>=~o#I+12 zl!zFv)fF@izo2LNZPJ!q1so_CekX~+ZHu)`ACgUC&WMmp#vP#13}KA?550Ljx!cn zFuWJ%&MxdBlZb`b3u@P1F~ak&nD3Q7jVq{oYvvO2i>>8-ok^3mYy^6tU6}*1=(Zw~ z55WtWW+ZF^7Kni?vPLsnm2#{b&oX<$y`eQ|hg_KumN;NyCyAN_P+Uh-(3oqnax_EYXAKX(p>%NRW2JG_{DIS5wclc75mncO6 zO}l!Kc2Jy-eilFy7UW=Ae_Bs<70HB$>Z|a8j@DMirFQBN>XJvg=co^nt}zj%>{Gg? z=rczfT-*rS+Sb?A%c|3_ODgKq>(=29;hx5k`T+X*)C^7dY@a|K;6+wIza6L=j2ohX zM>J%+eOsjx7)3bM*h%pe%sFB3x+egE^ox!aLh5;IfFU}EGX(poA=OGp#yXZ262X_0 zfJ4Ax=5nj9a!^Aa+g^NHVMY3V*7kz2j47U-)}9)CXLofyZ2;NhpAwj>;vYK zfx9|Ad(GN3qEu_kP~~RcURI-8#Q~fU8i0H7Tx= z=hK^6%t7M_x=n2OB{e|oIls3+SKVhudtY|dz2ySq*X38sb!0oF5 zuWpz0fE!n7gt0jARjKATy>m3XgQrd`zKiQF$M^aI6^(&r_l@kIwhKS1x?WYY&w-7L zsXJC&z}*Yn3Ta`gHHPr)7W+pRtx2{jW>D*g4`zE)n&(;DhU{ltN_0QNdwbc!wfHRx z|10<98?0kTzJA@anN3gz4~h1?K1Mph0k68z-y})0lSUjMh72iHr(|iF;^RhlqvNJ2 zh!N=Oi2KDf&2t1wB)I~Ccq%af$i*9?R~+l$6YUV5EX>IodKcpL@i?amROwQXjVU`g zCsh1_YLe|#P7SQ-%oS~DAb6}EWyMW0; zltxTNpbT;LaSjHQpztZ)ku{H%fp$x02o5-wh_^O{X?EixG6^^k9N6pD zg?G4h#JzQwaGe;gBZ<^-M~fYNQ!9XcaB}Ymj$B>27;k{+DH+wpENX-W0BMah-*kVT zfaavoz*UR`%GPCn#6hJ}gz-jT#)rmvW&|wtZ5Nu3w(TtQoENrjN8hHiL^q&;qo)9v zPJv-5Qn5yoLNEL=iI4&Q;!-Q?4@^x>)Yq+AwI60Ty*EV-T7_~uGKHIa=)fvA9k~mz ztGG;Yx>jGcdi8m0&b}yF;alKmO#<4O-+@7HK#@Ku=jK-asF9WAj2Xqx*m4#&m>P$& z$gVu3TZVR`*&6U*j3Wlo5o^Hj37!sdN1a#q5JKu?C6ZU>6ofE%cX0SHilAw2kS`Nk zO~5fHiWpbkH)waW)GOz%KHFXN$uW=Lbo6IdZogaty{WZ^hs?~Jdzv_H4GgR_}SH+DYD)D-sV{b9*)L# zBv;>=Uf-@E}8`9(A zr~pwjJmQHLJQpI>Jq{h3wv3NxZ5I&KxWOcME?979=|==N6enxICFdGZXH1dKw6HO2 zX+4}{Z)S6Tb8r3f)nP2%?AsRD2rmCQSNj@9JL7g1YD}vgjzaE-TItB*Un{V7AT?7E zl(`1~FhX;uM)f$r%p$Wam~nYmMt?9JHM&pxF7N6ezkGFJpx$AN_>}J~H785a7W-kn zqey2FYS18T4Ni10Urb(`A(D0&x>CIyy$li%?^;Ib{5UCIVF&W`$vX|*lAgu@=boFE%8E-(Wcg6X~Y&boQ>3Sc>$9) zpFW?;Uce*V%uh(L8SHpyy9XQ5nqFO)z{CnQic;QK-K@$7k{rTsLG{rRFm8mNcw$Nm zPChSLzQd*2@L(zb=hF4@=``uI5KI>J#2F)`S)k9VYJ)HwCga5z6(-a{;%#~-W3Ta@ zL5>VgF}9F8u6=`(>E?WzUdp$(j876EZ7QF5GCTwwvry43ov1cxg_C)mSR8kHH0>ZH zJ#_FgRXd95Jy2S~7_n|LxXGf&HGW2iU_z+YH5hA&qy*RDaW{z6bS>=Onu`?sh1xf{ z>VSUl%qxvL@ilPnJN+s*o|`d;_JYRFsc}mryP()S9CB_|H)|#aHq6?^2)U!QYU|L- zF(n6&4j5FtTTQv8zVMkZGNdJZ!uMnzd^ap0w&=#o)lp1^2jet=Z(rbBn`QY*aSC28 zoH6t7h4wk3lU+cy5Otu})eq>pEA%8N+u0VbP1G{dAOhp7_Js#X@?bzkS8_ZCm6z_k z^pYEi;*KH~Co8>uTXveBtRV*NN4pU?O{PoA@1{pLj0l*~k~L8wC*7$(TN>Z%B5u>T zbMOt+>h#zoZVr@43}Ki2D>l2r2$;SEQY{A;mab`9x_PIvYs}y|Zkl7fP!+#XvHTjW zKy=2>NRnv0vzlX%Du>rFhfOHLNaV&gnNddP-e3Hdv{l2rIU@M0PXW}718O;s$e0gGvN`eetC~||2%4Lu7qAYy=VaA^) zfxvLs2L#^q*q~iaLb5`;F^ziX$)o5Q$uNmHq#w-lKVeNiq~ibhfP|1l2D$w=Nj~$= z3zFKOpKky4|HDr_Y}*RjA`dkJa*xX;ES?C8d(8Z~0XRHEF6zjTZE+AnL2_%kP;ZOiCjQOV4giN*A8IHd(oY<@L+R8{40hMi-V& zYbAFg*#H9oRbt2_5%F7T&XZQ<6!8mUh6wt~d=J?q{ z=Zpa#BM6qm!ESu2x5?GSSyj0L=akZXjMn3&y&4JXy}2MgHCE6M=yq>Pv_cWcA_{Cs ztF70SJ5#q+ky~3s@U@LNBM#u%gpA>VI`>T=R5EjsfQZmM%}PX-e??)=K)8}fF-MW4D!idww(8+2fA5|!KqYz~#>dvl*w*c_ z+&Bb9d)y_fZQiUUY2%;>JiM0LoUB@ul;7yf3(^ABnxBM3t;w>N_`u?&y%{pWqI_`N zfFkw`Po`#^lQdsw{GZM1v!}IT;%1ad?QI1Rz6c6;4wr?7*PlZ8V2M;cvm@6c(Iu64S zFT}Dq8_*;?Im_?^oi}j;`*${Nnp}flB#$`Zc-{?rV5^ zB`eZ)s2H%>vxRH^{3O%bc>*Eu35pm-RX%kMD&UE?k!x)6-k z5Cn`p=wlO<0S7*^)3suxT+k7>gom;k+;1pqH;%`RFsdj(j|nFW=O~*Yv7MG)M_EH_ z^JW-YTwvZ0rTo78A3imth+N#}4-`MHSWpZ0 z7{_Ammdvf^DTeUWh)!l0MfRy$q{wO6t?-9X6~EMVM*CG`BFmD`T%2+wsr)rrY3gnG zEhy6>A`3$7D|~Z{d~xFYfojb{*f&kR6GUcW*~MPkw`&Zy*z^Rq*-Y2FUD$!*U*Ram z@~VjAsJUgK!r>b55zr}&U_e5***ho+^ukg?IfxZ+G2I_Anc=w+gez)$>r#?S$(w}e zPc`gvgy2Yci7lE7LFp#S99owu0$N;KFAh5tfMFfc(;Cl4t^o*_#pRfbsZf2O1Fv^N zcuqxt0pMMehlooNI4YNatSR1NfXCC9M@t3~^R~jut{vObl^iNOmqusvbiKIC1_^W+ zb*5lAcvXftevZk0;AuE3#udY&8zIWq+;~#fGen@&;>Qk2Tu%ORBwVO1aynJ-r=@qC zd||Tw62C!sNb`Zt(Y-W*ZVWR&+m*Uz@pSxenZqWrS-ywe$Z>kMtO&Ny{u z3WQuPIb$qBA5ybSxzQem@v7$H90=-j)i@+v5&t9O2251<5X;+J8yZybM@&;_uWI4} zg&2k>2agO)kL{(jQL!k80J|8YkM}i~yHT-P4rbm9ef`4v3R08&yMb_Is>TGZR%uu= zMpNY^*C}`Bq%MuG?y?E_S%6kw$c10{>t2df+z1@=;jx(pb#q+^yetRHEBH{h24itk zVLrWJ3x1oHV5m+m~@HwO)%7RLclTepK@!lSSm2-LF&~FZqBK1FT({ z<#-DUryxYhMwJ}9kvA+>BmDv{dS)G6SfW!C?I+LY%rr4O#s#>ZnzZJ3v=p=0gB4*Z zRPjqqC#;iop{8a*l4)MN?B;3?TXjtK5K;?*@NBW1?V;*Rci=E-cj|OtgP#Hm56n)< zPf)mNQ%lekK?CMYdtMj01vvcj3JEr83>Kx3h4KZmaW;pq^AFdC$pZ~L2)*-x5D&4T zB^w|^#|-+%=v+92PE+|lo{2AM_u}yeoojGjy$Q0Z1tAJBQcuOCgp>eRUu{x!WkwM-WLo$OX3J8NkMfpWeZ<;d8IjY3{NO(21Nc!paEg?qDplhsK|mE zquF1*uDk&JCW93h?P%olzAp9E*`aYiD{+caefHex%D#MaWJ)FPLJ+rYkWr%$N7xOj z2a1>V?(E*uqjD-DE!A$#5g>P1k|azOS}_Ul}`~4$IEInPLlSC z^Do0kWL8DI6qfZ9;blGUY(vUUkF1M+%6AJls9YCwhcX&BK)S3Hk2OeeS1P!^U%$`u zvzd)!ei)Qc7`#ZeBr}6LCk{tCt7?ZhD2m2luwf@;Ogv| z+0p`2(5X2Mwz3_A?4u;aVxGr#+QawvReEqg!M#oY5Rx+?VUo(wlX((? zb-ZndbR^o++F0Ah#}7_V*x^TRmA{0c!cC?69X6t$@{&A_h94tHMCDkrOX~AU@7II_ z5oNz!&TB3+oy$5-$INqqh8 z1K;V1(UHfT&+mGHQwbiZE(kWWcgkhrHA(Y@jntZ>jgkTq@dn2TN2)E0#D%zED7BxZ z`QW3TISTC z^$X;WP=N+*plXO ztu*IJ`%3q%n3hw_^NOv?b6FXED?vSo2~XJh3PpIJKi-uf`Akb0nrswG*Gy_#-X0ZL zF2lK9CX1Pv4e<|>T{oL<8NCj1-tb92Z@Rp9xPRO-aV>}UJ11j-#E6>BqiHO>B3SC^ zwLTUajw{W%9T_K?Zz*W6`kv`9ST+7oJuq)g!T+a9xg>@z+#-jE$qO z&cf1mqUb+5c|)?^y3i3kmNAISvNZbvaThEBYo2sfYiV(GdInp^eCjrYoI|X~9w=AQ zQioYG$q!V2DX&fmBi`#OXq@*UdlGvH@X9PWa)*@sF0r(TQ!#y{f8b`e#p+Gl`Qh6Ve`T8qtadEYc)1`Z2MOW#~r+SR&j>9#&nlFQ{2qyk&z-3#mp z9vVTHrbqdV;_<2_BiX_{+%e`LD#I?JH*SiTDn$6}X!=tJJZLKO5>$_l99vH`C#)-#_vYqRL>}ylK!_R(JWnyO4AbkeLe#+jGr3H(-la*WW zBM~hXv6PB`l*?PmT*M2`QEt1(xMF1CXIlHHz`!j-^i0mD4jd6w4TJ*-Z=D#ay~!%P zt(5r*%#z$|SqKav+8HP27HMIleo%ip$ic<}Z%Zv_)dUJ%(E>2kLP#FSLzH=ZsM$B) z5+gE84h-Zs>%xbktGe)2_{7{1o~6^!x-{kmtPfrUY-a=QC-M?)>zN$H3uA51`a+#< z9H{b!LctAw2=iiC4>YT^u{gf9nj}P|L;@CUb~{`n_4`}aNv(B<`CVkTNkT__t5{; z_8fS0eG?fQwm`fa2;b1>&5{ZH50MowQgGv`Nz9B4j=j5>AQ0~k&%9o@E(}p+B2QWz zsrSWX2qjh)JLav}jPJSC)lK<_x8aS$D; zy-;o<-4?}jb91?lWDYPZ0MHLZlHB*$FUssP7R=HpoRlVkOO)~R?72XZY3bREHzr-% z75Uj;bE0ebtF3Dz{@I->lZGnY550K)RNlzsNLSMlb)_Sm=6xfRc$ef##ZNm`=}@Mp zRUfQ?uqgDTJ*=e_*PJ;>L-Pay@1Uz3Fh@G+reu1l>etHoA-0f>Oum!`GM($r(hJ}& z)SC#7aG*7fU^2Z5bosQUM=6(SEmxR!B6P2$ec96B2`VP9>wPsCfo8lFiTlW4t`>g9 zXC@4KzPp1OVtC4XOV!h&8<9@_jJw1*@+cFwFQWF^QFZx)28-jkPa2o4p8)?e9tSfD zN9xdaelSEu2y4ahMBWTcm;T_sHb9B;s~yU$dHhtw7Xk!JO1>7>TR)^cRHdI=}7oyDp%Ph(QQ!eer88ZRnNk*&eJk~o=hXnJo5lclT4@TD=d?@vvotMCAsBq&e(J6 zIpKfp#HWRjIoz&8LD;dG6@lzCnvcquDet13NH8WZ6%+bEekSk;+hd{`%2o0f3!VZY zNth;&Gr4iga2p)e)UmIIX9!4B&@7fZXe1{`v#Ei~T46h@VTr~&>QtlqiU(|Bb!I{L z=S|uZ5Trq*?B;_*wD|1hrUem45kbgiPNr|xT(aN zyo*3xkx0+7V>i5;kliwOIOs&k>zL93;%t9p>sdPHlkE^&oGm&nbuQkNwDpZ2F6sYt zExlaHsAjYz?e=dvcf*~NwQ`$>ak@URkrtR^X!cjNmBga9VNba zOT!l^rP=E`|DifPB`8T6M40G0DsY>-Dx?%`^vTkd`OiS7-;Nne0+BPd_(bh|B>;d(n`zrN$IhLrD@_5o0DbzNBfj8R*L^{I=aKT3Hz4d)flNfGwDV^ zg+#3BQg7H=cev$Jz#T)UZ_eFwd8UALN470D*cyUTG@8#B^OsN>E7P6 zvvNi6wr!P-J(d0)Ju0!UbnmS6b?@5QvpFrTUfh##Iq|;Qb_T*w4*#Ofv;y3<;a#LI zdv?!KtN|2!v0|6Trd$-WQGMa!V0DJ+q)n7kg3;}R--Er^kg&NlxwTT+={j%VeKMg7 z-@z!!>XJ2U@N=)}j z7$s`KzLmswU!FA8Njzz`n@WF|?l=~rzL<2OvP4@QJDm+RCg&zGfbNOo`z5r-EV>sM zFG9y8+2Ii~s3n36cMk_MB~Vk&7Tp0Q5jeR#3$T?Iw3M!EUAo3LEx*s=o>Q<(&nQ_7 z2s~91524SsFLBm3!K>pInT8RyetU?1v9{-^ina6ct0zLa+n>t#K|RAg!vFx!_~r!6 zO$+}Cp(GklvzB+UyPNbt)uap}vFP9IJzumb4b(P!n(S&_i?gO%9Y4PD-J{AGc=UwL z9KXt+zWlCV{d_EqYg^EuCpZCQy&#M#{LWp2RpP9$4AHnuEM!@rY_&x4w0T@KAp<}K zzi80$P}XAz!O>Ujo`j^>F|v*N5Sc28uW%Kk0;w$pY{tio&gk z)Fc85hdikhgLvZLjZbNpeE7M$6u^MciuATTBe>GJi?<}p%^a1V6JPS{TH9$wk9n%U zRr55DfAKBKEzYvrOzk3lK$Rf!1^K5`KdMK57~$c=Tb~>4S^nGn3FHiRD-6*>n`#La zlt?qV!eH!9?9rI^F=e-#Dj9fG3!nq227w=EW$osGWEPGx38Eia#Ru~YC;p(}<|F|> z$#Cm)=(K9pF#=h0o`jt(ldT8_R}mZ~^#^d`_qa&)eSqXizE3Vr`nIcw)JBz#Y>$oc z>ZN&~wzhBL`VBfUBuiRw)P2EtN$?tqC_^4;97RTiYzLzu93i~P$7ym`WPj!cFjuN^ zA_^7Bun%XP6U4Xb>X25p2CGoY*lA6uR^>ZPMSu)IO5@3lwN3d@g%Sb=m9C|{puOZJ z9aPzgd3mIy(-SXzT=>>yyoJm3j)f`ES&-MkaN!KD#^yz#Aql#s)(~ms;#{2T9CN3P zTD^*GaF`X!01j#8^hl~y?b$#ye5+~c#Fo-KT3fpu)$F5df(gFETMgl(+FICQPzHqr z6?MA07jaz6{2Z|i&70!0C3RSe`8eQcp+R8-=8dOX=*pvnVc%S7$1XsI461vjufxP` zo)9Z~51DwuQ76;2D)0Gz9rOW+e5#sm=oKT=bxV`k3K6#KtHje2u8;^w_|hwO-JA1M zuM!hC8@RKSE%v8hYU%vNTa)&Uy1Qhu6#lz@jr+rcKueO$Gt$A1FPIG$tY6R)Rsg*XL(=*9ci~Hzkk~|i)lOsrQ|vQ zr*)`eakhGOlF>G3vId#KI3Tyv!!096L4F;ROP?(D;#4V_2ltFn&K);oh_wzwYFE> z=#3#-yN#-u_N$rL@UI=)0#zKgbj`wk zhubU%ssFHIezzuJdyo#GqTY{+3Z$XQFBA7WM!orsgK54P*tOd(oJ{zpsT2@If6&f1 z;Tam4>LY-LeWSdESyn;Yea6nbpfydfQ>KFsv-kI-LYO0Q9M{85u zl3)W?FF=k9JoHL?ps$Qyiw-nGuv*hi?h_f7J`0Zn{I90_^^H3w@GQx{b5DMFYw zgdXA$8iL1*8{$0c&u6hyW1>?V=HG|%FAkUEAnX?znFw_m9w=?<2JDFi7RXYqL-xA7 z1=uFoK<0bJAMYy?jX;vwdvQa9r_uBrXx1z#d@>wl6bc{6jW@+-=Nlj*$y_BKijK(D zibe;)=JZ5b+SQVDh`q&KL~kpXA3lN8<-usP4~QG&EG_~o4Nq(`44YobDDsVo4R;UI z{jpy6@bWR8)o(e5gQ*;&%xX7gx*KUEtP9n?aVqQpRJr5L z6xO9-8s|7Xhg$j>gSn}yPq@}z=dAAOh%WNR9|$DNT`XS$S3WECLJ|NsqqwlX^%JzV!aw32kM(r8 zu$-Q;;W>*bEBo-t*Mea%n>f^$@#zB3<&*+Ct`+&k!4B>K!yyRa5YnW?+uljo%voW> z0wX%bHf)6Oy&N@a$qjaxP7Qj}z8wWL(VEA&e^=#SLwg)@=Hf#wmGm07l#1wd&Q)AN zIO*PLA)H{2S_?<575ygYAq85=K2Q-qP_uk-FmDZrA`}ucEUFUU%@Bqx6(=10R%BC_ z{1C-xMjuiO+@ltV&9D=MSMst13ey!VaHe{x@bBirzhxJFto2?Z^Ud2l)0MN7eU+9* zTS~VR(%i-c33)viwkMmNl7kAgeeYS((N-IVQngRG2J5s z(Ug(G_9>&ol71j}(GB!Z_Y3G8jxX0^ryV$!A6$R<>-gU#Nwr%krI>l}E~ze=cQPs6 z-BNmh#N{_FzEphx52JpT!he@>*(j%onKvPuS^vT{Zn`Mrw^w$H!3`oX41bsJ zOwM!MVP4~^2Wf|fkl!J+?|O#@`s&jk2)Vp(oL)zvdOjQQ-j;W1M0byvqZSKyoCIhk zAOXl=2BK`rC^8V}<41I(LUc*5?;RbTR&sT1Gu?O`)_Yq%sG(gk9*MuZ)4VWAh{J~~ zX&gRvlSRJ+;g(7ww?X+tV&>&f5#ykXA~TyQ3FZ`IlY`*MAo{yEEmjC$F$o*M3J|R? zF{i!o+#^`zjvi($a0+*Exuz(tiZ4R6p6l^N5>nS`{S6%GiFMr!ufa`ep)@DsMESsS zo4Gy0H-`qVl};q5WS-B`u;P&XSxP>f+x)F)W8EAb)$QWA8i#+6k5NTxCbBZJO&Ma( zplfViL61s}t24tD)Hf+DS|>l^=L4DD9$vN|3J4aMmcWy)zjShh#;IsAzDuI)ds_$= zw0{A+Z30^9F*bx&6m*nG$}mX#u+YBYoU_R}f=OSY$hZ|u3+wozx2`^~i)Mr}f?;8K z5-lG{r(}`5W#=T;%32wB%zZR95aRH5q=toT_CT!~Cf)>QyJ&AAzd@ojKtG{ks42RD z^Lq3--8(T0o@vXi%R_g3n|0bDV24LbEt$cBT}&bi)2O}ZSSX-jzYJs?r|8FTyrTzwjj3v_?QCUNi7I|a)mD#QxHvP+@Q;1I{ydR6CFHf}7oa0Fs0#4p2DBJ8nVX)l_xFU|Lv!nqjek zNB-ONt&1Y~q~R?1i55^icK?3RPl(NKh54#c)bvEY8LC-0u~bgp`{0r`s8u__Io4B& z6A-Io7GZ>YjMHC?XK;jm;F6l3u+F4RBwL|7+a6 zSMH>Js-|yFmP3)-m6;qNHafK2f@=KQY#-0~e`Y@FawP9U zDJF^c01Tm=4%ZH&8tV$@!udUl=mS&uI{Cyfr>=tG{wS1%4o=k-gVq2(xu>#uymHoA zu;;VRY6L(+g-spivTQ^fBHuzA>9S4wZ|`tdW%KhEpQaO2E;*^~?&xt|aT9JEq82dF`c zX+VC?!G-+aE7fGwd{UxJyN&|y{?ruGrH<>v+%^5<@cbPTp5^j}jf<^nNQcDaDSI<@ zL`79PBx>cYkYSc(?-Dm0p)nhcy;2gBwn3lt^l$YiY3cUGeaZUE*@1mCJAs?X&ScGa z%Vo(1Nlvw}9mdL1Z>`dtC*_Xt4ka>DCX$b#nGnj9)B=F~M-dA%BVw!JAsdCDLILkp z@I?FXaI?L%mA-=0h0e_%2qr(j!*gJSQW-GM)&q0j2M~2!5uyj zt93X+I2{pT0tw~@YQ@kLxU&@ky&i|ZU_a&t8t1<{*+KbY3~-uj=4Lf)7=;`SG8ZvJ08%s&H9=VUCn3ruYaPMlhWVPVY$)iJ$$G>b&w9a zy<@L%PFA35$vshy`v=o)SgG{`-j!pE7!11!H$V2aw;Q3xM;Ra2+k6iWVGR5K*7CQ|h;r{~EpnEwYz_8*P<@ zM#(}MY0cZo!Agh6MZX&1EV+9F;;IBD#ldI0s} zoyi)byZV;NTJ2f_{f&RNF7xMf3NosD--%Guvt?3PnYinbn+fSrxa#ocPFn0n09$eF)N_ShK*zkQrT9(vt?xgkxF(X_5A6m+5?OFfP@Z>u!5jyU>It<-wsO8d)eKEA&A#y&(n>Fc%xWpJC9n4EqhoN$L#efwbuKSY*B3`h~> zbWKPr8Za^6ywv{l=;9ljhq=wcIx!nLxfO0drW-4VCHI`T!Ful_u6HQ7(3>n6?guMP0d`#UY0ZN1QsHAn2tQ95?ZW zEU8GfSSFzrK~+v4Ma!Ks0O%;t#h@`+`%h9bJ- z+n%K0=nG$&3g)i4K)KcMixMxS#fEDez2zPZAY_ty3BPsaOsB0v1QzF&GI6>HnJSuL zHh8&U8|rRKAx2@`Z1wRnuGwURCd5seKW)Vnsy zQ@?SVd#h@b<@I8jGXuEEkfL0{(7ZhR=VOacE0+&dy7fZgsAa#W$@%g%Xzyi*NV?*p zG|@XfrG{i792svdNB)L%tZWF$5c|yIxZ9Y9Cw?$t+9;{%S||lZ6;E_>dlNk^w{Xx+ z@5#BcbAW9lWAcKGP30@9H#3DlTv7hfVimB98KUkh(CR$$NX&U4eo}9Ztg5V!SCr2$H}Aq=)Jsk-;wahMrj6sgpbg#Ap1Jv& zz<-KcI)^&3-_!uXnQgde1nWMKp$P#an7twiw+I}WSN7Xw$NhOs#eAF1eFiT08{iDY za7$ae7wcYFZk;;%*y0WwVnAN6HQnXq2P&6qiNd*7U--^yr|m>?dJf=rGLkrqRh6k} zg|}Vz0rlhx(KRF{vpExSURHQJ#jdv9jBM65!{F!0Ym^xeKIgZtOU*<_9KyV=XIXNE zF>z@Q?#K9*lhUJpQhd2z$7XU6s`=u%wmK`8H&aNrLLU+ILw(oGpd)odJF zZpTY7-y;3+qA{$Xq+;hw@69o0I#^?^hKaPoI_kG7cHp-J+2WOWkBX*>7JNeW5mjuP zGcauqLG|kV^iU)?F+NM+q1ls2DL4-sc0BlIH%E#vD?(;!^VW>845fP38px`*ygYk$ zQ|tC}bG4D8vL91rDHT{DONLDguIv>R-ia1rRq5tS_wBP|d;=N#EC@<9JC}<^cB%y9Mtj$aeX2}(vFKcKf@D) z%v#I2e>yLfq0phUv^>hWt6L}#D>RB3+ZA^c&!BfSo4LOGndbBa!IsQHdUE(m_g>V*Gm`~EN@kQKDzwt$9udJkLy26iN zm>;(_>Dv455if~DW!$6)fO{t$=+4#Y9c0sT^)>Nm_0Sx!c8udgMkw^Nckfd!SX#F> zya1aW-`CT;9>>UlLQRtc_T;Mao^l1hBU&Iso1lzC<~jQ1@k&ST;mvNOcPCI4cZrF_fed_o=u4m{P_Y?$-rFFix?l-GZ?F~78%Q2Jgz@I+H>l{NU``{3AuVulcxVu&3MND? z!J%g}vx`AC1VxKn`cMkyR4hB#^bTjKnK0*~8&aLAepjDSl#e2|SdC*`w9O1EDud-Y zu!6vka?s#HRllhxnVez>)y~QLYzDML7eE8@OXci>q*X0#oHb_xazF!SI`Kq!hxd^& zHZt7R6=_y#;Z>!<@=7oel^94W>^z&#?pd63(hw}X=S#^35imS{JS>q(=+LA7`j)K+ z#5jgr?H1;7{vrwZ7iKNiN}m1B@?#znTASQ3>~MUaZG`B|onZbw$7T+K03E3>!tb3G zeEBPIX}fK}U1Zcag)lx5aSUB*ABN(rc#1^Ubq?2@m5ZU}E#Zy(=NE(&$siCxJ!O>{_8Pg<74>%iSQcE(KhbHC z4uN$F1PV@TOAH}_ygX)-vsQr;G?Tyqar)W?Yu0bV#WtcBHq0r@(`f@%VZPoL+Ja-z zH93)-wt8ts?}^!XPt!AvUzz4H%(u!r*%u?urvscvhWnYa^4RO z?H}ao3NWBd(Qu;cX)pwHo|qwlRoZ^!hQHi~0uRlC-{VHRV$#)-y>D zV*UGgMpf`BnawDdIL*KdMR~x0qDOYIT%{thnTOCv+;tYbbdm4Hl1Y^c$3ftF9PFqC zggO;mpt=rEm?6qf149=PL*wX>Sa3n{2jXD}Mv~7`1GzbO>#SRGP=-3z2Y)q^*pl~~ z+vr)yHC1RF(csiRN3V8x%jArnd!s^@3G30{-R_d>g?Djq{Y1bM(#_trd0=Wh-gncw zOsX@KSZ?><+S|#ED$Z%lgJKy@S`24{->SduR}x0IpN2{|hqp9|zm(6h9B-}+|KU2* zJbt`s{44EZHG6-aiMAT#5pm||Z_Za^x%+Oa7cuAY0P)8Lp!*zy1C67#`_rVo{MiOr z<2KAcSlA6q&BeFW_%e$sY*_3nQ0+OY^jd|O4~4)stszn4gVV+Zx1G{uItA2JOxfzq zu|@&1N7n|jR8)0wqK+%|+scY!S0^5RUGp9l>1E_<}cWLKOoF-}0B0U%*bvjnHiBX1a2sm&LqsdUU>+lO6 z{7@jtr(;L_v`cu&<}2{dn?q4q{4|@+4|F7tbd3!V5L?fg&XTs~&PK4qoh@+>B$Qo< zW#J=i?3mP;IZPheoc~&< z?G>@D=4D}>?d4{);-c^?yo(oycYSVDK8ltGF#+7+6J<#lC%qcevOZwPezHEGHfaME zBWrErhw`$31v)bo9Lk1N86qK?CY=Gl6iF1WN7?NR^5QUQHH<)%A`iMTbUIs)m^EQV z&fsi)fd>TVP{Xk9^`JqYXc#@qqFZc!qk*LvlVuwR_trpc`x>Yuk98-tJeXtLs^M1~ zXk+)ro^)GOh;cy-9T3hYr;@vytjaJK-}OVXoGoX|>w0|1;E%LV(=`nvB_rj+7)2T( zAsr)Dlp~afk19S!hr-bV_HPEDJyeFRdq=%qd|#dy== z@QNp~gUed)-AxT<@SRPg|2AeY+IZulpRtAUE<6=mm`yppCCnI^Jr#^fH-+W)lL3{P z&$Db{IR^hNt>H%s#;}uRh8@jm!jz+CaLHbYQh9Z~+;BJ;SS(J;%fo^@byBJ7; z7?SjFUK3OjY&f1Y7j&F^8XR2VH8d!{q$=iJgzpynxX1f5Ezv$pms@sD zUGr{;^9J1uEi^ny9(`7GZ!W;KsCa+0*9olOxrZ+ z+9d5;xzGSmF3X?P-tH9)Ntpo7RI(JEYP#0Gd_P*Cm8o~O_J=gE*e%STKYBukr92GC z$+&j9+(uoR$2L*vHE^i1h`wO-OHk zNi3C=T^_ELBXl<9+T&qxWz13>sgGXQ{O)qw!Uo;SmQb>Z%p3wJrVT5tV$8u`w0K29 zXU|czJ*U*_r3X87J4nanwW0OXYcE={0zKbPWtcCtZM9pAWzdn>Vu#i}eHLo4h{qri z3lAa`2uBpJDLA?{9~noJx+IeCM6H5P6sau#Jn)vVQ1pcG31$kutCgxfi%()hs6dti zjbqd^i5~$@1=}HmhT8Q|rmb^s+zW~xo}B5#6Ps_0#nwG5n6577dGWtOVxCT5M@S&T z4s30kK05W-;x^os<=e~2X8(mtAUJl`2B`SL$I8hzf58LE^4aiebG!f9(H?(hG-{}` zpZ(8rdy;H5J_I`mSz|!c_6FIBIDv=`kuPFbj|`xI5k>{@@SxnTi1O6xhy1^1^IvO5 z(azyiL!z4Cs62W?`EiAOJeg(C3{X#;&C8i|E5z|;Q4>ZC%wiq|E@Z()KB*$xNGNxy zXOhEO+lW)bPhz5r_AF#>4W}Qfbl~p705y2Y0UDsn-jd6Wl$8~IXkcG%gw|VGrJu0$0h?V zw6dFBf;VPy{5k3r3c@6ekey)M z1tDUi%6#rAl>bsEalIcWNg7Cl<7mQhrYW39iQ~(REf%e5b6=sLOxR^0b~OxlHiTH8 z;LusHmz=%D9(GmDT1~Gk%vn6>#i>75rpvO=tifUy)$s>?V!ogRNgqx7!_Mp0`dNBY z^$J@-|M-?#tr~@Zas*A@r(uHQaF(Yu!8YSc$D1HocFDdeDJbnrEiIz8Bi^w!PRD39 zm@hw)Y{;K^3P*GCtw)mOloFYnvDQ}n!9O=qx1xnXsM8)FB^fuon>)Ck;3MxDlz%jL zf<>h`Ltg>RY?g)v#W$16##(Kk^zvnEXu`N}WZFo)QEK>kC`E(g0!q2UZnW^+%kniJVpD2#`@y3iB zuS6O+OdNG=|5Hr4`IQB;?OaQ_>3JF{H%Myky{c7w-i9=hV*btJbMtQ+KP2?2GsGEv zCIJWCl2ncV@Ci7#H4-4wr#O1d=YBDS0!uYh@a#C+%cymZgg zB4*Dha(VJ39F>HcxkNqB_fNuDzjb_?d{T)Os};WQcWtfuCbQ}{KNX9rsE^>!Ls^%MnoE5E?DG4Ao-G#6~!L+h5XtuqWJ)i zhpWnk*0{7|Y(;e?n}px4cp00PfTcw4BZ-J^t-DzC)LruV=Gvy_d*zZo3Su&pCpGMVFL7_YVAMxSlWQ`67{(n5 zIo5B$GdBfM!w}gfnr>)CZ_y9y6O&EKQ^j|^#3>#1(LF!Orz~irYD|Y8Ht(mOS4znp z9p0BU#Xt8!1l}Ok{B%t5R-CysawDaM1~$t6hf4NxpQlY<^{=bcbqy&)M572SJ>Q2f z#)is%m4k{TLVyXBwtE||^~svsb7gT0l#ek*=3TwsRu($e%(ErS@P-_BPn-yHOde-j z9*@vb;E(72pWWp_J~W6%4B~1pFC1)a%oX zkI=MwT0*`Ul+sFg8P?i|)e}ZbNa+IyD*T6jFJciS#ZpRdR{BYhhA746PH6 z=Jhom7x`&t7)XnBE!isDXPy2h-f3i)K{acz03r3I)UJtH1J_d~9<0V{ z12I-34Va+Fe-u-lu6L|R<7qI`rA0PTz7UHOqS&f^+%{3GT8(fFo1v9b_$tg%OQ1^C zhB&#_qZ0r*1W-&MQ>Gw^P&lKpGNQ1)@T=YKYsd9vv^;9<&(x2&Has2m=5n26@f9s= zm!Y0!8isTVQ^DfgdexcI_=IE@bfpS0odZGYtXU(asN4NKu@7A(_v*62uR&yc$@j2(pZFlkxsg#xK^whq!Y?3VD{b_PvLkI>L zIRuhb$uV4Myex47=PABEfs$^cX%=J{`<$zskQ9UvqgcCSN!#1$U8Oe)3l85Gk(4H1 zYM4D{Duh=j2kC;7&suqr1(K&`Cg{m=fRMFqmm{wb{o&#Tg&9Maqh>Fdh;ASBQ8E?U z+iu9XueAX}8}v8WBF`!oVyp^)!$Fn=HECfNt1W^X>d9cUov%@%Md+CZ`aoM1{HizTA@V3mL1*OdpdP{ z6RFESxAZ(#oT-s;QFK%4nbev`^i3h;po8$ zy?&l)&8HQVO6xy)ycLhoXA#abHhy)!6(&OvI_TfSDR&P|q5Xr%spi&^;WL)NVg_T} zMipZ@mMDtRd$HSF@M4N;raIZtnxGV49=*28k>Fc_t2ASVp3#=Z>=uEH>8^*9SllQT+{OM!Xv$Ozk&(WFD1VeW~M z#?D}243k=R9PIhd$ys*|$9-Mxm>NOyl?@G!-5^+Wki^rf z1rAEypLH9d=EDxxfZg<)ykJS0&Grap0ipQ@)6URLw!;*bTRTrXU&lXQK02b8HTJ`` z!rz`?r4waa2BZR1k$YKchO6DxMgg#HUCs(K0vE=`i?8gh26tW-h zE_E;YH%ou);*e%dhMH+Cw0+6zmb4wnlS0Sx2HrB#q~gw-ut*}^0Q5Rg9Iv{km|fZS z)UqqzwB-M0KIOeF%Is)<$C76fDoq4f3AsvP6<=nM;wEurb{W;|vvUURqjF2^}> z)HTR*=ii=iKHrvXf;Ng1ciG;Cc3H_aODKx%XEi|+h-2s3X3_EtZVdt0~z7kQR?#D>@VNi^M_ Date: Fri, 12 Sep 2025 12:06:36 +0800 Subject: [PATCH 11/76] fix --- .../revive/src/evm/api/debug_rpc_types.rs | 326 +++++++++--------- 1 file changed, 162 insertions(+), 164 deletions(-) diff --git a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs index 2eb5a1bbc2354..a4d8beca13e92 100644 --- a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs +++ b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs @@ -534,170 +534,168 @@ fn get_opcode_name(opcode: u8) -> &'static str { /// Get opcode byte from name string fn get_opcode_byte(name: &str) -> Option { - match name { - // Arithmetic operations - "STOP" => Some(revm::bytecode::opcode::STOP), - "ADD" => Some(revm::bytecode::opcode::ADD), - "MUL" => Some(revm::bytecode::opcode::MUL), - "SUB" => Some(revm::bytecode::opcode::SUB), - "DIV" => Some(revm::bytecode::opcode::DIV), - "SDIV" => Some(revm::bytecode::opcode::SDIV), - "MOD" => Some(revm::bytecode::opcode::MOD), - "SMOD" => Some(revm::bytecode::opcode::SMOD), - "ADDMOD" => Some(revm::bytecode::opcode::ADDMOD), - "MULMOD" => Some(revm::bytecode::opcode::MULMOD), - "EXP" => Some(revm::bytecode::opcode::EXP), - "SIGNEXTEND" => Some(revm::bytecode::opcode::SIGNEXTEND), - // Comparison operations - "LT" => Some(revm::bytecode::opcode::LT), - "GT" => Some(revm::bytecode::opcode::GT), - "SLT" => Some(revm::bytecode::opcode::SLT), - "SGT" => Some(revm::bytecode::opcode::SGT), - "EQ" => Some(revm::bytecode::opcode::EQ), - "ISZERO" => Some(revm::bytecode::opcode::ISZERO), - // Bitwise operations - "AND" => Some(revm::bytecode::opcode::AND), - "OR" => Some(revm::bytecode::opcode::OR), - "XOR" => Some(revm::bytecode::opcode::XOR), - "NOT" => Some(revm::bytecode::opcode::NOT), - "BYTE" => Some(revm::bytecode::opcode::BYTE), - "SHL" => Some(revm::bytecode::opcode::SHL), - "SHR" => Some(revm::bytecode::opcode::SHR), - "SAR" => Some(revm::bytecode::opcode::SAR), - // Hash operations - "KECCAK256" => Some(revm::bytecode::opcode::KECCAK256), - // Environment information - "ADDRESS" => Some(revm::bytecode::opcode::ADDRESS), - "BALANCE" => Some(revm::bytecode::opcode::BALANCE), - "ORIGIN" => Some(revm::bytecode::opcode::ORIGIN), - "CALLER" => Some(revm::bytecode::opcode::CALLER), - "CALLVALUE" => Some(revm::bytecode::opcode::CALLVALUE), - "CALLDATALOAD" => Some(revm::bytecode::opcode::CALLDATALOAD), - "CALLDATASIZE" => Some(revm::bytecode::opcode::CALLDATASIZE), - "CALLDATACOPY" => Some(revm::bytecode::opcode::CALLDATACOPY), - "CODESIZE" => Some(revm::bytecode::opcode::CODESIZE), - "CODECOPY" => Some(revm::bytecode::opcode::CODECOPY), - "GASPRICE" => Some(revm::bytecode::opcode::GASPRICE), - "EXTCODESIZE" => Some(revm::bytecode::opcode::EXTCODESIZE), - "EXTCODECOPY" => Some(revm::bytecode::opcode::EXTCODECOPY), - "RETURNDATASIZE" => Some(revm::bytecode::opcode::RETURNDATASIZE), - "RETURNDATACOPY" => Some(revm::bytecode::opcode::RETURNDATACOPY), - "EXTCODEHASH" => Some(revm::bytecode::opcode::EXTCODEHASH), - // Block information - "BLOCKHASH" => Some(revm::bytecode::opcode::BLOCKHASH), - "COINBASE" => Some(revm::bytecode::opcode::COINBASE), - "TIMESTAMP" => Some(revm::bytecode::opcode::TIMESTAMP), - "NUMBER" => Some(revm::bytecode::opcode::NUMBER), - "DIFFICULTY" => Some(revm::bytecode::opcode::DIFFICULTY), - "GASLIMIT" => Some(revm::bytecode::opcode::GASLIMIT), - "CHAINID" => Some(revm::bytecode::opcode::CHAINID), - "SELFBALANCE" => Some(revm::bytecode::opcode::SELFBALANCE), - "BASEFEE" => Some(revm::bytecode::opcode::BASEFEE), - "BLOBHASH" => Some(revm::bytecode::opcode::BLOBHASH), - "BLOBBASEFEE" => Some(revm::bytecode::opcode::BLOBBASEFEE), - // Storage and memory operations - "POP" => Some(revm::bytecode::opcode::POP), - "MLOAD" => Some(revm::bytecode::opcode::MLOAD), - "MSTORE" => Some(revm::bytecode::opcode::MSTORE), - "MSTORE8" => Some(revm::bytecode::opcode::MSTORE8), - "SLOAD" => Some(revm::bytecode::opcode::SLOAD), - "SSTORE" => Some(revm::bytecode::opcode::SSTORE), - "JUMP" => Some(revm::bytecode::opcode::JUMP), - "JUMPI" => Some(revm::bytecode::opcode::JUMPI), - "PC" => Some(revm::bytecode::opcode::PC), - "MSIZE" => Some(revm::bytecode::opcode::MSIZE), - "GAS" => Some(revm::bytecode::opcode::GAS), - "JUMPDEST" => Some(revm::bytecode::opcode::JUMPDEST), - "TLOAD" => Some(revm::bytecode::opcode::TLOAD), - "TSTORE" => Some(revm::bytecode::opcode::TSTORE), - "MCOPY" => Some(revm::bytecode::opcode::MCOPY), - // Push operations - "PUSH0" => Some(revm::bytecode::opcode::PUSH0), - "PUSH1" => Some(revm::bytecode::opcode::PUSH1), - "PUSH2" => Some(revm::bytecode::opcode::PUSH2), - "PUSH3" => Some(revm::bytecode::opcode::PUSH3), - "PUSH4" => Some(revm::bytecode::opcode::PUSH4), - "PUSH5" => Some(revm::bytecode::opcode::PUSH5), - "PUSH6" => Some(revm::bytecode::opcode::PUSH6), - "PUSH7" => Some(revm::bytecode::opcode::PUSH7), - "PUSH8" => Some(revm::bytecode::opcode::PUSH8), - "PUSH9" => Some(revm::bytecode::opcode::PUSH9), - "PUSH10" => Some(revm::bytecode::opcode::PUSH10), - "PUSH11" => Some(revm::bytecode::opcode::PUSH11), - "PUSH12" => Some(revm::bytecode::opcode::PUSH12), - "PUSH13" => Some(revm::bytecode::opcode::PUSH13), - "PUSH14" => Some(revm::bytecode::opcode::PUSH14), - "PUSH15" => Some(revm::bytecode::opcode::PUSH15), - "PUSH16" => Some(revm::bytecode::opcode::PUSH16), - "PUSH17" => Some(revm::bytecode::opcode::PUSH17), - "PUSH18" => Some(revm::bytecode::opcode::PUSH18), - "PUSH19" => Some(revm::bytecode::opcode::PUSH19), - "PUSH20" => Some(revm::bytecode::opcode::PUSH20), - "PUSH21" => Some(revm::bytecode::opcode::PUSH21), - "PUSH22" => Some(revm::bytecode::opcode::PUSH22), - "PUSH23" => Some(revm::bytecode::opcode::PUSH23), - "PUSH24" => Some(revm::bytecode::opcode::PUSH24), - "PUSH25" => Some(revm::bytecode::opcode::PUSH25), - "PUSH26" => Some(revm::bytecode::opcode::PUSH26), - "PUSH27" => Some(revm::bytecode::opcode::PUSH27), - "PUSH28" => Some(revm::bytecode::opcode::PUSH28), - "PUSH29" => Some(revm::bytecode::opcode::PUSH29), - "PUSH30" => Some(revm::bytecode::opcode::PUSH30), - "PUSH31" => Some(revm::bytecode::opcode::PUSH31), - "PUSH32" => Some(revm::bytecode::opcode::PUSH32), - // Dup operations - "DUP1" => Some(revm::bytecode::opcode::DUP1), - "DUP2" => Some(revm::bytecode::opcode::DUP2), - "DUP3" => Some(revm::bytecode::opcode::DUP3), - "DUP4" => Some(revm::bytecode::opcode::DUP4), - "DUP5" => Some(revm::bytecode::opcode::DUP5), - "DUP6" => Some(revm::bytecode::opcode::DUP6), - "DUP7" => Some(revm::bytecode::opcode::DUP7), - "DUP8" => Some(revm::bytecode::opcode::DUP8), - "DUP9" => Some(revm::bytecode::opcode::DUP9), - "DUP10" => Some(revm::bytecode::opcode::DUP10), - "DUP11" => Some(revm::bytecode::opcode::DUP11), - "DUP12" => Some(revm::bytecode::opcode::DUP12), - "DUP13" => Some(revm::bytecode::opcode::DUP13), - "DUP14" => Some(revm::bytecode::opcode::DUP14), - "DUP15" => Some(revm::bytecode::opcode::DUP15), - "DUP16" => Some(revm::bytecode::opcode::DUP16), - // Swap operations - "SWAP1" => Some(revm::bytecode::opcode::SWAP1), - "SWAP2" => Some(revm::bytecode::opcode::SWAP2), - "SWAP3" => Some(revm::bytecode::opcode::SWAP3), - "SWAP4" => Some(revm::bytecode::opcode::SWAP4), - "SWAP5" => Some(revm::bytecode::opcode::SWAP5), - "SWAP6" => Some(revm::bytecode::opcode::SWAP6), - "SWAP7" => Some(revm::bytecode::opcode::SWAP7), - "SWAP8" => Some(revm::bytecode::opcode::SWAP8), - "SWAP9" => Some(revm::bytecode::opcode::SWAP9), - "SWAP10" => Some(revm::bytecode::opcode::SWAP10), - "SWAP11" => Some(revm::bytecode::opcode::SWAP11), - "SWAP12" => Some(revm::bytecode::opcode::SWAP12), - "SWAP13" => Some(revm::bytecode::opcode::SWAP13), - "SWAP14" => Some(revm::bytecode::opcode::SWAP14), - "SWAP15" => Some(revm::bytecode::opcode::SWAP15), - "SWAP16" => Some(revm::bytecode::opcode::SWAP16), - // Log operations - "LOG0" => Some(revm::bytecode::opcode::LOG0), - "LOG1" => Some(revm::bytecode::opcode::LOG1), - "LOG2" => Some(revm::bytecode::opcode::LOG2), - "LOG3" => Some(revm::bytecode::opcode::LOG3), - "LOG4" => Some(revm::bytecode::opcode::LOG4), - // System operations - "CREATE" => Some(revm::bytecode::opcode::CREATE), - "CALL" => Some(revm::bytecode::opcode::CALL), - "CALLCODE" => Some(revm::bytecode::opcode::CALLCODE), - "RETURN" => Some(revm::bytecode::opcode::RETURN), - "DELEGATECALL" => Some(revm::bytecode::opcode::DELEGATECALL), - "CREATE2" => Some(revm::bytecode::opcode::CREATE2), - "STATICCALL" => Some(revm::bytecode::opcode::STATICCALL), - "REVERT" => Some(revm::bytecode::opcode::REVERT), - "INVALID" => Some(revm::bytecode::opcode::INVALID), - "SELFDESTRUCT" => Some(revm::bytecode::opcode::SELFDESTRUCT), - _ => None, - } + use revm::bytecode::opcode::*; + macro_rules! opcode_byte_match { + ($($op:ident),*) => { + match name { + $( + stringify!($op) => Some($op), + )* + _ => None, + } + }; + } + opcode_byte_match!( + STOP, + ADD, + MUL, + SUB, + DIV, + SDIV, + MOD, + SMOD, + ADDMOD, + MULMOD, + EXP, + SIGNEXTEND, + LT, + GT, + SLT, + SGT, + EQ, + ISZERO, + AND, + OR, + XOR, + NOT, + BYTE, + SHL, + SHR, + SAR, + KECCAK256, + ADDRESS, + BALANCE, + ORIGIN, + CALLER, + CALLVALUE, + CALLDATALOAD, + CALLDATASIZE, + CALLDATACOPY, + CODESIZE, + CODECOPY, + GASPRICE, + EXTCODESIZE, + EXTCODECOPY, + RETURNDATASIZE, + RETURNDATACOPY, + EXTCODEHASH, + BLOCKHASH, + COINBASE, + TIMESTAMP, + NUMBER, + DIFFICULTY, + GASLIMIT, + CHAINID, + SELFBALANCE, + BASEFEE, + BLOBHASH, + BLOBBASEFEE, + POP, + MLOAD, + MSTORE, + MSTORE8, + SLOAD, + SSTORE, + JUMP, + JUMPI, + PC, + MSIZE, + GAS, + JUMPDEST, + TLOAD, + TSTORE, + MCOPY, + PUSH0, + PUSH1, + PUSH2, + PUSH3, + PUSH4, + PUSH5, + PUSH6, + PUSH7, + PUSH8, + PUSH9, + PUSH10, + PUSH11, + PUSH12, + PUSH13, + PUSH14, + PUSH15, + PUSH16, + PUSH17, + PUSH18, + PUSH19, + PUSH20, + PUSH21, + PUSH22, + PUSH23, + PUSH24, + PUSH25, + PUSH26, + PUSH27, + PUSH28, + PUSH29, + PUSH30, + PUSH31, + PUSH32, + DUP1, + DUP2, + DUP3, + DUP4, + DUP5, + DUP6, + DUP7, + DUP8, + DUP9, + DUP10, + DUP11, + DUP12, + DUP13, + DUP14, + DUP15, + DUP16, + SWAP1, + SWAP2, + SWAP3, + SWAP4, + SWAP5, + SWAP6, + SWAP7, + SWAP8, + SWAP9, + SWAP10, + SWAP11, + SWAP12, + SWAP13, + SWAP14, + SWAP15, + SWAP16, + LOG0, + LOG1, + LOG2, + LOG3, + LOG4, + CREATE, + CALL, + CALLCODE, + RETURN, + DELEGATECALL, + CREATE2, + STATICCALL, + REVERT, + INVALID, + SELFDESTRUCT + ) } /// Serialize opcode as string using REVM opcode names From a459aa60ccd6cb843dca0585cdaca0ad87378b82 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Fri, 12 Sep 2025 15:43:48 +0800 Subject: [PATCH 12/76] wip --- .../revive/src/evm/api/debug_rpc_types.rs | 19 +++ substrate/frame/revive/src/tests/sol.rs | 148 ------------------ 2 files changed, 19 insertions(+), 148 deletions(-) diff --git a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs index a4d8beca13e92..bb18cd4c601fb 100644 --- a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs +++ b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs @@ -155,9 +155,28 @@ impl Default for OpcodeTracerConfig { /// ```json /// { "tracer": "callTracer" } /// ``` +/// +/// By default if not specified the tracer is a StructLogger, and it's config is passed inline +/// +/// ```json +/// { "tracer": null, "tracerConfig": { "enableMemory": true, "disableStack": false, "disableStorage": false, "enableReturnData": true } } +/// ``` #[test] fn test_tracer_config_serialization() { let tracers = vec![ + ( + r#"{ "tracer": null, "tracerConfig": { "enableMemory": true, "disableStack": false, "disableStorage": false, "enableReturnData": true } }"#, + TracerConfig { + config: TracerType::StructLogger(Some(OpcodeTracerConfig { + enable_memory: true, + disable_stack: false, + disable_storage: false, + enable_return_data: true, + limit: 0, + })), + timeout: None, + }, + ), ( r#"{"tracer": "callTracer"}"#, TracerConfig { config: TracerType::CallTracer(None), timeout: None }, diff --git a/substrate/frame/revive/src/tests/sol.rs b/substrate/frame/revive/src/tests/sol.rs index 606fe8510ec28..a785545f0f0cd 100644 --- a/substrate/frame/revive/src/tests/sol.rs +++ b/substrate/frame/revive/src/tests/sol.rs @@ -170,120 +170,6 @@ fn basic_evm_flow_tracing_works() { #[test] fn opcode_tracing_works() { - use crate::{ - evm::{OpcodeTracer, OpcodeTracerConfig}, - tracing::trace, - }; - let (code, _) = compile_module_with_type("Fibonacci", FixtureType::Solc).unwrap(); - - ExtBuilder::default().build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); - - // Create a simple contract that will produce some opcodes - let Contract { addr, .. } = - builder::bare_instantiate(Code::Upload(code.clone())).build_and_unwrap_contract(); - - // Test opcode tracing with basic config - let config = OpcodeTracerConfig { - enable_memory: false, - disable_stack: false, - disable_storage: true, - enable_return_data: false, - limit: 10, - }; - let mut opcode_tracer = OpcodeTracer::new(config); - - let result = trace(&mut opcode_tracer, || { - builder::bare_call(addr) - .data( - Fibonacci::FibonacciCalls::fib(Fibonacci::fibCall { n: U256::from(3u64) }) - .abi_encode(), - ) - .build_and_unwrap_result() - }); - - assert_eq!( - U256::from(2u32), - U256::from_be_bytes::<32>(result.data.clone().try_into().unwrap()) - ); - - let trace = opcode_tracer.collect_trace(); - - // Should have captured some opcode steps - assert!(!trace.struct_logs.is_empty()); - - // Should have limited the number of steps to the config limit - assert!(trace.struct_logs.len() <= 10); - - // Each step should have the required fields - for step in &trace.struct_logs { - // Opcode should be valid (any u8 is valid) - // Stack should be present (not disabled) - assert!(step.stack.is_some()); - // Memory should not be present (disabled) - assert!(step.memory.is_none()); - // Storage should not be present (disabled) - assert!(step.storage.is_none()); - } - }); -} - -#[test] -fn opcode_tracing_with_memory_works() { - use crate::{ - evm::{OpcodeTracer, OpcodeTracerConfig}, - tracing::trace, - }; - let (code, _) = compile_module_with_type("Fibonacci", FixtureType::Solc).unwrap(); - - ExtBuilder::default().build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); - - let Contract { addr, .. } = - builder::bare_instantiate(Code::Upload(code.clone())).build_and_unwrap_contract(); - - // Test opcode tracing with memory enabled - let config = OpcodeTracerConfig { - enable_memory: true, - disable_stack: true, - disable_storage: true, - enable_return_data: true, - limit: 5, - }; - let mut opcode_tracer = OpcodeTracer::new(config); - - let result = trace(&mut opcode_tracer, || { - builder::bare_call(addr) - .data( - Fibonacci::FibonacciCalls::fib(Fibonacci::fibCall { n: U256::from(1u64) }) - .abi_encode(), - ) - .build_and_unwrap_result() - }); - - assert_eq!( - U256::from(1u32), - U256::from_be_bytes::<32>(result.data.clone().try_into().unwrap()) - ); - - let trace = opcode_tracer.collect_trace(); - - // Should have captured some opcode steps - assert!(!trace.struct_logs.is_empty()); - - // Each step should have the required fields based on config - for step in &trace.struct_logs { - // Opcode should be valid (any u8 is valid) - // Stack should not be present (disabled) - assert!(step.stack.is_none()); - // Memory should be present (enabled) - assert!(step.memory.is_some()); - } - }); -} - -#[test] -fn opcode_tracing_comprehensive_works() { use crate::{ evm::{OpcodeTrace, OpcodeTracer, OpcodeTracerConfig}, tracing::trace, @@ -388,37 +274,3 @@ fn opcode_tracing_comprehensive_works() { assert_eq!(actual_trace, expected_trace); }); } - -#[test] -fn revm_opcode_serialization_works() { - use crate::evm::OpcodeStep; - use revm::bytecode::opcode::*; - - // Test that our opcode serialization uses REVM opcode names - let step = OpcodeStep { - pc: 0, - op: PUSH1, - gas: 0, - gas_cost: 0, - depth: 0, - stack: Some(vec![]), - memory: None, - storage: None, - error: None, - }; - - // Serialize to JSON - let json = serde_json::to_string(&step).unwrap(); - - // Should contain "PUSH1" string from REVM - assert!(json.contains("\"op\":\"PUSH1\"")); - - // Test a few more opcodes - let step_add = OpcodeStep { op: ADD, ..step.clone() }; - let json_add = serde_json::to_string(&step_add).unwrap(); - assert!(json_add.contains("\"op\":\"ADD\"")); - - let step_call = OpcodeStep { op: CALL, ..step.clone() }; - let json_call = serde_json::to_string(&step_call).unwrap(); - assert!(json_call.contains("\"op\":\"CALL\"")); -} From c206d148d84fffa06a7a909997ffe6e9ae752bca Mon Sep 17 00:00:00 2001 From: pgherveou Date: Sat, 13 Sep 2025 15:34:28 +0800 Subject: [PATCH 13/76] wip --- .../revive/src/evm/api/debug_rpc_types.rs | 106 +++++++++- substrate/frame/revive/src/evm/tracing.rs | 4 +- .../revive/src/evm/tracing/opcode_tracing.rs | 81 ++++---- substrate/frame/revive/src/lib.rs | 7 +- substrate/frame/revive/src/tests/sol.rs | 40 ++-- substrate/frame/revive/src/tracing.rs | 35 ++-- substrate/frame/revive/src/vm/evm.rs | 181 +++++++----------- 7 files changed, 267 insertions(+), 187 deletions(-) diff --git a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs index bb18cd4c601fb..4fbfbabf93197 100644 --- a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs +++ b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs @@ -21,6 +21,7 @@ use codec::{Decode, Encode}; use derive_more::From; use scale_info::TypeInfo; use serde::{ + de::{self, Deserializer}, ser::{SerializeMap, Serializer}, Deserialize, Serialize, }; @@ -66,7 +67,7 @@ impl Default for TracerType { /// Tracer configuration used to trace calls. #[derive(TypeInfo, Debug, Clone, Default, PartialEq)] -#[cfg_attr(feature = "std", derive(Deserialize, Serialize), serde(rename_all = "camelCase"))] +#[cfg_attr(feature = "std", derive(Serialize), serde(rename_all = "camelCase"))] pub struct TracerConfig { /// The tracer type. #[cfg_attr(feature = "std", serde(flatten, default))] @@ -77,6 +78,93 @@ pub struct TracerConfig { pub timeout: Option, } +#[cfg(feature = "std")] +impl<'de> Deserialize<'de> for TracerConfig { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + #[derive(Deserialize, Default)] + #[serde(default, rename_all = "camelCase")] + struct RawTracerConfig<'a> { + tracer: Option<&'a str>, + tracer_config: Option, + + #[serde(with = "humantime_serde", default)] + timeout: Option, + } + + // We can't use untagged enums reliably because they may pick the wrong variant + // Instead, we'll use a custom deserializer that tries each type based on tracer name + #[derive(Deserialize, Default)] + #[serde(default, rename_all = "camelCase")] + struct TracerConfigInner { + // CallTracer fields + with_logs: Option, + only_top_call: Option, + + // PrestateTracer fields + diff_mode: Option, + disable_code: Option, + + // OpcodeTracer fields + enable_memory: Option, + disable_stack: Option, + + // This field exists in both PrestateTracer and OpcodeTracer + disable_storage: Option, + enable_return_data: Option, + limit: Option, + } + + let raw = RawTracerConfig::deserialize(deserializer)?; + let tracer_type = raw.tracer.unwrap_or_else(|| "structLogger"); + let config = match tracer_type { + "callTracer" => + if let Some(inner) = raw.tracer_config { + let call_config = CallTracerConfig { + with_logs: inner.with_logs.unwrap_or(true), + only_top_call: inner.only_top_call.unwrap_or(false), + }; + TracerType::CallTracer(Some(call_config)) + } else { + TracerType::CallTracer(None) + }, + "prestateTracer" => + if let Some(inner) = raw.tracer_config { + let prestate_config = PrestateTracerConfig { + diff_mode: inner.diff_mode.unwrap_or(false), + disable_storage: inner.disable_storage.unwrap_or(false), + disable_code: inner.disable_code.unwrap_or(false), + }; + TracerType::PrestateTracer(Some(prestate_config)) + } else { + TracerType::PrestateTracer(None) + }, + "structLogger" => + if let Some(inner) = raw.tracer_config { + let opcode_config = OpcodeTracerConfig { + enable_memory: inner.enable_memory.unwrap_or(false), + disable_stack: inner.disable_stack.unwrap_or(false), + disable_storage: inner.disable_storage.unwrap_or(false), + enable_return_data: inner.enable_return_data.unwrap_or(false), + limit: inner.limit.unwrap_or(0), + }; + TracerType::StructLogger(Some(opcode_config)) + } else { + TracerType::StructLogger(None) + }, + _ => + return Err(de::Error::unknown_variant( + &tracer_type, + &["callTracer", "prestateTracer", "structLogger"], + )), + }; + + Ok(TracerConfig { config, timeout: raw.timeout }) + } +} + /// The configuration for the call tracer. #[derive(Clone, Debug, Decode, Serialize, Deserialize, Encode, PartialEq, TypeInfo)] #[serde(default, rename_all = "camelCase")] @@ -336,20 +424,20 @@ where /// This matches Geth's structLogger output format. #[derive(TypeInfo, Encode, Decode, Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] #[serde(rename_all = "camelCase")] -pub struct OpcodeTrace { +pub struct OpcodeTrace { /// Total gas used by the transaction. - pub gas: u64, + pub gas: Gas, /// Whether the transaction failed. pub failed: bool, /// The return value of the transaction. pub return_value: Bytes, /// The list of opcode execution steps (structLogs in Geth). - pub struct_logs: Vec, + pub struct_logs: Vec>, } -impl Default for OpcodeTrace { +impl Default for OpcodeTrace { fn default() -> Self { - Self { gas: 0, failed: false, return_value: Bytes::default(), struct_logs: Vec::new() } + Self { gas: Gas::default(), failed: false, return_value: Bytes::default(), struct_logs: Vec::new() } } } @@ -357,16 +445,16 @@ impl Default for OpcodeTrace { /// This matches Geth's structLog format exactly. #[derive(TypeInfo, Encode, Decode, Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] #[serde(rename_all = "camelCase")] -pub struct OpcodeStep { +pub struct OpcodeStep { /// The program counter. pub pc: u64, /// The opcode being executed. #[serde(serialize_with = "serialize_opcode", deserialize_with = "deserialize_opcode")] pub op: u8, /// Remaining gas before executing this opcode. - pub gas: u64, + pub gas: Gas, /// Cost of executing this opcode. - pub gas_cost: u64, + pub gas_cost: Gas, /// Current call depth. pub depth: u32, /// EVM stack contents (optional based on config). diff --git a/substrate/frame/revive/src/evm/tracing.rs b/substrate/frame/revive/src/evm/tracing.rs index 74af195c204e0..64efbc1c47d47 100644 --- a/substrate/frame/revive/src/evm/tracing.rs +++ b/substrate/frame/revive/src/evm/tracing.rs @@ -38,7 +38,7 @@ pub enum Tracer { /// A tracer that traces the prestate. PrestateTracer(PrestateTracer), /// A tracer that traces opcodes. - OpcodeTracer(OpcodeTracer), + OpcodeTracer(OpcodeTracer U256>), } impl Tracer @@ -76,7 +76,7 @@ where } /// Get a mutable reference to the opcode tracer if this is an opcode tracer. - pub fn as_opcode_tracer(&mut self) -> Option<&mut OpcodeTracer> { + pub fn as_opcode_tracer(&mut self) -> Option<&mut OpcodeTracer U256>> { match self { Tracer::OpcodeTracer(inner) => Some(inner), _ => None, diff --git a/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs b/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs index b066c31e03b48..d15f1f0bfb6c8 100644 --- a/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs +++ b/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs @@ -17,7 +17,6 @@ use crate::{ evm::{OpcodeStep, OpcodeTrace, OpcodeTracerConfig}, - tracing::Tracing, DispatchError, ExecReturnValue, Weight, }; use alloc::{ @@ -25,16 +24,19 @@ use alloc::{ string::{String, ToString}, vec::Vec, }; -use sp_core::{H160, U256}; +use sp_core::H160; /// A tracer that traces opcode execution step-by-step. #[derive(Default, Debug, Clone, PartialEq)] -pub struct OpcodeTracer { +pub struct OpcodeTracer { + /// Map Weight to Gas equivalent. + gas_mapper: GasMapper, + /// The tracer configuration. config: OpcodeTracerConfig, /// The collected trace steps. - steps: Vec, + steps: Vec>, /// Current call depth. depth: u32, @@ -43,7 +45,7 @@ pub struct OpcodeTracer { step_count: u64, /// Total gas used by the transaction. - total_gas_used: u64, + total_gas_used: Gas, /// Whether the transaction failed. failed: bool, @@ -52,22 +54,29 @@ pub struct OpcodeTracer { return_value: Vec, } -impl OpcodeTracer { +impl OpcodeTracer { /// Create a new [`OpcodeTracer`] instance. - pub fn new(config: OpcodeTracerConfig) -> Self { + pub fn new(config: OpcodeTracerConfig, gas_mapper: GasMapper) -> Self + where + Gas: Default, + { Self { + gas_mapper, config, steps: Vec::new(), depth: 0, step_count: 0, - total_gas_used: 0, + total_gas_used: Gas::default(), failed: false, return_value: Vec::new(), } } /// Collect the traces and return them. - pub fn collect_trace(&mut self) -> OpcodeTrace { + pub fn collect_trace(&mut self) -> OpcodeTrace + where + Gas: Copy, + { let struct_logs = core::mem::take(&mut self.steps); let return_value = crate::evm::Bytes(self.return_value.clone()); @@ -92,23 +101,28 @@ impl OpcodeTracer { } /// Set the total gas used by the transaction. - pub fn set_total_gas_used(&mut self, gas_used: u64) { + pub fn set_total_gas_used(&mut self, gas_used: Gas) { self.total_gas_used = gas_used; } } -impl Tracing for OpcodeTracer { - fn get_opcode_tracer_config(&self) -> Option { - Some(self.config.clone()) +impl sp_core::U256> crate::tracing::OpcodeTracing + for OpcodeTracer +{ + fn stack_recording_enabled(&self) -> bool { + !self.config.disable_stack + } + + fn memory_recording_enabled(&self) -> bool { + self.config.enable_memory } fn record_opcode_step( &mut self, pc: u64, opcode: u8, - gas_before: u64, - gas_cost: u64, - depth: u32, + gas_before: Weight, + gas_cost: Weight, stack: Option>, memory: Option>, ) { @@ -122,31 +136,35 @@ impl Tracing for OpcodeTracer { let final_memory = if self.config.enable_memory { memory } else { None }; - // TODO: Storage capture would need to be implemented based on the EVM storage access - let storage = if !self.config.disable_storage { - // For now, return empty storage since we need to track storage changes - // This would need to be implemented with actual storage change tracking - Some(alloc::collections::BTreeMap::new()) - } else { - None - }; + // TODO: Storage capture // Create the opcode step + let gas_before_mapped = (self.gas_mapper)(gas_before); + let gas_cost_mapped = (self.gas_mapper)(gas_cost); + let step = OpcodeStep { pc, op: opcode, - gas: gas_before, - gas_cost, - depth, + gas: gas_before_mapped, + gas_cost: gas_cost_mapped, + depth: self.depth, stack: final_stack, memory: final_memory, - storage, + storage: None, error: None, }; self.steps.push(step); self.step_count += 1; } +} + +impl sp_core::U256 + 'static> crate::tracing::Tracing + for OpcodeTracer +{ + fn as_opcode_tracer(&mut self) -> Option<&mut dyn crate::tracing::OpcodeTracing> { + Some(self) + } fn enter_child_span( &mut self, @@ -154,7 +172,7 @@ impl Tracing for OpcodeTracer { _to: H160, _is_delegate_call: bool, _is_read_only: bool, - _value: U256, + _value: sp_core::U256, _input: &[u8], _gas_left: Weight, ) { @@ -173,8 +191,7 @@ impl Tracing for OpcodeTracer { // Set total gas used if this is the top-level call (depth 1, will become 0 after decrement) if self.depth == 1 { - // Convert Weight to gas units - this is a simplified conversion - self.set_total_gas_used(gas_used.ref_time() / 1_000_000); // Rough conversion + self.set_total_gas_used((self.gas_mapper)(gas_used)); } if self.depth > 0 { @@ -188,7 +205,7 @@ impl Tracing for OpcodeTracer { // Mark as failed if this is the top-level call if self.depth == 1 { self.mark_failed(); - self.set_total_gas_used(gas_used.ref_time() / 1_000_000); // Rough conversion + self.set_total_gas_used((self.gas_mapper)(gas_used)); } if self.depth > 0 { diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 848eda32b9374..68deb883b2683 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -1636,8 +1636,11 @@ where .into(), TracerType::PrestateTracer(config) => PrestateTracer::new(config.unwrap_or_default()).into(), - TracerType::StructLogger(config) => - OpcodeTracer::new(config.unwrap_or_default()).into(), + TracerType::StructLogger(config) => OpcodeTracer::new( + config.unwrap_or_default(), + Self::evm_gas_from_weight as fn(Weight) -> U256, + ) + .into(), } } diff --git a/substrate/frame/revive/src/tests/sol.rs b/substrate/frame/revive/src/tests/sol.rs index a785545f0f0cd..26fc516a5c92d 100644 --- a/substrate/frame/revive/src/tests/sol.rs +++ b/substrate/frame/revive/src/tests/sol.rs @@ -190,7 +190,7 @@ fn opcode_tracing_works() { limit: 5, }; - let mut tracer = OpcodeTracer::new(config); + let mut tracer = OpcodeTracer::new(config, |_| sp_core::U256::from(0u64)); let _result = trace(&mut tracer, || { builder::bare_call(addr) .data( @@ -212,9 +212,9 @@ fn opcode_tracing_works() { OpcodeStep { pc: 0, op: PUSH1, - gas: 0, - gas_cost: 0, - depth: 0, + gas: sp_core::U256::from(0u64), + gas_cost: sp_core::U256::from(0u64), + depth: 1, stack: Some(vec![]), memory: None, storage: None, @@ -223,10 +223,10 @@ fn opcode_tracing_works() { OpcodeStep { pc: 2, op: PUSH1, - gas: 0, - gas_cost: 0, - depth: 0, - stack: Some(vec![crate::evm::Bytes(U256::from(1).to_be_bytes_vec())]), + gas: sp_core::U256::from(0u64), + gas_cost: sp_core::U256::from(0u64), + depth: 1, + stack: Some(vec![crate::evm::Bytes(U256::from(0x80).to_be_bytes_vec())]), memory: None, storage: None, error: None, @@ -234,12 +234,12 @@ fn opcode_tracing_works() { OpcodeStep { pc: 4, op: MSTORE, - gas: 0, - gas_cost: 0, - depth: 0, + gas: sp_core::U256::from(0u64), + gas_cost: sp_core::U256::from(0u64), + depth: 1, stack: Some(vec![ - crate::evm::Bytes(U256::from(2).to_be_bytes_vec()), - crate::evm::Bytes(U256::from(1).to_be_bytes_vec()), + crate::evm::Bytes(U256::from(0x40).to_be_bytes_vec()), + crate::evm::Bytes(U256::from(0x80).to_be_bytes_vec()), ]), memory: None, storage: None, @@ -248,9 +248,9 @@ fn opcode_tracing_works() { OpcodeStep { pc: 5, op: CALLVALUE, - gas: 0, - gas_cost: 0, - depth: 0, + gas: sp_core::U256::from(0u64), + gas_cost: sp_core::U256::from(0u64), + depth: 1, stack: Some(vec![]), memory: None, storage: None, @@ -259,10 +259,10 @@ fn opcode_tracing_works() { OpcodeStep { pc: 6, op: DUP1, - gas: 0, - gas_cost: 0, - depth: 0, - stack: Some(vec![crate::evm::Bytes(U256::from(1).to_be_bytes_vec())]), + gas: sp_core::U256::from(0u64), + gas_cost: sp_core::U256::from(0u64), + depth: 1, + stack: Some(vec![crate::evm::Bytes(U256::from(0).to_be_bytes_vec())]), memory: None, storage: None, error: None, diff --git a/substrate/frame/revive/src/tracing.rs b/substrate/frame/revive/src/tracing.rs index 01f815c0f5087..30a9c8b1b6410 100644 --- a/substrate/frame/revive/src/tracing.rs +++ b/substrate/frame/revive/src/tracing.rs @@ -40,27 +40,32 @@ pub(crate) fn if_tracing R>(f: F) tracer::with(f) } +/// Trait for opcode-level tracing functionality. +pub trait OpcodeTracing { + /// Check if stack recording is enabled. + fn stack_recording_enabled(&self) -> bool; + /// Check if memory recording is enabled. + fn memory_recording_enabled(&self) -> bool; + /// Record an opcode step. + fn record_opcode_step( + &mut self, + pc: u64, + opcode: u8, + gas_before: Weight, + gas_cost: Weight, + stack: Option>, + memory: Option>, + ); +} + /// Defines methods to trace contract interactions. pub trait Tracing { - /// Get opcode tracer configuration if this tracer supports opcode-level tracing. + /// Get opcode tracer if this tracer supports opcode-level tracing. /// Returns None if opcode tracing is not supported. - fn get_opcode_tracer_config(&self) -> Option { + fn as_opcode_tracer(&mut self) -> Option<&mut dyn OpcodeTracing> { None } - /// Record an opcode step for opcode tracers. - fn record_opcode_step( - &mut self, - _pc: u64, - _opcode: u8, - _gas_before: u64, - _gas_cost: u64, - _depth: u32, - _stack: Option>, - _memory: Option>, - ) { - } - /// Register an address that should be traced. fn watch_address(&mut self, _addr: &H160) {} diff --git a/substrate/frame/revive/src/vm/evm.rs b/substrate/frame/revive/src/vm/evm.rs index d5ebc5395733c..db9e3a69f10f3 100644 --- a/substrate/frame/revive/src/vm/evm.rs +++ b/substrate/frame/revive/src/vm/evm.rs @@ -33,9 +33,7 @@ use revm::{ host::DummyHost, interpreter::{ExtBytecode, ReturnDataImpl, RuntimeFlags}, interpreter_action::InterpreterAction, - interpreter_types::{ - InputsTr, Jumps, LegacyBytecode, LoopControl, MemoryTr, ReturnData, StackTr, - }, + interpreter_types::{InputsTr, Jumps, LoopControl, MemoryTr, ReturnData}, CallInput, CallInputs, CallScheme, CreateInputs, FrameInput, Gas, InstructionResult, Interpreter, InterpreterResult, InterpreterTypes, SharedMemory, Stack, }, @@ -157,16 +155,17 @@ fn run<'a, E: Ext>( table: &revm::interpreter::InstructionTable, DummyHost>, ) -> InterpreterResult { let host = &mut DummyHost {}; - loop { - // Check if opcode tracing is enabled and get configuration - let opcode_config = - tracing::if_tracing(|tracer| tracer.get_opcode_tracer_config()).flatten(); - let action = if let Some(config) = opcode_config { - run_with_opcode_tracing(interpreter, table, host, config) - } else { - interpreter.run_plain(table, host) - }; + loop { + let action = tracing::if_tracing(|tracer| { + if let Some(opcode_tracer) = tracer.as_opcode_tracer() { + Some(run_with_opcode_tracing(interpreter, table, host, opcode_tracer)) + } else { + None + } + }) + .flatten() + .unwrap_or_else(|| interpreter.run_plain(table, host)); match action { InterpreterAction::Return(result) => { @@ -193,112 +192,80 @@ fn run_with_opcode_tracing<'a, E: Ext>( interpreter: &mut Interpreter>, table: &revm::interpreter::InstructionTable, DummyHost>, host: &mut DummyHost, - config: crate::evm::OpcodeTracerConfig, -) -> InterpreterAction -where - EVMInterpreter<'a, E>: InterpreterTypes, -{ - use revm::bytecode::OpCode; + opcode_tracer: &mut dyn crate::tracing::OpcodeTracing, +) -> InterpreterAction { + use revm::interpreter::InstructionContext; + + while interpreter.bytecode.is_not_end() { + let opcode = interpreter.bytecode.opcode(); + let pc = interpreter.bytecode.pc(); + + let stack_data = if opcode_tracer.stack_recording_enabled() { + // Get actual stack values using the data() method + let stack_values = interpreter.stack.data(); + let mut stack_bytes = Vec::new(); + + // Convert stack values to bytes in reverse order (top of stack first) + for value in stack_values.iter().rev() { + let bytes = value.to_be_bytes_vec(); + stack_bytes.push(crate::evm::Bytes(bytes)); + } - // Track instruction count for limiting - let mut _instruction_count = 0u64; + Some(stack_bytes) + } else { + None + }; - loop { - // Check if bytecode execution is complete - if interpreter.bytecode.is_not_end() { - // Get current program counter and opcode - let pc = interpreter.bytecode.pc(); - let opcode_byte = interpreter.bytecode.bytecode_slice()[pc]; - let opcode = OpCode::new(opcode_byte).unwrap_or(unsafe { OpCode::new_unchecked(0xFF) }); // INVALID opcode - - // Record gas before execution - let gas_before = interpreter.gas.remaining(); - - // Capture stack data only if enabled - let stack_data = if !config.disable_stack { - // Get stack length - this is available through the trait - let stack_len = interpreter.stack.len(); - - // Create a simplified stack representation showing the stack has items - // Unfortunately, we can't directly read stack values without modifying the stack - // So we'll show placeholder values indicating stack depth - let mut stack_bytes = Vec::new(); - for i in 0..core::cmp::min(stack_len, 16) { - // Limit to 16 items for performance - let value = (stack_len - i) as u64; - let mut bytes = [0u8; 32]; - bytes[24..32].copy_from_slice(&value.to_be_bytes()); - stack_bytes.push(crate::evm::Bytes(bytes.to_vec())); - } + let memory_data = if opcode_tracer.memory_recording_enabled() { + let memory_size = interpreter.memory.size(); - Some(stack_bytes) + if memory_size == 0 { + Some(Vec::new()) } else { - None - }; - - // Capture memory data only if enabled - let memory_data = if config.enable_memory { - // Get memory size - this is available through the trait - let memory_size = interpreter.memory.size(); - - if memory_size == 0 { - Some(Vec::new()) - } else { - let mut memory_bytes = Vec::new(); - // Read memory in 32-byte chunks, limiting to reasonable size - let chunks_to_read = core::cmp::min(memory_size / 32 + 1, 16); // Limit to 16 chunks - - for i in 0..chunks_to_read { - let offset = i * 32; - let end = core::cmp::min(offset + 32, memory_size); - - if offset < memory_size { - // Use the slice method available from the MemoryTr trait - let slice = interpreter.memory.slice(offset..end); - - // Convert to bytes, padding to 32 bytes - let mut chunk_bytes = vec![0u8; 32]; - for (i, &byte) in slice.iter().enumerate().take(32) { - chunk_bytes[i] = byte; - } - memory_bytes.push(crate::evm::Bytes(chunk_bytes)); + let mut memory_bytes = Vec::new(); + // Read memory in 32-byte chunks, limiting to reasonable size + let chunks_to_read = core::cmp::min(memory_size / 32 + 1, 16); // Limit to 16 chunks + + for i in 0..chunks_to_read { + let offset = i * 32; + let end = core::cmp::min(offset + 32, memory_size); + + if offset < memory_size { + let slice = interpreter.memory.slice(offset..end); + + // Convert to bytes, padding to 32 bytes + let mut chunk_bytes = vec![0u8; 32]; + for (i, &byte) in slice.iter().enumerate().take(32) { + chunk_bytes[i] = byte; } + memory_bytes.push(crate::evm::Bytes(chunk_bytes)); } - - Some(memory_bytes) } - } else { - None - }; - - // Execute the instruction step - interpreter.step(table, host); - - // Calculate gas cost - let gas_after = interpreter.gas.remaining(); - let gas_cost = gas_before.saturating_sub(gas_after); - - // Record the step in the tracer - tracing::if_tracing(|tracer| { - tracer.record_opcode_step( - pc as u64, - opcode.get(), - gas_before, - gas_cost, - 0, // TODO: track actual call depth from the call stack - stack_data, - memory_data, - ); - }); - _instruction_count += 1; + Some(memory_bytes) + } } else { - // Bytecode execution is complete - break; - } + None + }; + + let gas_before = interpreter.extend.gas_meter().gas_left(); + + interpreter.bytecode.relative_jump(1); + let context = InstructionContext { interpreter, host }; + table[opcode as usize](context); + let gas_cost = gas_before.saturating_sub(interpreter.extend.gas_meter().gas_left()); + + opcode_tracer.record_opcode_step( + pc as u64, + opcode, + gas_before, + gas_cost, + stack_data, + memory_data, + ); } + interpreter.bytecode.revert_to_previous_pointer(); - // Return the final result interpreter.take_next_action() } From ccf30258404083648a4771e900d54eaa628a983f Mon Sep 17 00:00:00 2001 From: pgherveou Date: Sat, 13 Sep 2025 16:50:59 +0800 Subject: [PATCH 14/76] fixes --- .../revive/src/evm/tracing/opcode_tracing.rs | 100 ++++++++++++++---- substrate/frame/revive/src/tracing.rs | 16 ++- substrate/frame/revive/src/vm/evm.rs | 68 ++---------- 3 files changed, 94 insertions(+), 90 deletions(-) diff --git a/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs b/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs index d15f1f0bfb6c8..141a42cf60a1a 100644 --- a/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs +++ b/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs @@ -24,6 +24,7 @@ use alloc::{ string::{String, ToString}, vec::Vec, }; +use revm::interpreter::interpreter_types::MemoryTr; use sp_core::H160; /// A tracer that traces opcode execution step-by-step. @@ -52,6 +53,12 @@ pub struct OpcodeTracer { /// The return value of the transaction. return_value: Vec, + + /// Pending step that's waiting for gas cost to be recorded. + pending_step: Option>, + + /// Gas before executing the current pending step. + pending_gas_before: Option, } impl OpcodeTracer { @@ -69,6 +76,8 @@ impl OpcodeTracer { total_gas_used: Gas::default(), failed: false, return_value: Vec::new(), + pending_step: None, + pending_gas_before: None, } } @@ -109,54 +118,99 @@ impl OpcodeTracer { impl sp_core::U256> crate::tracing::OpcodeTracing for OpcodeTracer { - fn stack_recording_enabled(&self) -> bool { - !self.config.disable_stack - } - - fn memory_recording_enabled(&self) -> bool { - self.config.enable_memory - } - - fn record_opcode_step( + fn enter_opcode( &mut self, pc: u64, opcode: u8, gas_before: Weight, - gas_cost: Weight, - stack: Option>, - memory: Option>, + stack: &revm::interpreter::Stack, + memory: &revm::interpreter::SharedMemory, ) { - // Check step limit + // Check step limit - if exceeded, don't record anything if self.config.limit > 0 && self.step_count >= self.config.limit { return; } - // Apply configuration settings - let final_stack = if self.config.disable_stack { None } else { stack }; + // Extract stack data if enabled + let stack_data = if !self.config.disable_stack { + // Get actual stack values using the data() method + let stack_values = stack.data(); + let mut stack_bytes = Vec::new(); - let final_memory = if self.config.enable_memory { memory } else { None }; + // Convert stack values to bytes in reverse order (top of stack first) + for value in stack_values.iter().rev() { + let bytes = value.to_be_bytes_vec(); + stack_bytes.push(crate::evm::Bytes(bytes)); + } - // TODO: Storage capture + Some(stack_bytes) + } else { + None + }; - // Create the opcode step + // Extract memory data if enabled + let memory_data = if self.config.enable_memory { + let memory_size = memory.size(); + + if memory_size == 0 { + Some(Vec::new()) + } else { + let mut memory_bytes = Vec::new(); + // Read memory in 32-byte chunks, limiting to reasonable size + let chunks_to_read = core::cmp::min(memory_size / 32 + 1, 16); // Limit to 16 chunks + + for i in 0..chunks_to_read { + let offset = i * 32; + let end = core::cmp::min(offset + 32, memory_size); + + if offset < memory_size { + let slice = memory.slice(offset..end); + + // Convert to bytes, padding to 32 bytes + let mut chunk_bytes = vec![0u8; 32]; + for (i, &byte) in slice.iter().enumerate().take(32) { + chunk_bytes[i] = byte; + } + memory_bytes.push(crate::evm::Bytes(chunk_bytes)); + } + } + + Some(memory_bytes) + } + } else { + None + }; + + // Create the pending opcode step (without gas cost) let gas_before_mapped = (self.gas_mapper)(gas_before); - let gas_cost_mapped = (self.gas_mapper)(gas_cost); let step = OpcodeStep { pc, op: opcode, gas: gas_before_mapped, - gas_cost: gas_cost_mapped, + gas_cost: sp_core::U256::zero(), // Will be set in exit_opcode depth: self.depth, - stack: final_stack, - memory: final_memory, + stack: stack_data, + memory: memory_data, storage: None, error: None, }; - self.steps.push(step); + self.pending_step = Some(step); + self.pending_gas_before = Some(gas_before); self.step_count += 1; } + + fn exit_opcode(&mut self, gas_left: Weight) { + if let Some(mut step) = self.pending_step.take() { + if let Some(gas_before) = self.pending_gas_before.take() { + let gas_cost = gas_before.saturating_sub(gas_left); + let gas_cost_mapped = (self.gas_mapper)(gas_cost); + step.gas_cost = gas_cost_mapped; + } + self.steps.push(step); + } + } } impl sp_core::U256 + 'static> crate::tracing::Tracing diff --git a/substrate/frame/revive/src/tracing.rs b/substrate/frame/revive/src/tracing.rs index 30a9c8b1b6410..5290d6930adbc 100644 --- a/substrate/frame/revive/src/tracing.rs +++ b/substrate/frame/revive/src/tracing.rs @@ -42,20 +42,18 @@ pub(crate) fn if_tracing R>(f: F) /// Trait for opcode-level tracing functionality. pub trait OpcodeTracing { - /// Check if stack recording is enabled. - fn stack_recording_enabled(&self) -> bool; - /// Check if memory recording is enabled. - fn memory_recording_enabled(&self) -> bool; - /// Record an opcode step. - fn record_opcode_step( + /// Called before an opcode is executed. + fn enter_opcode( &mut self, pc: u64, opcode: u8, gas_before: Weight, - gas_cost: Weight, - stack: Option>, - memory: Option>, + stack: &revm::interpreter::Stack, + memory: &revm::interpreter::SharedMemory, ); + + /// Called after an opcode is executed to record the gas cost. + fn exit_opcode(&mut self, gas_left: Weight); } /// Defines methods to trace contract interactions. diff --git a/substrate/frame/revive/src/vm/evm.rs b/substrate/frame/revive/src/vm/evm.rs index db9e3a69f10f3..feb7997de3975 100644 --- a/substrate/frame/revive/src/vm/evm.rs +++ b/substrate/frame/revive/src/vm/evm.rs @@ -199,70 +199,22 @@ fn run_with_opcode_tracing<'a, E: Ext>( while interpreter.bytecode.is_not_end() { let opcode = interpreter.bytecode.opcode(); let pc = interpreter.bytecode.pc(); - - let stack_data = if opcode_tracer.stack_recording_enabled() { - // Get actual stack values using the data() method - let stack_values = interpreter.stack.data(); - let mut stack_bytes = Vec::new(); - - // Convert stack values to bytes in reverse order (top of stack first) - for value in stack_values.iter().rev() { - let bytes = value.to_be_bytes_vec(); - stack_bytes.push(crate::evm::Bytes(bytes)); - } - - Some(stack_bytes) - } else { - None - }; - - let memory_data = if opcode_tracer.memory_recording_enabled() { - let memory_size = interpreter.memory.size(); - - if memory_size == 0 { - Some(Vec::new()) - } else { - let mut memory_bytes = Vec::new(); - // Read memory in 32-byte chunks, limiting to reasonable size - let chunks_to_read = core::cmp::min(memory_size / 32 + 1, 16); // Limit to 16 chunks - - for i in 0..chunks_to_read { - let offset = i * 32; - let end = core::cmp::min(offset + 32, memory_size); - - if offset < memory_size { - let slice = interpreter.memory.slice(offset..end); - - // Convert to bytes, padding to 32 bytes - let mut chunk_bytes = vec![0u8; 32]; - for (i, &byte) in slice.iter().enumerate().take(32) { - chunk_bytes[i] = byte; - } - memory_bytes.push(crate::evm::Bytes(chunk_bytes)); - } - } - - Some(memory_bytes) - } - } else { - None - }; - let gas_before = interpreter.extend.gas_meter().gas_left(); - interpreter.bytecode.relative_jump(1); - let context = InstructionContext { interpreter, host }; - table[opcode as usize](context); - let gas_cost = gas_before.saturating_sub(interpreter.extend.gas_meter().gas_left()); - - opcode_tracer.record_opcode_step( + opcode_tracer.enter_opcode( pc as u64, opcode, gas_before, - gas_cost, - stack_data, - memory_data, + &interpreter.stack, + &interpreter.memory, ); + + interpreter.bytecode.relative_jump(1); + let context = InstructionContext { interpreter, host }; + table[opcode as usize](context); + let gas_left = interpreter.extend.gas_meter().gas_left(); + + opcode_tracer.exit_opcode(gas_left); } interpreter.bytecode.revert_to_previous_pointer(); From 45d0c4cc984415644f18f215e14ce487adb4beb5 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Sat, 13 Sep 2025 19:12:19 +0800 Subject: [PATCH 15/76] fixes --- .../revive/src/evm/tracing/opcode_tracing.rs | 21 ++++------ substrate/frame/revive/src/tracing.rs | 33 ++++++++-------- substrate/frame/revive/src/vm/evm.rs | 38 ++++++++++--------- 3 files changed, 43 insertions(+), 49 deletions(-) diff --git a/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs b/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs index 141a42cf60a1a..c3e877bb6520d 100644 --- a/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs +++ b/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs @@ -16,16 +16,17 @@ // limitations under the License. use crate::{ - evm::{OpcodeStep, OpcodeTrace, OpcodeTracerConfig}, + evm::{tracing::Tracing, OpcodeStep, OpcodeTrace, OpcodeTracerConfig}, DispatchError, ExecReturnValue, Weight, }; use alloc::{ format, string::{String, ToString}, + vec, vec::Vec, }; use revm::interpreter::interpreter_types::MemoryTr; -use sp_core::H160; +use sp_core::{H160, U256}; /// A tracer that traces opcode execution step-by-step. #[derive(Default, Debug, Clone, PartialEq)] @@ -115,9 +116,11 @@ impl OpcodeTracer { } } -impl sp_core::U256> crate::tracing::OpcodeTracing - for OpcodeTracer -{ +impl U256> Tracing for OpcodeTracer { + fn is_opcode_tracing_enabled(&self) -> bool { + true + } + fn enter_opcode( &mut self, pc: u64, @@ -211,14 +214,6 @@ impl sp_core::U256> crate::tracing::OpcodeTracing self.steps.push(step); } } -} - -impl sp_core::U256 + 'static> crate::tracing::Tracing - for OpcodeTracer -{ - fn as_opcode_tracer(&mut self) -> Option<&mut dyn crate::tracing::OpcodeTracing> { - Some(self) - } fn enter_child_span( &mut self, diff --git a/substrate/frame/revive/src/tracing.rs b/substrate/frame/revive/src/tracing.rs index 5290d6930adbc..1bd4b69cd477c 100644 --- a/substrate/frame/revive/src/tracing.rs +++ b/substrate/frame/revive/src/tracing.rs @@ -40,29 +40,26 @@ pub(crate) fn if_tracing R>(f: F) tracer::with(f) } -/// Trait for opcode-level tracing functionality. -pub trait OpcodeTracing { +/// Defines methods to trace contract interactions. +pub trait Tracing { + /// Check if opcode tracing is enabled. + fn is_opcode_tracing_enabled(&self) -> bool { + false + } + /// Called before an opcode is executed. fn enter_opcode( &mut self, - pc: u64, - opcode: u8, - gas_before: Weight, - stack: &revm::interpreter::Stack, - memory: &revm::interpreter::SharedMemory, - ); + _pc: u64, + _opcode: u8, + _gas_before: Weight, + _stack: &revm::interpreter::Stack, + _memory: &revm::interpreter::SharedMemory, + ) { + } /// Called after an opcode is executed to record the gas cost. - fn exit_opcode(&mut self, gas_left: Weight); -} - -/// Defines methods to trace contract interactions. -pub trait Tracing { - /// Get opcode tracer if this tracer supports opcode-level tracing. - /// Returns None if opcode tracing is not supported. - fn as_opcode_tracer(&mut self) -> Option<&mut dyn OpcodeTracing> { - None - } + fn exit_opcode(&mut self, _gas_left: Weight) {} /// Register an address that should be traced. fn watch_address(&mut self, _addr: &H160) {} diff --git a/substrate/frame/revive/src/vm/evm.rs b/substrate/frame/revive/src/vm/evm.rs index feb7997de3975..6f1b73e6f397d 100644 --- a/substrate/frame/revive/src/vm/evm.rs +++ b/substrate/frame/revive/src/vm/evm.rs @@ -157,15 +157,14 @@ fn run<'a, E: Ext>( let host = &mut DummyHost {}; loop { - let action = tracing::if_tracing(|tracer| { - if let Some(opcode_tracer) = tracer.as_opcode_tracer() { - Some(run_with_opcode_tracing(interpreter, table, host, opcode_tracer)) - } else { - None - } - }) - .flatten() - .unwrap_or_else(|| interpreter.run_plain(table, host)); + let use_opcode_tracing = tracing::if_tracing(|tracer| tracer.is_opcode_tracing_enabled()) + .unwrap_or(false); + + let action = if use_opcode_tracing { + run_with_opcode_tracing(interpreter, table, host) + } else { + interpreter.run_plain(table, host) + }; match action { InterpreterAction::Return(result) => { @@ -192,7 +191,6 @@ fn run_with_opcode_tracing<'a, E: Ext>( interpreter: &mut Interpreter>, table: &revm::interpreter::InstructionTable, DummyHost>, host: &mut DummyHost, - opcode_tracer: &mut dyn crate::tracing::OpcodeTracing, ) -> InterpreterAction { use revm::interpreter::InstructionContext; @@ -201,20 +199,24 @@ fn run_with_opcode_tracing<'a, E: Ext>( let pc = interpreter.bytecode.pc(); let gas_before = interpreter.extend.gas_meter().gas_left(); - opcode_tracer.enter_opcode( - pc as u64, - opcode, - gas_before, - &interpreter.stack, - &interpreter.memory, - ); + tracing::if_tracing(|tracer| { + tracer.enter_opcode( + pc as u64, + opcode, + gas_before, + &interpreter.stack, + &interpreter.memory, + ); + }); interpreter.bytecode.relative_jump(1); let context = InstructionContext { interpreter, host }; table[opcode as usize](context); let gas_left = interpreter.extend.gas_meter().gas_left(); - opcode_tracer.exit_opcode(gas_left); + tracing::if_tracing(|tracer| { + tracer.exit_opcode(gas_left); + }); } interpreter.bytecode.revert_to_previous_pointer(); From 3a8d20c58cb91fc2d0be6d0b9a345d31004a46b6 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Sun, 14 Sep 2025 09:19:37 +0800 Subject: [PATCH 16/76] wip --- .../frame/revive/rpc/src/apis/debug_apis.rs | 1 + substrate/frame/revive/src/evm/api/byte.rs | 23 +++ .../revive/src/evm/api/debug_rpc_types.rs | 132 +++++++----------- .../revive/src/evm/tracing/opcode_tracing.rs | 13 +- substrate/frame/revive/src/tests/sol.rs | 22 +-- 5 files changed, 92 insertions(+), 99 deletions(-) diff --git a/substrate/frame/revive/rpc/src/apis/debug_apis.rs b/substrate/frame/revive/rpc/src/apis/debug_apis.rs index 5d2c61458c55a..e9e1e584c7c83 100644 --- a/substrate/frame/revive/rpc/src/apis/debug_apis.rs +++ b/substrate/frame/revive/rpc/src/apis/debug_apis.rs @@ -113,6 +113,7 @@ impl DebugRpcServer for DebugRpcServerImpl { tracer_config: TracerConfig, ) -> RpcResult { let TracerConfig { config, timeout } = tracer_config; + dbg!(&config); with_timeout(timeout, self.client.trace_call(transaction, block, config)).await } } diff --git a/substrate/frame/revive/src/evm/api/byte.rs b/substrate/frame/revive/src/evm/api/byte.rs index 57fa07e17d985..9a73ca862c70b 100644 --- a/substrate/frame/revive/src/evm/api/byte.rs +++ b/substrate/frame/revive/src/evm/api/byte.rs @@ -75,6 +75,17 @@ impl Bytes { pub fn is_empty(&self) -> bool { self.0.is_empty() } + + /// Convert to minimal hex format without padding 0s + pub fn to_short_hex(&self) -> alloc::string::String { + let word = sp_core::U256::from_big_endian(&self.0); + alloc::format!("0x{:x}", word) + } + + /// Convert to hex format without "0x" prefix + pub fn to_hex_no_prefix(&self) -> alloc::string::String { + alloy_core::hex::encode(&self.0) + } } impl_hex!(Byte, u8, 0u8); @@ -83,6 +94,18 @@ impl_hex!(Bytes8, [u8; 8], [0u8; 8]); impl_hex!(Bytes32, [u8; 32], [0u8; 32]); impl_hex!(Bytes256, [u8; 256], [0u8; 256]); +#[test] +fn test_to_short_hex() { + let bytes = Bytes(crate::U256::from(4).to_big_endian().to_vec()); + assert_eq!(bytes.to_short_hex(), "0x4"); +} + +#[test] +fn test_to_hex_no_prefix() { + let bytes = Bytes(vec![0x12, 0x34, 0x56, 0x78]); + assert_eq!(bytes.to_hex_no_prefix(), "12345678"); +} + #[test] fn serialize_works() { let a = Byte(42); diff --git a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs index 4fbfbabf93197..eee4509dbe5be 100644 --- a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs +++ b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs @@ -21,7 +21,7 @@ use codec::{Decode, Encode}; use derive_more::From; use scale_info::TypeInfo; use serde::{ - de::{self, Deserializer}, + de::Deserializer, ser::{SerializeMap, Serializer}, Deserialize, Serialize, }; @@ -84,84 +84,24 @@ impl<'de> Deserialize<'de> for TracerConfig { where D: Deserializer<'de>, { - #[derive(Deserialize, Default)] + #[derive(Default, Deserialize)] #[serde(default, rename_all = "camelCase")] - struct RawTracerConfig<'a> { - tracer: Option<&'a str>, - tracer_config: Option, + struct TracerConfigInner { + #[serde(flatten)] + config: Option, - #[serde(with = "humantime_serde", default)] - timeout: Option, - } + #[serde(flatten)] + opcode_config: Option, - // We can't use untagged enums reliably because they may pick the wrong variant - // Instead, we'll use a custom deserializer that tries each type based on tracer name - #[derive(Deserialize, Default)] - #[serde(default, rename_all = "camelCase")] - struct TracerConfigInner { - // CallTracer fields - with_logs: Option, - only_top_call: Option, - - // PrestateTracer fields - diff_mode: Option, - disable_code: Option, - - // OpcodeTracer fields - enable_memory: Option, - disable_stack: Option, - - // This field exists in both PrestateTracer and OpcodeTracer - disable_storage: Option, - enable_return_data: Option, - limit: Option, + #[serde(with = "humantime_serde")] + timeout: Option, } - let raw = RawTracerConfig::deserialize(deserializer)?; - let tracer_type = raw.tracer.unwrap_or_else(|| "structLogger"); - let config = match tracer_type { - "callTracer" => - if let Some(inner) = raw.tracer_config { - let call_config = CallTracerConfig { - with_logs: inner.with_logs.unwrap_or(true), - only_top_call: inner.only_top_call.unwrap_or(false), - }; - TracerType::CallTracer(Some(call_config)) - } else { - TracerType::CallTracer(None) - }, - "prestateTracer" => - if let Some(inner) = raw.tracer_config { - let prestate_config = PrestateTracerConfig { - diff_mode: inner.diff_mode.unwrap_or(false), - disable_storage: inner.disable_storage.unwrap_or(false), - disable_code: inner.disable_code.unwrap_or(false), - }; - TracerType::PrestateTracer(Some(prestate_config)) - } else { - TracerType::PrestateTracer(None) - }, - "structLogger" => - if let Some(inner) = raw.tracer_config { - let opcode_config = OpcodeTracerConfig { - enable_memory: inner.enable_memory.unwrap_or(false), - disable_stack: inner.disable_stack.unwrap_or(false), - disable_storage: inner.disable_storage.unwrap_or(false), - enable_return_data: inner.enable_return_data.unwrap_or(false), - limit: inner.limit.unwrap_or(0), - }; - TracerType::StructLogger(Some(opcode_config)) - } else { - TracerType::StructLogger(None) - }, - _ => - return Err(de::Error::unknown_variant( - &tracer_type, - &["callTracer", "prestateTracer", "structLogger"], - )), - }; - - Ok(TracerConfig { config, timeout: raw.timeout }) + let inner = TracerConfigInner::deserialize(deserializer)?; + Ok(TracerConfig { + config: inner.config.unwrap_or_else(|| TracerType::StructLogger(inner.opcode_config)), + timeout: inner.timeout, + }) } } @@ -247,13 +187,13 @@ impl Default for OpcodeTracerConfig { /// By default if not specified the tracer is a StructLogger, and it's config is passed inline /// /// ```json -/// { "tracer": null, "tracerConfig": { "enableMemory": true, "disableStack": false, "disableStorage": false, "enableReturnData": true } } +/// { "tracer": null, "enableMemory": true, "disableStack": false, "disableStorage": false, "enableReturnData": true } /// ``` #[test] fn test_tracer_config_serialization() { let tracers = vec![ ( - r#"{ "tracer": null, "tracerConfig": { "enableMemory": true, "disableStack": false, "disableStorage": false, "enableReturnData": true } }"#, + r#"{ "enableMemory": true, "disableStack": false, "disableStorage": false, "enableReturnData": true }"#, TracerConfig { config: TracerType::StructLogger(Some(OpcodeTracerConfig { enable_memory: true, @@ -265,6 +205,13 @@ fn test_tracer_config_serialization() { timeout: None, }, ), + ( + r#"{ }"#, + TracerConfig { + config: TracerType::StructLogger(Some(OpcodeTracerConfig::default())), + timeout: None, + }, + ), ( r#"{"tracer": "callTracer"}"#, TracerConfig { config: TracerType::CallTracer(None), timeout: None }, @@ -437,7 +384,12 @@ pub struct OpcodeTrace { impl Default for OpcodeTrace { fn default() -> Self { - Self { gas: Gas::default(), failed: false, return_value: Bytes::default(), struct_logs: Vec::new() } + Self { + gas: Gas::default(), + failed: false, + return_value: Bytes::default(), + struct_logs: Vec::new(), + } } } @@ -458,11 +410,11 @@ pub struct OpcodeStep { /// Current call depth. pub depth: u32, /// EVM stack contents (optional based on config). - #[serde(skip_serializing_if = "Option::is_none")] - pub stack: Option>, + #[serde(serialize_with = "serialize_stack_minimal")] + pub stack: Vec, /// EVM memory contents (optional based on config). - #[serde(skip_serializing_if = "Option::is_none")] - pub memory: Option>, + #[serde(skip_serializing_if = "Vec::is_empty", serialize_with = "serialize_memory_no_prefix")] + pub memory: Vec, /// Contract storage changes (optional based on config). #[serde(skip_serializing_if = "Option::is_none")] pub storage: Option>, @@ -891,3 +843,21 @@ pub struct TransactionTrace { #[serde(rename = "result")] pub trace: Trace, } + +/// Serialize stack values using minimal hex format (like Geth) +fn serialize_stack_minimal(stack: &Vec, serializer: S) -> Result +where + S: serde::Serializer, +{ + let minimal_values: Vec = stack.iter().map(|bytes| bytes.to_short_hex()).collect(); + minimal_values.serialize(serializer) +} + +/// Serialize memory values without "0x" prefix (like Geth) +fn serialize_memory_no_prefix(memory: &Vec, serializer: S) -> Result +where + S: serde::Serializer, +{ + let hex_values: Vec = memory.iter().map(|bytes| bytes.to_hex_no_prefix()).collect(); + hex_values.serialize(serializer) +} diff --git a/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs b/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs index c3e877bb6520d..af6ff4f7ed387 100644 --- a/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs +++ b/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs @@ -140,15 +140,14 @@ impl U256> Tracing for OpcodeTracer U256> Tracing for OpcodeTracer U256> Tracing for OpcodeTracer Date: Sun, 14 Sep 2025 13:43:30 +0200 Subject: [PATCH 17/76] wip --- .../revive/src/evm/api/debug_rpc_types.rs | 14 ++- .../revive/src/evm/tracing/opcode_tracing.rs | 90 ++++++++++++++----- substrate/frame/revive/src/tests/sol.rs | 8 +- substrate/frame/revive/src/tracing.rs | 1 + substrate/frame/revive/src/vm/evm.rs | 1 + 5 files changed, 89 insertions(+), 25 deletions(-) diff --git a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs index eee4509dbe5be..cb90c9cffd386 100644 --- a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs +++ b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs @@ -160,6 +160,9 @@ pub struct OpcodeTracerConfig { /// Limit number of steps captured (default: 0, no limit) pub limit: u64, + + /// Maximum number of memory words to capture per step (default: 16) + pub memory_word_limit: u32, } impl Default for OpcodeTracerConfig { @@ -170,6 +173,7 @@ impl Default for OpcodeTracerConfig { disable_storage: false, enable_return_data: false, limit: 0, + memory_word_limit: 16, } } } @@ -201,6 +205,7 @@ fn test_tracer_config_serialization() { disable_storage: false, enable_return_data: true, limit: 0, + memory_word_limit: 16, })), timeout: None, }, @@ -409,15 +414,18 @@ pub struct OpcodeStep { pub gas_cost: Gas, /// Current call depth. pub depth: u32, - /// EVM stack contents (optional based on config). + /// EVM stack contents. #[serde(serialize_with = "serialize_stack_minimal")] pub stack: Vec, - /// EVM memory contents (optional based on config). + /// EVM memory contents. #[serde(skip_serializing_if = "Vec::is_empty", serialize_with = "serialize_memory_no_prefix")] pub memory: Vec, - /// Contract storage changes (optional based on config). + /// Contract storage changes. #[serde(skip_serializing_if = "Option::is_none")] pub storage: Option>, + /// Return data from last frame output. + #[serde(skip_serializing_if = "Bytes::is_empty")] + pub return_data: Bytes, /// Any error that occurred during opcode execution. #[serde(skip_serializing_if = "Option::is_none")] pub error: Option, diff --git a/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs b/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs index af6ff4f7ed387..667ad606dcd24 100644 --- a/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs +++ b/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs @@ -14,15 +14,14 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. - use crate::{ - evm::{tracing::Tracing, OpcodeStep, OpcodeTrace, OpcodeTracerConfig}, - DispatchError, ExecReturnValue, Weight, + evm::{tracing::Tracing, Bytes, OpcodeStep, OpcodeTrace, OpcodeTracerConfig}, + DispatchError, ExecReturnValue, Key, Weight, }; use alloc::{ + collections::BTreeMap, format, string::{String, ToString}, - vec, vec::Vec, }; use revm::interpreter::interpreter_types::MemoryTr; @@ -60,6 +59,9 @@ pub struct OpcodeTracer { /// Gas before executing the current pending step. pending_gas_before: Option, + + /// List of storage per call + storages_per_call: Vec>, } impl OpcodeTracer { @@ -79,6 +81,8 @@ impl OpcodeTracer { return_value: Vec::new(), pending_step: None, pending_gas_before: None, + // Initialize with one storage map for the root call + storages_per_call: vec![Default::default()], } } @@ -128,6 +132,7 @@ impl U256> Tracing for OpcodeTracer 0 && self.step_count >= self.config.limit { @@ -158,23 +163,14 @@ impl U256> Tracing for OpcodeTracer U256> Tracing for OpcodeTracer U256> Tracing for OpcodeTracer U256> Tracing for OpcodeTracer U256> Tracing for OpcodeTracer 0 { self.depth -= 1; } @@ -259,5 +266,46 @@ impl U256> Tracing for OpcodeTracer 0 { self.depth -= 1; } + + self.storages_per_call.pop(); + } + + fn storage_write(&mut self, key: &Key, _old_value: Option>, new_value: Option<&[u8]>) { + // Only track storage if not disabled + if self.config.disable_storage { + return; + } + + // Get the last storage map for the current call depth + if let Some(storage) = self.storages_per_call.last_mut() { + let key_bytes = crate::evm::Bytes(key.unhashed().to_vec()); + let value_bytes = crate::evm::Bytes(new_value.map(|v| v.to_vec()).unwrap_or_default()); + storage.insert(key_bytes, value_bytes); + + // Set storage on the pending step + if let Some(ref mut step) = self.pending_step { + step.storage = Some(storage.clone()); + } + } + } + + fn storage_read(&mut self, key: &Key, value: Option<&[u8]>) { + // Only track storage if not disabled + if self.config.disable_storage { + return; + } + + // Get the last storage map for the current call depth + if let Some(storage) = self.storages_per_call.last_mut() { + let key_bytes = crate::evm::Bytes(key.unhashed().to_vec()); + storage.entry(key_bytes).or_insert_with(|| { + crate::evm::Bytes(value.map(|v| v.to_vec()).unwrap_or_default()) + }); + + // Set storage on the pending step + if let Some(ref mut step) = self.pending_step { + step.storage = Some(storage.clone()); + } + } } } diff --git a/substrate/frame/revive/src/tests/sol.rs b/substrate/frame/revive/src/tests/sol.rs index 1c74e1e43bd52..b4f9b29fa9f4d 100644 --- a/substrate/frame/revive/src/tests/sol.rs +++ b/substrate/frame/revive/src/tests/sol.rs @@ -188,6 +188,7 @@ fn opcode_tracing_works() { disable_storage: true, enable_return_data: true, limit: 5, + memory_word_limit: 16, }; let mut tracer = OpcodeTracer::new(config, |_| sp_core::U256::from(0u64)); @@ -218,6 +219,7 @@ fn opcode_tracing_works() { stack: vec![], memory: vec![], storage: None, + return_data: crate::evm::Bytes::default(), error: None, }, OpcodeStep { @@ -229,6 +231,7 @@ fn opcode_tracing_works() { stack: vec![crate::evm::Bytes(U256::from(0x80).to_be_bytes_vec())], memory: vec![], storage: None, + return_data: crate::evm::Bytes::default(), error: None, }, OpcodeStep { @@ -238,11 +241,12 @@ fn opcode_tracing_works() { gas_cost: sp_core::U256::from(0u64), depth: 1, stack: vec![ - crate::evm::Bytes(U256::from(0x40).to_be_bytes_vec()), crate::evm::Bytes(U256::from(0x80).to_be_bytes_vec()), + crate::evm::Bytes(U256::from(0x40).to_be_bytes_vec()), ], memory: vec![], storage: None, + return_data: crate::evm::Bytes::default(), error: None, }, OpcodeStep { @@ -254,6 +258,7 @@ fn opcode_tracing_works() { stack: vec![], memory: vec![], storage: None, + return_data: crate::evm::Bytes::default(), error: None, }, OpcodeStep { @@ -265,6 +270,7 @@ fn opcode_tracing_works() { stack: vec![crate::evm::Bytes(U256::from(0).to_be_bytes_vec())], memory: vec![], storage: None, + return_data: crate::evm::Bytes::default(), error: None, }, ], diff --git a/substrate/frame/revive/src/tracing.rs b/substrate/frame/revive/src/tracing.rs index 1bd4b69cd477c..a8da6a632f6bc 100644 --- a/substrate/frame/revive/src/tracing.rs +++ b/substrate/frame/revive/src/tracing.rs @@ -55,6 +55,7 @@ pub trait Tracing { _gas_before: Weight, _stack: &revm::interpreter::Stack, _memory: &revm::interpreter::SharedMemory, + _last_frame_output: &crate::ExecReturnValue, ) { } diff --git a/substrate/frame/revive/src/vm/evm.rs b/substrate/frame/revive/src/vm/evm.rs index 6f1b73e6f397d..4b46e213c4ebc 100644 --- a/substrate/frame/revive/src/vm/evm.rs +++ b/substrate/frame/revive/src/vm/evm.rs @@ -206,6 +206,7 @@ fn run_with_opcode_tracing<'a, E: Ext>( gas_before, &interpreter.stack, &interpreter.memory, + interpreter.extend.last_frame_output(), ); }); From 4df7eae04a2a46750ec8bd631021b3da53cad92d Mon Sep 17 00:00:00 2001 From: pgherveou Date: Mon, 15 Sep 2025 05:58:08 +0200 Subject: [PATCH 18/76] wip --- substrate/frame/revive/src/evm/tracing/opcode_tracing.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs b/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs index 667ad606dcd24..c677dc4b39142 100644 --- a/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs +++ b/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs @@ -82,7 +82,7 @@ impl OpcodeTracer { pending_step: None, pending_gas_before: None, // Initialize with one storage map for the root call - storages_per_call: vec![Default::default()], + storages_per_call: alloc::vec![Default::default()], } } From 049c17de3ac74755e149a838b2d872d00c5a5b89 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Mon, 15 Sep 2025 06:12:09 +0200 Subject: [PATCH 19/76] fix --- substrate/frame/revive/rpc/src/apis/debug_apis.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/substrate/frame/revive/rpc/src/apis/debug_apis.rs b/substrate/frame/revive/rpc/src/apis/debug_apis.rs index e9e1e584c7c83..5d2c61458c55a 100644 --- a/substrate/frame/revive/rpc/src/apis/debug_apis.rs +++ b/substrate/frame/revive/rpc/src/apis/debug_apis.rs @@ -113,7 +113,6 @@ impl DebugRpcServer for DebugRpcServerImpl { tracer_config: TracerConfig, ) -> RpcResult { let TracerConfig { config, timeout } = tracer_config; - dbg!(&config); with_timeout(timeout, self.client.trace_call(transaction, block, config)).await } } From 813e2f90735a3ab3f9202162ac8e9c97ee89f48f Mon Sep 17 00:00:00 2001 From: pgherveou Date: Mon, 15 Sep 2025 08:40:55 +0200 Subject: [PATCH 20/76] nit --- substrate/frame/revive/src/evm/tracing.rs | 8 -------- substrate/frame/revive/src/evm/tracing/opcode_tracing.rs | 1 - 2 files changed, 9 deletions(-) diff --git a/substrate/frame/revive/src/evm/tracing.rs b/substrate/frame/revive/src/evm/tracing.rs index 64efbc1c47d47..b80d17e72a160 100644 --- a/substrate/frame/revive/src/evm/tracing.rs +++ b/substrate/frame/revive/src/evm/tracing.rs @@ -75,14 +75,6 @@ where } } - /// Get a mutable reference to the opcode tracer if this is an opcode tracer. - pub fn as_opcode_tracer(&mut self) -> Option<&mut OpcodeTracer U256>> { - match self { - Tracer::OpcodeTracer(inner) => Some(inner), - _ => None, - } - } - /// Check if this is an opcode tracer. pub fn is_opcode_tracer(&self) -> bool { matches!(self, Tracer::OpcodeTracer(_)) diff --git a/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs b/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs index c677dc4b39142..09edfe7943c22 100644 --- a/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs +++ b/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs @@ -81,7 +81,6 @@ impl OpcodeTracer { return_value: Vec::new(), pending_step: None, pending_gas_before: None, - // Initialize with one storage map for the root call storages_per_call: alloc::vec![Default::default()], } } From ab31e6f2c79ad19fbf982eb019a184a29b47109e Mon Sep 17 00:00:00 2001 From: pgherveou Date: Mon, 15 Sep 2025 13:26:07 +0200 Subject: [PATCH 21/76] fix --- .../revive/src/evm/tracing/opcode_tracing.rs | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs b/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs index 09edfe7943c22..b0acc318392f3 100644 --- a/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs +++ b/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs @@ -52,7 +52,7 @@ pub struct OpcodeTracer { failed: bool, /// The return value of the transaction. - return_value: Vec, + return_value: Bytes, /// Pending step that's waiting for gas cost to be recorded. pending_step: Option>, @@ -78,7 +78,7 @@ impl OpcodeTracer { step_count: 0, total_gas_used: Gas::default(), failed: false, - return_value: Vec::new(), + return_value: Bytes::default(), pending_step: None, pending_gas_before: None, storages_per_call: alloc::vec![Default::default()], @@ -86,14 +86,9 @@ impl OpcodeTracer { } /// Collect the traces and return them. - pub fn collect_trace(&mut self) -> OpcodeTrace - where - Gas: Copy, - { - let struct_logs = core::mem::take(&mut self.steps); - let return_value = crate::evm::Bytes(self.return_value.clone()); - - OpcodeTrace { gas: self.total_gas_used, failed: self.failed, return_value, struct_logs } + pub fn collect_trace(self) -> OpcodeTrace { + let Self { steps: struct_logs, return_value, total_gas_used: gas, failed, .. } = self; + OpcodeTrace { gas, failed, return_value, struct_logs } } /// Record an error in the current step. @@ -105,7 +100,7 @@ impl OpcodeTracer { /// Record return data. pub fn record_return_data(&mut self, data: &[u8]) { - self.return_value = data.to_vec(); + self.return_value = Bytes(data.to_vec()); } /// Mark the transaction as failed. From b557153785c14dba18ee8555c0b3c2d35484002b Mon Sep 17 00:00:00 2001 From: pgherveou Date: Tue, 16 Sep 2025 20:08:45 +0200 Subject: [PATCH 22/76] wip --- substrate/frame/revive/src/vm/evm.rs | 41 ++- .../frame/revive/src/vm/evm/interpreter.rs | 40 +++ substrate/frame/revive/src/vm/evm/memory.rs | 161 +++++++++++ substrate/frame/revive/src/vm/evm/stack.rs | 256 ++++++++++++++++++ 4 files changed, 496 insertions(+), 2 deletions(-) create mode 100644 substrate/frame/revive/src/vm/evm/interpreter.rs create mode 100644 substrate/frame/revive/src/vm/evm/memory.rs create mode 100644 substrate/frame/revive/src/vm/evm/stack.rs diff --git a/substrate/frame/revive/src/vm/evm.rs b/substrate/frame/revive/src/vm/evm.rs index 797b3d6b57657..0d742a3319fe0 100644 --- a/substrate/frame/revive/src/vm/evm.rs +++ b/substrate/frame/revive/src/vm/evm.rs @@ -47,6 +47,10 @@ pub mod instructions; #[cfg(not(feature = "runtime-benchmarks"))] mod instructions; +mod interpreter; +mod memory; +mod stack; + /// Hard-coded value returned by the EVM `DIFFICULTY` opcode. /// /// After Ethereum's Merge (Sept 2022), the `DIFFICULTY` opcode was redefined to return @@ -157,9 +161,9 @@ fn run<'a, E: Ext>( let host = &mut DummyHost {}; loop { #[cfg(not(feature = "std"))] - let action = interpreter.run_plain(table, host); - #[cfg(feature = "std")] let action = run_plain(interpreter, table, host); + #[cfg(feature = "std")] + let action = run_plain_with_tracing(interpreter, table, host); match action { InterpreterAction::Return(result) => { log::trace!(target: LOG_TARGET, "Evm return {:?}", result); @@ -252,6 +256,39 @@ fn run_plain( interpreter: &mut Interpreter, instruction_table: &revm::interpreter::InstructionTable, host: &mut DummyHost, +) -> InterpreterAction { + use crate::{alloc::string::ToString, format}; + use revm::{ + bytecode::OpCode, + interpreter::{ + instruction_context::InstructionContext, + interpreter_types::{Jumps, LoopControl, MemoryTr, StackTr}, + }, + }; + while interpreter.bytecode.is_not_end() { + // Get current opcode. + let opcode = interpreter.bytecode.opcode(); + + // SAFETY: In analysis we are doing padding of bytecode so that we are sure that last + // byte instruction is STOP so we are safe to just increment program_counter bcs on last + // instruction it will do noop and just stop execution of this contract + interpreter.bytecode.relative_jump(1); + let context = InstructionContext { interpreter, host }; + // Execute instruction. + instruction_table[opcode as usize](context); + } + interpreter.bytecode.revert_to_previous_pointer(); + + interpreter.take_next_action() +} + +/// Re-implementation of REVM run_plain function to add trace logging to our EVM interpreter loop. +/// NB: copied directly from revm tag v82 +#[cfg(feature = "std")] +fn run_plain_with_tracing( + interpreter: &mut Interpreter, + instruction_table: &revm::interpreter::InstructionTable, + host: &mut DummyHost, ) -> InterpreterAction { use crate::{alloc::string::ToString, format}; use revm::{ diff --git a/substrate/frame/revive/src/vm/evm/interpreter.rs b/substrate/frame/revive/src/vm/evm/interpreter.rs new file mode 100644 index 0000000000000..ddadebafdfd45 --- /dev/null +++ b/substrate/frame/revive/src/vm/evm/interpreter.rs @@ -0,0 +1,40 @@ +//! Custom EVM interpreter implementation using sp_core types + +use crate::vm::{ + evm::{memory::Memory, stack::Stack}, + Ext, +}; +use alloc::vec::Vec; +use core::marker::PhantomData; +use revm::interpreter::interpreter::ExtBytecode; + +/// EVM interpreter state using sp_core types +#[derive(Debug)] +pub struct Interpreter<'a, E: Ext> { + /// The bytecode being executed + pub bytecode: ExtBytecode, + /// The execution stack + pub stack: Stack, + /// Return data from the last call + pub return_data: Vec, + /// EVM memory + pub memory: Memory, + /// Input data for the current call + pub input: Vec, + /// Phantom data for the Ext type + pub _phantom: PhantomData<&'a E>, +} + +impl<'a, E: Ext> Interpreter<'a, E> { + /// Create a new interpreter instance + pub fn new(bytecode: ExtBytecode, input: Vec) -> Self { + Self { + bytecode, + stack: Stack::new(), + return_data: Vec::new(), + memory: Memory::new(), + input, + _phantom: Default::default(), + } + } +} diff --git a/substrate/frame/revive/src/vm/evm/memory.rs b/substrate/frame/revive/src/vm/evm/memory.rs new file mode 100644 index 0000000000000..f77fa746962e7 --- /dev/null +++ b/substrate/frame/revive/src/vm/evm/memory.rs @@ -0,0 +1,161 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Custom EVM memory implementation using standard Vec + +use alloc::vec::Vec; +use core::ops::Range; + +/// EVM memory implementation using standard Vec and sp_core::U256 +#[derive(Debug, Clone)] +pub struct Memory(Vec); + +impl Memory { + /// Create a new empty memory + pub fn new() -> Self { + Self(Vec::new()) + } + + /// Get a slice of memory for the given range + pub fn slice(&self, range: Range) -> &[u8] { + let end = core::cmp::min(range.end, self.0.len()); + let start = core::cmp::min(range.start, end); + &self.0[start..end] + } + + /// Get the current memory size in bytes + pub fn size(&self) -> usize { + self.0.len() + } + + /// Resize memory to at least the given size + pub fn resize(&mut self, new_size: usize) -> bool { + if new_size > self.0.len() { + self.0.resize(new_size, 0); + } + true + } + + /// Set memory at the given offset with the provided data + pub fn set(&mut self, offset: usize, data: &[u8]) { + let end = offset.saturating_add(data.len()); + if end > self.0.len() { + self.0.resize(end, 0); + } + self.0[offset..end].copy_from_slice(data); + } + + /// Set data in memory from another memory's global slice + pub fn set_data(&mut self, offset: usize, data_offset: usize, len: usize, data: &[u8]) { + if len > 0 && data_offset < data.len() { + let copy_len = core::cmp::min(len, data.len() - data_offset); + let source = &data[data_offset..data_offset + copy_len]; + self.set(offset, source); + } + } + + /// Copy data within memory from src to dst + pub fn copy(&mut self, dst: usize, src: usize, len: usize) { + if len == 0 { + return; + } + + let max_offset = core::cmp::max(dst.saturating_add(len), src.saturating_add(len)); + if max_offset > self.0.len() { + self.0.resize(max_offset, 0); + } + + // Handle overlapping memory regions correctly + if dst != src { + self.0.copy_within(src..src + len, dst); + } + } +} + +impl Default for Memory { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_memory_resize() { + let mut memory = Memory::new(); + assert_eq!(memory.size(), 0); + + memory.resize(100); + assert_eq!(memory.size(), 100); + + // Resizing to smaller size should not shrink + memory.resize(50); + assert_eq!(memory.size(), 100); + } + + #[test] + fn test_set_get() { + let mut memory = Memory::new(); + + let data = b"Hello, World!"; + memory.set(10, data); + + assert_eq!(memory.slice(10..10 + data.len()), data); + assert_eq!(memory.size(), 10 + data.len()); + } + + #[test] + fn test_memory_copy() { + let mut memory = Memory::new(); + + // Set some initial data + memory.set(0, b"Hello"); + memory.set(10, b"World"); + + // Copy "Hello" to position 20 + memory.copy(20, 0, 5); + + assert_eq!(memory.slice(20..25), b"Hello"); + assert_eq!(memory.slice(0..5), b"Hello"); // Original should still be there + } + + #[test] + fn test_overlapping_copy() { + let mut memory = Memory::new(); + + memory.set(0, b"HelloWorld"); + + // Overlapping copy - move "World" to overlap with "Hello" + memory.copy(2, 5, 5); + + assert_eq!(memory.slice(0..10), b"HeWorldrld"); + } + + #[test] + fn test_set_data() { + let mut memory = Memory::new(); + let source_data = b"Hello World"; + + memory.set_data(5, 0, 5, source_data); + assert_eq!(memory.slice(5..10), b"Hello"); + + memory.set_data(15, 6, 5, source_data); + assert_eq!(memory.slice(15..20), b"World"); + } +} diff --git a/substrate/frame/revive/src/vm/evm/stack.rs b/substrate/frame/revive/src/vm/evm/stack.rs new file mode 100644 index 0000000000000..ef75d10d68db1 --- /dev/null +++ b/substrate/frame/revive/src/vm/evm/stack.rs @@ -0,0 +1,256 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Custom EVM stack implementation using sp_core::U256 + +use alloc::vec::Vec; +use sp_core::U256; + +/// EVM stack implementation using sp_core types +#[derive(Debug, Clone)] +pub struct Stack(Vec); + +impl Stack { + /// Create a new empty stack + pub fn new() -> Self { + Self(Vec::with_capacity(32)) + } + + /// Push a value onto the stack + /// Returns true if successful, false if stack would overflow + pub fn push(&mut self, value: U256) -> bool { + if self.0.len() >= 1024 { + false + } else { + self.0.push(value); + true + } + } + + /// Pop a value from the stack + /// Returns Some(value) if successful, None if stack is empty + pub fn pop(&mut self) -> Option { + self.0.pop() + } + + /// Get a reference to the top stack item without removing it + pub fn top(&self) -> Option<&U256> { + self.0.last() + } + + /// Get the current stack size + pub fn len(&self) -> usize { + self.0.len() + } + + /// Check if stack is empty + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + /// Pop multiple values from the stack + /// Returns Some(array) if successful, None if not enough values on stack + pub fn popn(&mut self) -> Option<[U256; N]> { + if self.0.len() < N { + return None; + } + + let mut result: [U256; N] = [U256::zero(); N]; + for i in 0..N { + result[i] = self.0.pop()?; + } + Some(result) + } + + /// Pop multiple values and return them along with a mutable reference to the new top + /// This is used for operations that pop some values and modify the top of the stack + pub fn popn_top(&mut self) -> Option<([U256; N], &mut U256)> { + if self.0.len() < N + 1 { + return None; + } + + let mut popped: [U256; N] = [U256::zero(); N]; + for i in 0..N { + popped[i] = self.0.pop()?; + } + + // Get mutable reference to the new top + let top = self.0.last_mut()?; + Some((popped, top)) + } + + /// Duplicate the Nth item from the top and push it onto the stack + /// Returns true if successful, false if stack would overflow or index is invalid + pub fn dup(&mut self, n: usize) -> bool { + if n == 0 || n > self.0.len() || self.0.len() >= 1024 { + return false; + } + + let idx = self.0.len() - n; + let value = self.0[idx]; + self.0.push(value); + true + } + + /// Swap the top stack item with the Nth item from the top + /// Returns true if successful, false if indices are invalid + pub fn exchange(&mut self, i: usize, j: usize) -> bool { + let len = self.0.len(); + if i >= len || j >= len { + return false; + } + + let i_idx = len - 1 - i; + let j_idx = len - 1 - j; + self.0.swap(i_idx, j_idx); + true + } +} + +impl Default for Stack { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_push_pop() { + let mut stack = Stack::new(); + + // Test push + assert!(stack.push(U256::from(42))); + assert_eq!(stack.len(), 1); + + // Test pop + assert_eq!(stack.pop(), Some(U256::from(42))); + assert_eq!(stack.len(), 0); + assert_eq!(stack.pop(), None); + } + + #[test] + fn test_popn() { + let mut stack = Stack::new(); + + // Push some values + stack.push(U256::from(1)); + stack.push(U256::from(2)); + stack.push(U256::from(3)); + + // Pop multiple values + let result: Option<[U256; 2]> = stack.popn(); + assert_eq!(result, Some([U256::from(3), U256::from(2)])); + assert_eq!(stack.len(), 1); + + // Try to pop more than available + let result: Option<[U256; 2]> = stack.popn(); + assert_eq!(result, None); + } + + #[test] + fn test_popn_top() { + let mut stack = Stack::new(); + + // Push some values + stack.push(U256::from(1)); + stack.push(U256::from(2)); + stack.push(U256::from(3)); + stack.push(U256::from(4)); + + // Pop 2 values and get mutable reference to new top + let result = stack.popn_top::<2>(); + assert!(result.is_some()); + let (popped, top_ref) = result.unwrap(); + assert_eq!(popped, [U256::from(4), U256::from(3)]); + assert_eq!(*top_ref, U256::from(2)); + + // Modify the top + *top_ref = U256::from(99); + assert_eq!(stack.top(), Some(&U256::from(99))); + } + + #[test] + fn test_dup() { + let mut stack = Stack::new(); + + stack.push(U256::from(1)); + stack.push(U256::from(2)); + + // Duplicate the top item (index 1) + assert!(stack.dup(1)); + assert_eq!(stack.0, vec![U256::from(1), U256::from(2), U256::from(2)]); + + // Duplicate the second item (index 2) + assert!(stack.dup(2)); + assert_eq!(stack.0, vec![U256::from(1), U256::from(2), U256::from(2), U256::from(2)]); + } + + #[test] + fn test_exchange() { + let mut stack = Stack::new(); + + stack.push(U256::from(1)); + stack.push(U256::from(2)); + stack.push(U256::from(3)); + + // Swap top (index 0) with second (index 1) + assert!(stack.exchange(0, 1)); + assert_eq!(stack.0, vec![U256::from(1), U256::from(3), U256::from(2)]); + } + + #[test] + fn test_stack_limit() { + let mut stack = Stack::new(); + + // Fill stack to limit + for i in 0..1024 { + assert!(stack.push(U256::from(i))); + } + + // Should fail to push one more + assert!(!stack.push(U256::from(9999))); + assert_eq!(stack.len(), 1024); + } + + #[test] + fn test_top() { + let mut stack = Stack::new(); + assert_eq!(stack.top(), None); + + stack.push(U256::from(42)); + assert_eq!(stack.top(), Some(&U256::from(42))); + + stack.push(U256::from(100)); + assert_eq!(stack.top(), Some(&U256::from(100))); + } + + #[test] + fn test_is_empty() { + let mut stack = Stack::new(); + assert!(stack.is_empty()); + + stack.push(U256::from(1)); + assert!(!stack.is_empty()); + + stack.pop(); + assert!(stack.is_empty()); + } +} + From 1a34ea6936b4e57cf723196055ca07cc405641cb Mon Sep 17 00:00:00 2001 From: pgherveou Date: Wed, 17 Sep 2025 14:35:32 +0200 Subject: [PATCH 23/76] wip --- substrate/frame/revive/src/vm/evm.rs | 705 ++++++++---------- .../src/vm/evm/instructions/arithmetic.rs | 211 +++--- .../revive/src/vm/evm/instructions/i256.rs | 160 ++-- .../revive/src/vm/evm/instructions/mod.rs | 442 ++++++----- .../frame/revive/src/vm/evm/interpreter.rs | 178 ++++- substrate/frame/revive/src/vm/evm/stack.rs | 446 +++++------ substrate/frame/revive/src/vm/mod.rs | 11 +- 7 files changed, 1119 insertions(+), 1034 deletions(-) diff --git a/substrate/frame/revive/src/vm/evm.rs b/substrate/frame/revive/src/vm/evm.rs index 0d742a3319fe0..42290ea497bde 100644 --- a/substrate/frame/revive/src/vm/evm.rs +++ b/substrate/frame/revive/src/vm/evm.rs @@ -14,7 +14,7 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. - +#![allow(unused)] // TODO remove use crate::{ exec::ExecError, gas, vec, @@ -24,21 +24,8 @@ use crate::{ }; use alloc::{boxed::Box, vec::Vec}; use core::cmp::min; -use instructions::instruction_table; use pallet_revive_uapi::ReturnFlags; -use revm::{ - bytecode::Bytecode, - context::CreateScheme, - interpreter::{ - host::DummyHost, - interpreter::{ExtBytecode, ReturnDataImpl, RuntimeFlags}, - interpreter_action::InterpreterAction, - interpreter_types::{InputsTr, MemoryTr, ReturnData}, - CallInput, CallInputs, CallScheme, CreateInputs, FrameInput, Gas, InstructionResult, - Interpreter, InterpreterResult, InterpreterTypes, SharedMemory, Stack, - }, - primitives::{self, hardfork::SpecId, Address, Bytes}, -}; +use revm::{bytecode::Bytecode, interpreter::interpreter::ExtBytecode, primitives::Bytes}; use sp_core::H160; use sp_runtime::Weight; @@ -48,8 +35,13 @@ pub mod instructions; mod instructions; mod interpreter; +use interpreter::{InstructionTable, Interpreter}; + mod memory; +use memory::Memory; + mod stack; +use stack::Stack; /// Hard-coded value returned by the EVM `DIFFICULTY` opcode. /// @@ -128,413 +120,324 @@ impl ContractBlob { } /// Calls the EVM interpreter with the provided bytecode and inputs. -pub fn call<'a, E: Ext>(bytecode: Bytecode, ext: &'a mut E, inputs: EVMInputs) -> ExecResult { - let mut interpreter: Interpreter> = Interpreter { - gas: Gas::default(), - bytecode: ExtBytecode::new(bytecode), - stack: Stack::new(), - return_data: Default::default(), - memory: SharedMemory::new(), - input: inputs, - runtime_flag: RuntimeFlags { is_static: ext.is_read_only(), spec_id: SpecId::default() }, - extend: ext, - }; - - let table = instruction_table::<'a, E>(); - let result = run(&mut interpreter, &table); - - instruction_result_into_exec_error::(result.result) - .map(Err) - .unwrap_or_else(|| { - Ok(ExecReturnValue { - flags: if result.is_revert() { ReturnFlags::REVERT } else { ReturnFlags::empty() }, - data: result.output.to_vec(), - }) - }) +pub fn call<'a, E: Ext>(bytecode: Bytecode, ext: &'a mut E, inputs: &'a [u8]) -> ExecResult { + todo!() + // let mut interpreter: Interpreter> = Interpreter { + // gas: Gas::default(), + // bytecode: ExtBytecode::new(bytecode), + // stack: Stack::new(), + // return_data: Default::default(), + // memory: SharedMemory::new(), + // input: inputs, + // runtime_flag: RuntimeFlags { is_static: ext.is_read_only(), spec_id: SpecId::default() }, + // extend: ext, + // }; + // + // let table = instruction_table::<'a, E>(); + // let result = run(&mut interpreter, &table); + // + // instruction_result_into_exec_error::(result.result) + // .map(Err) + // .unwrap_or_else(|| { + // Ok(ExecReturnValue { + // flags: if result.is_revert() { ReturnFlags::REVERT } else { ReturnFlags::empty() }, + // data: result.output.to_vec(), + // }) + // }) } /// Runs the EVM interpreter fn run<'a, E: Ext>( - interpreter: &mut Interpreter>, - table: &revm::interpreter::InstructionTable, DummyHost>, -) -> InterpreterResult { - let host = &mut DummyHost {}; + interpreter: &mut Interpreter<'a, E>, + table: &InstructionTable, +) -> ExecResult { loop { #[cfg(not(feature = "std"))] - let action = run_plain(interpreter, table, host); + let action = run_plain(interpreter, table); #[cfg(feature = "std")] - let action = run_plain_with_tracing(interpreter, table, host); - match action { - InterpreterAction::Return(result) => { - log::trace!(target: LOG_TARGET, "Evm return {:?}", result); - debug_assert!( - result.gas == Default::default(), - "Interpreter gas state is unused; found: {:?}", - result.gas, - ); - return result; - }, - InterpreterAction::NewFrame(frame_input) => match frame_input { - FrameInput::Call(call_input) => run_call(interpreter, call_input), - FrameInput::Create(create_input) => run_create(interpreter, create_input), - FrameInput::Empty => unreachable!(), - }, - } + let action = run_plain_with_tracing(interpreter, table); + todo!() + // match action { + // InterpreterAction::Return(result) => { + // log::trace!(target: LOG_TARGET, "Evm return {:?}", result); + // return result; + // }, + // InterpreterAction::NewFrame(frame_input) => match frame_input { + // FrameInput::Call(call_input) => run_call(interpreter, call_input), + // FrameInput::Create(create_input) => run_create(interpreter, create_input), + // FrameInput::Empty => unreachable!(), + // }, + // } } } -fn run_call<'a, E: Ext>( - interpreter: &mut Interpreter>, - call_input: Box, -) { - let callee: H160 = if call_input.scheme.is_delegate_call() { - call_input.bytecode_address.0 .0.into() - } else { - call_input.target_address.0 .0.into() - }; - - let input = match &call_input.input { - CallInput::Bytes(bytes) => bytes.to_vec(), - CallInput::SharedBuffer(range) => interpreter.memory.global_slice(range.clone()).to_vec(), - }; - let call_result = match call_input.scheme { - CallScheme::Call | CallScheme::StaticCall => interpreter.extend.call( - Weight::from_parts(call_input.gas_limit, u64::MAX), - U256::MAX, - &callee, - U256::from_revm_u256(&call_input.call_value()), - input, - true, - call_input.is_static, - ), - CallScheme::CallCode => { - unreachable!() - }, - CallScheme::DelegateCall => interpreter.extend.delegate_call( - Weight::from_parts(call_input.gas_limit, u64::MAX), - U256::MAX, - callee, - input, - ), - }; - - let (return_data, did_revert) = { - let return_value = interpreter.extend.last_frame_output(); - let return_data: Bytes = return_value.data.clone().into(); - (return_data, return_value.did_revert()) - }; - - let mem_length = call_input.return_memory_offset.len(); - let mem_start = call_input.return_memory_offset.start; - let returned_len = return_data.len(); - let target_len = min(mem_length, returned_len); - - interpreter.return_data.set_buffer(return_data); - - match call_result { - Ok(()) => { - // success or revert - gas!(interpreter, RuntimeCosts::CopyToContract(target_len as u32)); - interpreter - .memory - .set(mem_start, &interpreter.return_data.buffer()[..target_len]); - let _ = interpreter.stack.push(primitives::U256::from(!did_revert as u8)); - }, - Err(err) => { - let _ = interpreter.stack.push(primitives::U256::ZERO); - if let Some(reason) = exec_error_into_halt_reason::(err) { - interpreter.halt(reason); - } - }, - } +fn run_call<'a, E: Ext>(interpreter: &mut Interpreter<'a, E>, call_input: &'a [u8]) { + todo!() + // let callee: H160 = if call_input.scheme.is_delegate_call() { + // call_input.bytecode_address.0 .0.into() + // } else { + // call_input.target_address.0 .0.into() + // }; + // + // let input = match &call_input.input { + // CallInput::Bytes(bytes) => bytes.to_vec(), + // CallInput::SharedBuffer(range) => interpreter.memory.global_slice(range.clone()).to_vec(), + // }; + // let call_result = match call_input.scheme { + // CallScheme::Call | CallScheme::StaticCall => interpreter.extend.call( + // Weight::from_parts(call_input.gas_limit, u64::MAX), + // U256::MAX, + // &callee, + // U256::from_revm_u256(&call_input.call_value()), + // input, + // true, + // call_input.is_static, + // ), + // CallScheme::CallCode => { + // unreachable!() + // }, + // CallScheme::DelegateCall => interpreter.extend.delegate_call( + // Weight::from_parts(call_input.gas_limit, u64::MAX), + // U256::MAX, + // callee, + // input, + // ), + // }; + // + // let (return_data, did_revert) = { + // let return_value = interpreter.extend.last_frame_output(); + // let return_data: Bytes = return_value.data.clone().into(); + // (return_data, return_value.did_revert()) + // }; + // + // let mem_length = call_input.return_memory_offset.len(); + // let mem_start = call_input.return_memory_offset.start; + // let returned_len = return_data.len(); + // let target_len = min(mem_length, returned_len); + // + // interpreter.return_data.set_buffer(return_data); + // + // match call_result { + // Ok(()) => { + // // success or revert + // gas!(interpreter, RuntimeCosts::CopyToContract(target_len as u32)); + // interpreter + // .memory + // .set(mem_start, &interpreter.return_data.buffer()[..target_len]); + // let _ = interpreter.stack.push(primitives::U256::from(!did_revert as u8)); + // }, + // Err(err) => { + // let _ = interpreter.stack.push(primitives::U256::ZERO); + // if let Some(reason) = exec_error_into_halt_reason::(err) { + // interpreter.halt(reason); + // } + // }, + // } } /// Re-implementation of REVM run_plain function to add trace logging to our EVM interpreter loop. /// NB: copied directly from revm tag v82 #[cfg(feature = "std")] -fn run_plain( - interpreter: &mut Interpreter, - instruction_table: &revm::interpreter::InstructionTable, - host: &mut DummyHost, -) -> InterpreterAction { - use crate::{alloc::string::ToString, format}; - use revm::{ - bytecode::OpCode, - interpreter::{ - instruction_context::InstructionContext, - interpreter_types::{Jumps, LoopControl, MemoryTr, StackTr}, - }, - }; - while interpreter.bytecode.is_not_end() { - // Get current opcode. - let opcode = interpreter.bytecode.opcode(); - - // SAFETY: In analysis we are doing padding of bytecode so that we are sure that last - // byte instruction is STOP so we are safe to just increment program_counter bcs on last - // instruction it will do noop and just stop execution of this contract - interpreter.bytecode.relative_jump(1); - let context = InstructionContext { interpreter, host }; - // Execute instruction. - instruction_table[opcode as usize](context); - } - interpreter.bytecode.revert_to_previous_pointer(); - - interpreter.take_next_action() +fn run_plain<'a, E: Ext>( + interpreter: &mut Interpreter, + table: &InstructionTable, +) -> ExecResult { + todo!() + // use crate::{alloc::string::ToString, format}; + // use revm::{ + // bytecode::OpCode, + // interpreter::{ + // instruction_context::InstructionContext, + // interpreter_types::{Jumps, LoopControl, MemoryTr, StackTr}, + // }, + // }; + // while interpreter.bytecode.is_not_end() { + // // Get current opcode. + // let opcode = interpreter.bytecode.opcode(); + // + // // SAFETY: In analysis we are doing padding of bytecode so that we are sure that last + // // byte instruction is STOP so we are safe to just increment program_counter bcs on last + // // instruction it will do noop and just stop execution of this contract + // interpreter.bytecode.relative_jump(1); + // let context = InstructionContext { interpreter, host }; + // // Execute instruction. + // instruction_table[opcode as usize](context); + // } + // interpreter.bytecode.revert_to_previous_pointer(); + // + // interpreter.take_next_action() } /// Re-implementation of REVM run_plain function to add trace logging to our EVM interpreter loop. /// NB: copied directly from revm tag v82 #[cfg(feature = "std")] -fn run_plain_with_tracing( - interpreter: &mut Interpreter, - instruction_table: &revm::interpreter::InstructionTable, - host: &mut DummyHost, -) -> InterpreterAction { - use crate::{alloc::string::ToString, format}; - use revm::{ - bytecode::OpCode, - interpreter::{ - instruction_context::InstructionContext, - interpreter_types::{Jumps, LoopControl, MemoryTr, StackTr}, - }, - }; - while interpreter.bytecode.is_not_end() { - log::trace!(target: LOG_TARGET, - "[{pc}]: {opcode}, stacktop: {stacktop}, memory size: {memsize} {memory:?}", - pc = interpreter.bytecode.pc(), - opcode = OpCode::new(interpreter.bytecode.opcode()) - .map_or("INVALID".to_string(), |x| format!("{:?}", x.info())), - stacktop = interpreter.stack.top().map_or("None".to_string(), |x| format!("{:#x}", x)), - memsize = interpreter.memory.size(), - // printing at most the first 32 bytes of memory - memory = interpreter - .memory - .slice_len(0, core::cmp::min(32, interpreter.memory.size())) - .to_vec(), - ); - // Get current opcode. - let opcode = interpreter.bytecode.opcode(); - - // SAFETY: In analysis we are doing padding of bytecode so that we are sure that last - // byte instruction is STOP so we are safe to just increment program_counter bcs on last - // instruction it will do noop and just stop execution of this contract - interpreter.bytecode.relative_jump(1); - let context = InstructionContext { interpreter, host }; - // Execute instruction. - instruction_table[opcode as usize](context); - } - interpreter.bytecode.revert_to_previous_pointer(); - - interpreter.take_next_action() -} - -fn run_create<'a, E: Ext>( - interpreter: &mut Interpreter>, - create_input: Box, -) { - let value = U256::from_revm_u256(&create_input.value); - - let salt = match create_input.scheme { - CreateScheme::Create => None, - CreateScheme::Create2 { salt } => Some(salt.to_le_bytes()), - CreateScheme::Custom { .. } => unreachable!("custom create schemes are not supported"), - }; - - let call_result = interpreter.extend.instantiate( - Weight::from_parts(create_input.gas_limit, u64::MAX), - U256::MAX, - Code::Upload(create_input.init_code.to_vec()), - value, - vec![], - salt.as_ref(), - ); - - let return_value = interpreter.extend.last_frame_output(); - let return_data: Bytes = return_value.data.clone().into(); - - match call_result { - Ok(address) => { - if return_value.did_revert() { - // Contract creation reverted — return data must be propagated - gas!(interpreter, RuntimeCosts::CopyToContract(return_data.len() as u32)); - interpreter.return_data.set_buffer(return_data); - let _ = interpreter.stack.push(primitives::U256::ZERO); - } else { - // Otherwise clear it. Note that RETURN opcode should abort. - interpreter.return_data.clear(); - let stack_item: Address = address.0.into(); - let _ = interpreter.stack.push(stack_item.into_word().into()); - } - }, - Err(err) => { - let _ = interpreter.stack.push(primitives::U256::ZERO); - if let Some(reason) = exec_error_into_halt_reason::(err) { - interpreter.halt(reason); - } - }, - } -} - -/// EVMInterpreter implements the `InterpreterTypes`. -/// -/// Note: -/// -/// Our implementation set the `InterpreterTypes::Extend` associated type, to the `Ext` trait, to -/// reuse all the host functions that are defined by this trait -pub struct EVMInterpreter<'a, E: Ext> { - _phantom: core::marker::PhantomData<&'a E>, -} - -impl<'a, E: Ext> InterpreterTypes for EVMInterpreter<'a, E> { - type Stack = Stack; - type Memory = SharedMemory; - type Bytecode = ExtBytecode; - type ReturnData = ReturnDataImpl; - type Input = EVMInputs; - type RuntimeFlag = RuntimeFlags; - type Extend = &'a mut E; - type Output = InterpreterAction; +fn run_plain_with_tracing<'a, E: Ext>( + interpreter: &mut Interpreter<'a, E>, + table: &InstructionTable, +) -> ExecResult { + todo!() + // use crate::{alloc::string::ToString, format}; + // use revm::{ + // bytecode::OpCode, + // interpreter::{ + // instruction_context::InstructionContext, + // interpreter_types::{Jumps, LoopControl, MemoryTr, StackTr}, + // }, + // }; + // while interpreter.bytecode.is_not_end() { + // log::trace!(target: LOG_TARGET, + // "[{pc}]: {opcode}, stacktop: {stacktop}, memory size: {memsize} {memory:?}", + // pc = interpreter.bytecode.pc(), + // opcode = OpCode::new(interpreter.bytecode.opcode()) + // .map_or("INVALID".to_string(), |x| format!("{:?}", x.info())), + // stacktop = interpreter.stack.top().map_or("None".to_string(), |x| format!("{:#x}", x)), + // memsize = interpreter.memory.size(), + // // printing at most the first 32 bytes of memory + // memory = interpreter + // .memory + // .slice_len(0, core::cmp::min(32, interpreter.memory.size())) + // .to_vec(), + // ); + // // Get current opcode. + // let opcode = interpreter.bytecode.opcode(); + // + // // SAFETY: In analysis we are doing padding of bytecode so that we are sure that last + // // byte instruction is STOP so we are safe to just increment program_counter bcs on last + // // instruction it will do noop and just stop execution of this contract + // interpreter.bytecode.relative_jump(1); + // let context = InstructionContext { interpreter, host }; + // // Execute instruction. + // instruction_table[opcode as usize](context); + // } + // interpreter.bytecode.revert_to_previous_pointer(); + // + // interpreter.take_next_action() } -/// EVMInputs implements the `InputsTr` trait for EVM inputs, allowing the EVM interpreter to access -/// the call input data. -/// -/// Note: -/// -/// In our implementation of the instruction table, Everything except the call input data will be -/// accessed through the `InterpreterTypes::Extend` associated type, our implementation will panic -/// if any of those methods are called. -#[derive(Debug, Clone, Default)] -pub struct EVMInputs(CallInput); - -impl EVMInputs { - pub fn new(input: Vec) -> Self { - Self(CallInput::Bytes(input.into())) - } +fn run_create<'a, E: Ext>(interpreter: &mut Interpreter<'a, E>, create_input: &[u8]) { + todo!() + // let value = U256::from_revm_u256(&create_input.value); + // + // let salt = match create_input.scheme { + // CreateScheme::Create => None, + // CreateScheme::Create2 { salt } => Some(salt.to_le_bytes()), + // CreateScheme::Custom { .. } => unreachable!("custom create schemes are not supported"), + // }; + // + // let call_result = interpreter.extend.instantiate( + // Weight::from_parts(create_input.gas_limit, u64::MAX), + // U256::MAX, + // Code::Upload(create_input.init_code.to_vec()), + // value, + // vec![], + // salt.as_ref(), + // ); + // + // let return_value = interpreter.extend.last_frame_output(); + // let return_data: Bytes = return_value.data.clone().into(); + // + // match call_result { + // Ok(address) => { + // if return_value.did_revert() { + // // Contract creation reverted — return data must be propagated + // gas!(interpreter, RuntimeCosts::CopyToContract(return_data.len() as u32)); + // interpreter.return_data.set_buffer(return_data); + // let _ = interpreter.stack.push(primitives::U256::ZERO); + // } else { + // // Otherwise clear it. Note that RETURN opcode should abort. + // interpreter.return_data.clear(); + // let stack_item: Address = address.0.into(); + // let _ = interpreter.stack.push(stack_item.into_word().into()); + // } + // }, + // Err(err) => { + // let _ = interpreter.stack.push(primitives::U256::ZERO); + // if let Some(reason) = exec_error_into_halt_reason::(err) { + // interpreter.halt(reason); + // } + // }, + // } } -impl InputsTr for EVMInputs { - fn target_address(&self) -> Address { - panic!() - } - - fn caller_address(&self) -> Address { - panic!() - } - - fn bytecode_address(&self) -> Option<&Address> { - panic!() - } - - fn input(&self) -> &CallInput { - &self.0 - } - - fn call_value(&self) -> primitives::U256 { - panic!() - } -} - -/// Conversion of a `ExecError` to `ReturnErrorCode`. -/// -/// Used when converting the error returned from a subcall in order to map it to the -/// equivalent EVM interpreter [InstructionResult]. -/// -/// - Returns `None` when the caller can recover the error. -/// - Otherwise, some [InstructionResult] error code (the halt reason) is returned. Most [ExecError] -/// variants don't map to a [InstructionResult]. The conversion is lossy and defaults to -/// [InstructionResult::Revert] for most cases. -/// -/// Uses the overarching [super::exec_error_into_return_code] method to determine if -/// the error is recoverable or not. This guarantees consistent behavior accross both -/// VM backends. -fn exec_error_into_halt_reason(from: ExecError) -> Option { - log::trace!("call frame execution error in EVM caller: {:?}", &from); - - if super::exec_error_into_return_code::(from).is_ok() { - return None; - } - - let static_memory_too_large = Error::::StaticMemoryTooLarge.into(); - let code_rejected = Error::::CodeRejected.into(); - let transfer_failed = Error::::TransferFailed.into(); - let duplicate_contract = Error::::DuplicateContract.into(); - let balance_conversion_failed = Error::::BalanceConversionFailed.into(); - let value_too_large = Error::::ValueTooLarge.into(); - let out_of_gas = Error::::OutOfGas.into(); - let out_of_deposit = Error::::StorageDepositLimitExhausted.into(); - - Some(match from.error { - err if err == static_memory_too_large => InstructionResult::MemoryLimitOOG, - err if err == code_rejected => InstructionResult::OpcodeNotFound, - err if err == transfer_failed => InstructionResult::OutOfFunds, - err if err == duplicate_contract => InstructionResult::CreateCollision, - err if err == balance_conversion_failed => InstructionResult::OverflowPayment, - err if err == value_too_large => InstructionResult::OverflowPayment, - err if err == out_of_deposit => InstructionResult::OutOfFunds, - err if err == out_of_gas => InstructionResult::OutOfGas, - _ => InstructionResult::Revert, - }) -} - -/// Map [InstructionResult] into an [ExecError] for passing it up the stack. -/// -/// Returns `None` if the instruction result is not an error case. -fn instruction_result_into_exec_error(from: InstructionResult) -> Option { - match from { - InstructionResult::OutOfGas | - InstructionResult::InvalidOperandOOG | - InstructionResult::ReentrancySentryOOG | - InstructionResult::PrecompileOOG | - InstructionResult::MemoryOOG => Some(Error::::OutOfGas), - InstructionResult::MemoryLimitOOG => Some(Error::::StaticMemoryTooLarge), - InstructionResult::OpcodeNotFound | - InstructionResult::InvalidJump | - InstructionResult::NotActivated | - InstructionResult::InvalidFEOpcode | - InstructionResult::CreateContractStartingWithEF => Some(Error::::InvalidInstruction), - InstructionResult::CallNotAllowedInsideStatic | - InstructionResult::StateChangeDuringStaticCall => Some(Error::::StateChangeDenied), - InstructionResult::StackUnderflow | - InstructionResult::StackOverflow | - InstructionResult::NonceOverflow | - InstructionResult::PrecompileError | - InstructionResult::FatalExternalError => Some(Error::::ContractTrapped), - InstructionResult::OutOfOffset => Some(Error::::OutOfBounds), - InstructionResult::CreateCollision => Some(Error::::DuplicateContract), - InstructionResult::OverflowPayment => Some(Error::::BalanceConversionFailed), - InstructionResult::CreateContractSizeLimit | InstructionResult::CreateInitCodeSizeLimit => - Some(Error::::StaticMemoryTooLarge), - InstructionResult::CallTooDeep => Some(Error::::MaxCallDepthReached), - InstructionResult::OutOfFunds => Some(Error::::TransferFailed), - InstructionResult::CreateInitCodeStartingEF00 | - InstructionResult::InvalidEOFInitCode | - InstructionResult::InvalidExtDelegateCallTarget => Some(Error::::ContractTrapped), - InstructionResult::Stop | - InstructionResult::Return | - InstructionResult::Revert | - InstructionResult::SelfDestruct => None, - } - .map(Into::into) -} - -/// Blanket conversion trait between `sp_core::U256` and `revm::primitives::U256` -pub trait U256Converter { - /// Convert `self` into `revm::primitives::U256` - fn into_revm_u256(&self) -> revm::primitives::U256; - - /// Convert from `revm::primitives::U256` into `Self` - fn from_revm_u256(value: &revm::primitives::U256) -> Self; -} - -impl U256Converter for sp_core::U256 { - fn into_revm_u256(&self) -> revm::primitives::U256 { - let bytes = self.to_big_endian(); - revm::primitives::U256::from_be_bytes(bytes) - } - - fn from_revm_u256(value: &revm::primitives::U256) -> Self { - let bytes = value.to_be_bytes::<32>(); - sp_core::U256::from_big_endian(&bytes) - } -} +// /// Conversion of a `ExecError` to `ReturnErrorCode`. +// /// +// /// Used when converting the error returned from a subcall in order to map it to the +// /// equivalent EVM interpreter [InstructionResult]. +// /// +// /// - Returns `None` when the caller can recover the error. +// /// - Otherwise, some [InstructionResult] error code (the halt reason) is returned. Most +// [ExecError] /// variants don't map to a [InstructionResult]. The conversion is lossy and +// defaults to /// [InstructionResult::Revert] for most cases. +// /// +// /// Uses the overarching [super::exec_error_into_return_code] method to determine if +// /// the error is recoverable or not. This guarantees consistent behavior accross both +// /// VM backends. +// fn exec_error_into_halt_reason(from: ExecError) -> Option { +// log::trace!("call frame execution error in EVM caller: {:?}", &from); +// +// if super::exec_error_into_return_code::(from).is_ok() { +// return None; +// } +// +// let static_memory_too_large = Error::::StaticMemoryTooLarge.into(); +// let code_rejected = Error::::CodeRejected.into(); +// let transfer_failed = Error::::TransferFailed.into(); +// let duplicate_contract = Error::::DuplicateContract.into(); +// let balance_conversion_failed = Error::::BalanceConversionFailed.into(); +// let value_too_large = Error::::ValueTooLarge.into(); +// let out_of_gas = Error::::OutOfGas.into(); +// let out_of_deposit = Error::::StorageDepositLimitExhausted.into(); +// +// Some(match from.error { +// err if err == static_memory_too_large => InstructionResult::MemoryLimitOOG, +// err if err == code_rejected => InstructionResult::OpcodeNotFound, +// err if err == transfer_failed => InstructionResult::OutOfFunds, +// err if err == duplicate_contract => InstructionResult::CreateCollision, +// err if err == balance_conversion_failed => InstructionResult::OverflowPayment, +// err if err == value_too_large => InstructionResult::OverflowPayment, +// err if err == out_of_deposit => InstructionResult::OutOfFunds, +// err if err == out_of_gas => InstructionResult::OutOfGas, +// _ => InstructionResult::Revert, +// }) +// } +// +// /// Map [InstructionResult] into an [ExecError] for passing it up the stack. +// /// +// /// Returns `None` if the instruction result is not an error case. +// fn instruction_result_into_exec_error(from: InstructionResult) -> Option { +// match from { +// InstructionResult::OutOfGas | +// InstructionResult::InvalidOperandOOG | +// InstructionResult::ReentrancySentryOOG | +// InstructionResult::PrecompileOOG | +// InstructionResult::MemoryOOG => Some(Error::::OutOfGas), +// InstructionResult::MemoryLimitOOG => Some(Error::::StaticMemoryTooLarge), +// InstructionResult::OpcodeNotFound | +// InstructionResult::InvalidJump | +// InstructionResult::NotActivated | +// InstructionResult::InvalidFEOpcode | +// InstructionResult::CreateContractStartingWithEF => Some(Error::::InvalidInstruction), +// InstructionResult::CallNotAllowedInsideStatic | +// InstructionResult::StateChangeDuringStaticCall => Some(Error::::StateChangeDenied), +// InstructionResult::StackUnderflow | +// InstructionResult::StackOverflow | +// InstructionResult::NonceOverflow | +// InstructionResult::PrecompileError | +// InstructionResult::FatalExternalError => Some(Error::::ContractTrapped), +// InstructionResult::OutOfOffset => Some(Error::::OutOfBounds), +// InstructionResult::CreateCollision => Some(Error::::DuplicateContract), +// InstructionResult::OverflowPayment => Some(Error::::BalanceConversionFailed), +// InstructionResult::CreateContractSizeLimit | InstructionResult::CreateInitCodeSizeLimit => +// Some(Error::::StaticMemoryTooLarge), +// InstructionResult::CallTooDeep => Some(Error::::MaxCallDepthReached), +// InstructionResult::OutOfFunds => Some(Error::::TransferFailed), +// InstructionResult::CreateInitCodeStartingEF00 | +// InstructionResult::InvalidEOFInitCode | +// InstructionResult::InvalidExtDelegateCallTarget => Some(Error::::ContractTrapped), +// InstructionResult::Stop | +// InstructionResult::Return | +// InstructionResult::Revert | +// InstructionResult::SelfDestruct => None, +// } +// .map(Into::into) +// } diff --git a/substrate/frame/revive/src/vm/evm/instructions/arithmetic.rs b/substrate/frame/revive/src/vm/evm/instructions/arithmetic.rs index ffd2d968981cf..77859e680420a 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/arithmetic.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/arithmetic.rs @@ -15,142 +15,169 @@ // See the License for the specific language governing permissions and // limitations under the License. -use super::{ - i256::{i256_div, i256_mod}, - Context, -}; -use crate::vm::Ext; -use revm::{ - interpreter::{ - gas as revm_gas, - interpreter_types::{RuntimeFlag, StackTr}, - }, - primitives::U256, +use super::i256::{i256_div, i256_mod}; +use crate::{ + vm::{evm::Interpreter, Ext}, + U256, }; +use revm::interpreter::gas::{EXP, LOW, MID, VERYLOW}; +use sp_runtime::DispatchResult; /// Implements the ADD instruction - adds two values from stack. -pub fn add<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::VERYLOW); - popn_top!([op1], op2, context.interpreter); - *op2 = op1.wrapping_add(*op2); +pub fn add<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { + interpreter.ext.gas_meter_mut().charge_evm_gas(VERYLOW)?; + let ([op1], op2) = interpreter.stack.popn_top()?; + *op2 = op1.saturating_add(*op2); + Ok(()) } /// Implements the MUL instruction - multiplies two values from stack. -pub fn mul<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::LOW); - popn_top!([op1], op2, context.interpreter); - *op2 = op1.wrapping_mul(*op2); +pub fn mul<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { + interpreter.ext.gas_meter_mut().charge_evm_gas(LOW)?; + let ([op1], op2) = interpreter.stack.popn_top()?; + *op2 = op1.saturating_mul(*op2); + Ok(()) } /// Implements the SUB instruction - subtracts two values from stack. -pub fn sub<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::VERYLOW); - popn_top!([op1], op2, context.interpreter); - *op2 = op1.wrapping_sub(*op2); +pub fn sub<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { + interpreter.ext.gas_meter_mut().charge_evm_gas(VERYLOW)?; + let ([op1], op2) = interpreter.stack.popn_top()?; + *op2 = op1.saturating_sub(*op2); + Ok(()) } /// Implements the DIV instruction - divides two values from stack. -pub fn div<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::LOW); - popn_top!([op1], op2, context.interpreter); +pub fn div<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { + interpreter.ext.gas_meter_mut().charge_evm_gas(LOW)?; + let ([op1], op2) = interpreter.stack.popn_top()?; if !op2.is_zero() { - *op2 = op1.wrapping_div(*op2); + *op2 = op1 / *op2; } + Ok(()) } /// Implements the SDIV instruction. /// /// Performs signed division of two values from stack. -pub fn sdiv<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::LOW); - popn_top!([op1], op2, context.interpreter); +pub fn sdiv<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { + interpreter.ext.gas_meter_mut().charge_evm_gas(LOW)?; + let ([op1], op2) = interpreter.stack.popn_top()?; *op2 = i256_div(op1, *op2); + Ok(()) } - /// Implements the MOD instruction. /// /// Pops two values from stack and pushes the remainder of their division. -pub fn rem<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::LOW); - popn_top!([op1], op2, context.interpreter); +pub fn rem<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { + interpreter.ext.gas_meter_mut().charge_evm_gas(LOW)?; + let ([op1], op2) = interpreter.stack.popn_top()?; if !op2.is_zero() { - *op2 = op1.wrapping_rem(*op2); + *op2 = op1 % *op2; } + Ok(()) } /// Implements the SMOD instruction. /// /// Performs signed modulo of two values from stack. -pub fn smod<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::LOW); - popn_top!([op1], op2, context.interpreter); - *op2 = i256_mod(op1, *op2) +pub fn smod<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { + interpreter.ext.gas_meter_mut().charge_evm_gas(LOW)?; + let ([op1], op2) = interpreter.stack.popn_top()?; + *op2 = i256_mod(op1, *op2); + Ok(()) } /// Implements the ADDMOD instruction. /// /// Pops three values from stack and pushes (a + b) % n. -pub fn addmod<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::MID); - popn_top!([op1, op2], op3, context.interpreter); - *op3 = op1.add_mod(op2, *op3) +pub fn addmod<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { + interpreter.ext.gas_meter_mut().charge_evm_gas(MID)?; + let ([op1, op2], op3) = interpreter.stack.popn_top()?; + *op3 = op1.add_mod(op2, *op3); + Ok(()) } /// Implements the MULMOD instruction. /// /// Pops three values from stack and pushes (a * b) % n. -pub fn mulmod<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::MID); - popn_top!([op1, op2], op3, context.interpreter); - *op3 = op1.mul_mod(op2, *op3) +pub fn mulmod<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { + interpreter.ext.gas_meter_mut().charge_evm_gas(MID)?; + let ([op1, op2], op3) = interpreter.stack.popn_top()?; + *op3 = op1.mul_mod(op2, *op3); + Ok(()) } /// Implements the EXP instruction - exponentiates two values from stack. -pub fn exp<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - let spec_id = context.interpreter.runtime_flag.spec_id(); - popn_top!([op1], op2, context.interpreter); - gas_or_fail_legacy!(context.interpreter, revm_gas::exp_cost(spec_id, *op2)); - *op2 = op1.pow(*op2); -} +// pub fn exp<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { +// let ([op1], op2) = interpreter.stack.popn_top()?; +// // Calculate gas cost based on exponent size - use approximate cost calculation +// // since we don't have access to SpecId here +// interpreter +// .ext +// .gas_meter_mut() +// .charge_evm_gas(revm_gas::exp_cost(spec_id, *op2))?; +// *op2 = op1.pow(*op2); +// Ok(()) +// } +// /// Implements the `SIGNEXTEND` opcode as defined in the Ethereum Yellow Paper. +// /// +// /// In the yellow paper `SIGNEXTEND` is defined to take two inputs, we will call them +// /// `x` and `y`, and produce one output. +// /// +// /// The first `t` bits of the output (numbering from the left, starting from 0) are +// /// equal to the `t`-th bit of `y`, where `t` is equal to `256 - 8(x + 1)`. +// /// +// /// The remaining bits of the output are equal to the corresponding bits of `y`. +// /// +// /// **Note**: If `x >= 32` then the output is equal to `y` since `t <= 0`. +// /// +// /// To efficiently implement this algorithm in the case `x < 32` we do the following. +// /// +// /// Let `b` be equal to the `t`-th bit of `y` and let `s = 255 - t = 8x + 7` +// /// (this is effectively the same index as `t`, but numbering the bits from the +// /// right instead of the left). +// /// +// /// We can create a bit mask which is all zeros up to and including the `t`-th bit, +// /// and all ones afterwards by computing the quantity `2^s - 1`. +// /// +// /// We can use this mask to compute the output depending on the value of `b`. +// /// +// /// If `b == 1` then the yellow paper says the output should be all ones up to +// /// and including the `t`-th bit, followed by the remaining bits of `y`; this is equal to +// /// `y | !mask` where `|` is the bitwise `OR` and `!` is bitwise negation. +// /// +// /// Similarly, if `b == 0` then the yellow paper says the output should start with all zeros, +// /// then end with bits from `b`; this is equal to `y & mask` where `&` is bitwise `AND`. +// pub fn signextend<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { +// interpreter.ext.gas_meter_mut().charge_evm_gas(LOW)?; +// let ([ext], x) = interpreter.stack.popn_top()?; +// // For 31 we also don't need to do anything. +// if ext < U256::from(31) { +// let ext_low = ext.low_u64() as usize; +// if ext_low < 32 { +// let bit_index = 8 * ext_low + 7; +// if bit_index < 256 { +// let bit = x.bit(bit_index); +// let mask = (U256::from(1) << bit_index) - U256::from(1); +// *x = if bit { *x | !mask } else { *x & mask }; +// } +// } +// } +// Ok(()) +// } -/// Implements the `SIGNEXTEND` opcode as defined in the Ethereum Yellow Paper. -/// -/// In the yellow paper `SIGNEXTEND` is defined to take two inputs, we will call them -/// `x` and `y`, and produce one output. -/// -/// The first `t` bits of the output (numbering from the left, starting from 0) are -/// equal to the `t`-th bit of `y`, where `t` is equal to `256 - 8(x + 1)`. -/// -/// The remaining bits of the output are equal to the corresponding bits of `y`. -/// -/// **Note**: If `x >= 32` then the output is equal to `y` since `t <= 0`. -/// -/// To efficiently implement this algorithm in the case `x < 32` we do the following. -/// -/// Let `b` be equal to the `t`-th bit of `y` and let `s = 255 - t = 8x + 7` -/// (this is effectively the same index as `t`, but numbering the bits from the -/// right instead of the left). -/// -/// We can create a bit mask which is all zeros up to and including the `t`-th bit, -/// and all ones afterwards by computing the quantity `2^s - 1`. -/// -/// We can use this mask to compute the output depending on the value of `b`. -/// -/// If `b == 1` then the yellow paper says the output should be all ones up to -/// and including the `t`-th bit, followed by the remaining bits of `y`; this is equal to -/// `y | !mask` where `|` is the bitwise `OR` and `!` is bitwise negation. -/// -/// Similarly, if `b == 0` then the yellow paper says the output should start with all zeros, -/// then end with bits from `b`; this is equal to `y & mask` where `&` is bitwise `AND`. -pub fn signextend<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::LOW); - popn_top!([ext], x, context.interpreter); - // For 31 we also don't need to do anything. - if ext < U256::from(31) { - let ext = ext.as_limbs()[0]; - let bit_index = (8 * ext + 7) as usize; - let bit = x.bit(bit_index); - let mask = (U256::from(1) << bit_index) - U256::from(1); - *x = if bit { *x | !mask } else { *x & mask }; +/// `EXP` opcode cost calculation. +#[inline] +pub fn exp_cost(power: U256) -> Option { + if power.is_zero() { + Some(EXP) + } else { + // EIP-160: EXP cost increase + let gas_byte = 50; + let gas = U256::from(EXP) + .checked_add(gas_byte.checked_mul(U256::from(log2floor(power) / 8 + 1))?)?; + + u64::try_from(gas).ok() } } diff --git a/substrate/frame/revive/src/vm/evm/instructions/i256.rs b/substrate/frame/revive/src/vm/evm/instructions/i256.rs index 44f1b35a101b8..226fdf0deec3a 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/i256.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/i256.rs @@ -16,7 +16,7 @@ // limitations under the License. use core::cmp::Ordering; -use revm::primitives::U256; +use sp_core::U256; /// Represents the sign of a 256-bit signed integer value. #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -34,7 +34,7 @@ pub enum Sign { #[cfg(test)] /// The maximum positive value for a 256-bit signed integer. -pub const MAX_POSITIVE_VALUE: U256 = U256::from_limbs([ +pub const MAX_POSITIVE_VALUE: U256 = U256([ 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, @@ -42,7 +42,7 @@ pub const MAX_POSITIVE_VALUE: U256 = U256::from_limbs([ ]); /// The minimum negative value for a 256-bit signed integer. -pub const MIN_NEGATIVE_VALUE: U256 = U256::from_limbs([ +pub const MIN_NEGATIVE_VALUE: U256 = U256([ 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, @@ -54,7 +54,7 @@ const FLIPH_BITMASK_U64: u64 = 0x7FFF_FFFF_FFFF_FFFF; /// Determines the sign of a 256-bit signed integer. #[inline] pub fn i256_sign(val: &U256) -> Sign { - if val.bit(U256::BITS - 1) { + if val.bit(255) { Sign::Minus } else { // SAFETY: false == 0 == Zero, true == 1 == Plus @@ -74,10 +74,14 @@ pub fn i256_sign_compl(val: &mut U256) -> Sign { #[inline] fn u256_remove_sign(val: &mut U256) { - // SAFETY: U256 does not have any padding bytes - unsafe { - val.as_limbs_mut()[3] &= FLIPH_BITMASK_U64; - } + // Clear the sign bit by masking the highest bit + let limbs = val.0; + *val = U256([ + limbs[0], + limbs[1], + limbs[2], + limbs[3] & FLIPH_BITMASK_U64, + ]); } /// Computes the two's complement of a U256 value in place. @@ -89,7 +93,7 @@ pub fn two_compl_mut(op: &mut U256) { /// Computes the two's complement of a U256 value. #[inline] pub fn two_compl(op: U256) -> U256 { - op.wrapping_neg() + (!op).overflowing_add(U256::from(1)).0 } /// Compares two 256-bit signed integers. @@ -110,7 +114,7 @@ pub fn i256_cmp(first: &U256, second: &U256) -> Ordering { pub fn i256_div(mut first: U256, mut second: U256) -> U256 { let second_sign = i256_sign_compl(&mut second); if second_sign == Sign::Zero { - return U256::ZERO; + return U256::zero(); } let first_sign = i256_sign_compl(&mut first); @@ -140,12 +144,12 @@ pub fn i256_div(mut first: U256, mut second: U256) -> U256 { pub fn i256_mod(mut first: U256, mut second: U256) -> U256 { let first_sign = i256_sign_compl(&mut first); if first_sign == Sign::Zero { - return U256::ZERO; + return U256::zero(); } let second_sign = i256_sign_compl(&mut second); if second_sign == Sign::Zero { - return U256::ZERO; + return U256::zero(); } let mut r = first % second; @@ -164,7 +168,6 @@ pub fn i256_mod(mut first: U256, mut second: U256) -> U256 { mod tests { use super::*; use core::num::Wrapping; - use revm::primitives::uint; #[test] fn div_i256() { @@ -173,101 +176,94 @@ mod tests { assert_eq!(Wrapping(i8::MIN) / Wrapping(-1), Wrapping(i8::MIN)); assert_eq!(i8::MAX / -1, -i8::MAX); - uint! { - assert_eq!(i256_div(MIN_NEGATIVE_VALUE, -1_U256), MIN_NEGATIVE_VALUE); - assert_eq!(i256_div(MIN_NEGATIVE_VALUE, 1_U256), MIN_NEGATIVE_VALUE); - assert_eq!(i256_div(MAX_POSITIVE_VALUE, 1_U256), MAX_POSITIVE_VALUE); - assert_eq!(i256_div(MAX_POSITIVE_VALUE, -1_U256), -1_U256 * MAX_POSITIVE_VALUE); - assert_eq!(i256_div(100_U256, -1_U256), -100_U256); - assert_eq!(i256_div(100_U256, 2_U256), 50_U256); - } + let neg_one = U256::from(1).overflowing_neg().0; + assert_eq!(i256_div(MIN_NEGATIVE_VALUE, neg_one), MIN_NEGATIVE_VALUE); + assert_eq!(i256_div(MIN_NEGATIVE_VALUE, U256::from(1)), MIN_NEGATIVE_VALUE); + assert_eq!(i256_div(MAX_POSITIVE_VALUE, U256::from(1)), MAX_POSITIVE_VALUE); + assert_eq!(i256_div(MAX_POSITIVE_VALUE, neg_one), (!MAX_POSITIVE_VALUE).overflowing_add(U256::from(1)).0); + assert_eq!(i256_div(U256::from(100), neg_one), U256::from(100).overflowing_neg().0); + assert_eq!(i256_div(U256::from(100), U256::from(2)), U256::from(50)); } #[test] fn test_i256_sign() { - uint! { - assert_eq!(i256_sign(&0_U256), Sign::Zero); - assert_eq!(i256_sign(&1_U256), Sign::Plus); - assert_eq!(i256_sign(&-1_U256), Sign::Minus); - assert_eq!(i256_sign(&MIN_NEGATIVE_VALUE), Sign::Minus); - assert_eq!(i256_sign(&MAX_POSITIVE_VALUE), Sign::Plus); - } + assert_eq!(i256_sign(&U256::zero()), Sign::Zero); + assert_eq!(i256_sign(&U256::from(1)), Sign::Plus); + assert_eq!(i256_sign(&U256::from(1).overflowing_neg().0), Sign::Minus); + assert_eq!(i256_sign(&MIN_NEGATIVE_VALUE), Sign::Minus); + assert_eq!(i256_sign(&MAX_POSITIVE_VALUE), Sign::Plus); } #[test] fn test_i256_sign_compl() { - uint! { - let mut zero = 0_U256; - let mut positive = 1_U256; - let mut negative = -1_U256; - assert_eq!(i256_sign_compl(&mut zero), Sign::Zero); - assert_eq!(i256_sign_compl(&mut positive), Sign::Plus); - assert_eq!(i256_sign_compl(&mut negative), Sign::Minus); - } + let mut zero = U256::zero(); + let mut positive = U256::from(1); + let mut negative = U256::from(1).overflowing_neg().0; + assert_eq!(i256_sign_compl(&mut zero), Sign::Zero); + assert_eq!(i256_sign_compl(&mut positive), Sign::Plus); + assert_eq!(i256_sign_compl(&mut negative), Sign::Minus); } #[test] fn test_two_compl() { - uint! { - assert_eq!(two_compl(0_U256), 0_U256); - assert_eq!(two_compl(1_U256), -1_U256); - assert_eq!(two_compl(-1_U256), 1_U256); - assert_eq!(two_compl(2_U256), -2_U256); - assert_eq!(two_compl(-2_U256), 2_U256); - - // Two's complement of the min value is itself. - assert_eq!(two_compl(MIN_NEGATIVE_VALUE), MIN_NEGATIVE_VALUE); - } + assert_eq!(two_compl(U256::zero()), U256::zero()); + assert_eq!(two_compl(U256::from(1)), U256::from(1).overflowing_neg().0); + assert_eq!(two_compl(U256::from(1).overflowing_neg().0), U256::from(1)); + assert_eq!(two_compl(U256::from(2)), U256::from(2).overflowing_neg().0); + assert_eq!(two_compl(U256::from(2).overflowing_neg().0), U256::from(2)); + + // Two's complement of the min value is itself. + assert_eq!(two_compl(MIN_NEGATIVE_VALUE), MIN_NEGATIVE_VALUE); } #[test] fn test_two_compl_mut() { - uint! { - let mut value = 1_U256; - two_compl_mut(&mut value); - assert_eq!(value, -1_U256); - } + let mut value = U256::from(1); + two_compl_mut(&mut value); + assert_eq!(value, U256::from(1).overflowing_neg().0); } #[test] fn test_i256_cmp() { - uint! { - assert_eq!(i256_cmp(&1_U256, &2_U256), Ordering::Less); - assert_eq!(i256_cmp(&2_U256, &2_U256), Ordering::Equal); - assert_eq!(i256_cmp(&3_U256, &2_U256), Ordering::Greater); - assert_eq!(i256_cmp(&-1_U256, &-1_U256), Ordering::Equal); - assert_eq!(i256_cmp(&-1_U256, &-2_U256), Ordering::Greater); - assert_eq!(i256_cmp(&-1_U256, &0_U256), Ordering::Less); - assert_eq!(i256_cmp(&-2_U256, &2_U256), Ordering::Less); - } + assert_eq!(i256_cmp(&U256::from(1), &U256::from(2)), Ordering::Less); + assert_eq!(i256_cmp(&U256::from(2), &U256::from(2)), Ordering::Equal); + assert_eq!(i256_cmp(&U256::from(3), &U256::from(2)), Ordering::Greater); + let neg_one = U256::from(1).overflowing_neg().0; + let neg_two = U256::from(2).overflowing_neg().0; + assert_eq!(i256_cmp(&neg_one, &neg_one), Ordering::Equal); + assert_eq!(i256_cmp(&neg_one, &neg_two), Ordering::Greater); + assert_eq!(i256_cmp(&neg_one, &U256::zero()), Ordering::Less); + assert_eq!(i256_cmp(&neg_two, &U256::from(2)), Ordering::Less); } #[test] fn test_i256_div() { - uint! { - assert_eq!(i256_div(1_U256, 0_U256), 0_U256); - assert_eq!(i256_div(0_U256, 1_U256), 0_U256); - assert_eq!(i256_div(0_U256, -1_U256), 0_U256); - assert_eq!(i256_div(MIN_NEGATIVE_VALUE, 1_U256), MIN_NEGATIVE_VALUE); - assert_eq!(i256_div(4_U256, 2_U256), 2_U256); - assert_eq!(i256_div(MIN_NEGATIVE_VALUE, MIN_NEGATIVE_VALUE), 1_U256); - assert_eq!(i256_div(2_U256, -1_U256), -2_U256); - assert_eq!(i256_div(-2_U256, -1_U256), 2_U256); - } + let neg_one = U256::from(1).overflowing_neg().0; + let neg_two = U256::from(2).overflowing_neg().0; + + assert_eq!(i256_div(U256::from(1), U256::zero()), U256::zero()); + assert_eq!(i256_div(U256::zero(), U256::from(1)), U256::zero()); + assert_eq!(i256_div(U256::zero(), neg_one), U256::zero()); + assert_eq!(i256_div(MIN_NEGATIVE_VALUE, U256::from(1)), MIN_NEGATIVE_VALUE); + assert_eq!(i256_div(U256::from(4), U256::from(2)), U256::from(2)); + assert_eq!(i256_div(MIN_NEGATIVE_VALUE, MIN_NEGATIVE_VALUE), U256::from(1)); + assert_eq!(i256_div(U256::from(2), neg_one), neg_two); + assert_eq!(i256_div(neg_two, neg_one), U256::from(2)); } #[test] fn test_i256_mod() { - uint! { - assert_eq!(i256_mod(0_U256, 1_U256), 0_U256); - assert_eq!(i256_mod(1_U256, 0_U256), 0_U256); - assert_eq!(i256_mod(4_U256, 2_U256), 0_U256); - assert_eq!(i256_mod(3_U256, 2_U256), 1_U256); - assert_eq!(i256_mod(MIN_NEGATIVE_VALUE, 1_U256), 0_U256); - assert_eq!(i256_mod(2_U256, 2_U256), 0_U256); - assert_eq!(i256_mod(2_U256, 3_U256), 2_U256); - assert_eq!(i256_mod(-2_U256, 3_U256), -2_U256); - assert_eq!(i256_mod(2_U256, -3_U256), 2_U256); - assert_eq!(i256_mod(-2_U256, -3_U256), -2_U256); - } + let neg_two = U256::from(2).overflowing_neg().0; + let neg_three = U256::from(3).overflowing_neg().0; + + assert_eq!(i256_mod(U256::zero(), U256::from(1)), U256::zero()); + assert_eq!(i256_mod(U256::from(1), U256::zero()), U256::zero()); + assert_eq!(i256_mod(U256::from(4), U256::from(2)), U256::zero()); + assert_eq!(i256_mod(U256::from(3), U256::from(2)), U256::from(1)); + assert_eq!(i256_mod(MIN_NEGATIVE_VALUE, U256::from(1)), U256::zero()); + assert_eq!(i256_mod(U256::from(2), U256::from(2)), U256::zero()); + assert_eq!(i256_mod(U256::from(2), U256::from(3)), U256::from(2)); + assert_eq!(i256_mod(neg_two, U256::from(3)), neg_two); + assert_eq!(i256_mod(U256::from(2), neg_three), U256::from(2)); + assert_eq!(i256_mod(neg_two, neg_three), neg_two); } } diff --git a/substrate/frame/revive/src/vm/evm/instructions/mod.rs b/substrate/frame/revive/src/vm/evm/instructions/mod.rs index 826c1febd474d..8b2e1cb00db3d 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/mod.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/mod.rs @@ -17,231 +17,225 @@ //! EVM opcode implementations. -use crate::vm::{ - evm::{DummyHost, EVMInterpreter}, - Ext, -}; -use revm::interpreter::{Instruction, InstructionContext}; +use super::interpreter::Interpreter; +use crate::vm::Ext; -type Context<'ctx, 'ext, E> = - InstructionContext<'ctx, crate::vm::evm::DummyHost, crate::vm::evm::EVMInterpreter<'ext, E>>; - -#[macro_use] -mod macros; -/// Arithmetic operations (ADD, SUB, MUL, DIV, etc.). +// #[macro_use] +// mod macros; +// /// Arithmetic operations (ADD, SUB, MUL, DIV, etc.). mod arithmetic; -/// Bitwise operations (AND, OR, XOR, NOT, etc.). -mod bitwise; -/// Block information instructions (COINBASE, TIMESTAMP, etc.). -mod block_info; -/// Contract operations (CALL, CREATE, DELEGATECALL, etc.). -mod contract; -/// Control flow instructions (JUMP, JUMPI, REVERT, etc.). -mod control; -/// Host environment interactions (SLOAD, SSTORE, LOG, etc.). -mod host; +// /// Bitwise operations (AND, OR, XOR, NOT, etc.). +// mod bitwise; +// /// Block information instructions (COINBASE, TIMESTAMP, etc.). +// mod block_info; +// /// Contract operations (CALL, CREATE, DELEGATECALL, etc.). +// mod contract; +// /// Control flow instructions (JUMP, JUMPI, REVERT, etc.). +// mod control; +// /// Host environment interactions (SLOAD, SSTORE, LOG, etc.). +// mod host; /// Signed 256-bit integer operations. mod i256; -/// Memory operations (MLOAD, MSTORE, MSIZE, etc.). -mod memory; -/// Stack operations (PUSH, POP, DUP, SWAP, etc.). -mod stack; -/// System information instructions (ADDRESS, CALLER, etc.). -mod system; -/// Transaction information instructions (ORIGIN, GASPRICE, etc.). -mod tx_info; -/// Utility functions and helpers for instruction implementation. -mod utility; - -/// Returns the instruction table for the given spec. -pub const fn instruction_table<'a, E: Ext>() -> [Instruction, DummyHost>; 256] -{ - use revm::bytecode::opcode::*; - let mut table = [control::unknown as Instruction, DummyHost>; 256]; - - table[STOP as usize] = control::stop; - table[ADD as usize] = arithmetic::add; - table[MUL as usize] = arithmetic::mul; - table[SUB as usize] = arithmetic::sub; - table[DIV as usize] = arithmetic::div; - table[SDIV as usize] = arithmetic::sdiv; - table[MOD as usize] = arithmetic::rem; - table[SMOD as usize] = arithmetic::smod; - table[ADDMOD as usize] = arithmetic::addmod; - table[MULMOD as usize] = arithmetic::mulmod; - table[EXP as usize] = arithmetic::exp; - table[SIGNEXTEND as usize] = arithmetic::signextend; - - table[LT as usize] = bitwise::lt; - table[GT as usize] = bitwise::gt; - table[SLT as usize] = bitwise::slt; - table[SGT as usize] = bitwise::sgt; - table[EQ as usize] = bitwise::eq; - table[ISZERO as usize] = bitwise::iszero; - table[AND as usize] = bitwise::bitand; - table[OR as usize] = bitwise::bitor; - table[XOR as usize] = bitwise::bitxor; - table[NOT as usize] = bitwise::not; - table[BYTE as usize] = bitwise::byte; - table[SHL as usize] = bitwise::shl; - table[SHR as usize] = bitwise::shr; - table[SAR as usize] = bitwise::sar; - table[CLZ as usize] = bitwise::clz; - - table[KECCAK256 as usize] = system::keccak256; - - table[ADDRESS as usize] = system::address; - table[BALANCE as usize] = host::balance; - table[ORIGIN as usize] = tx_info::origin; - table[CALLER as usize] = system::caller; - table[CALLVALUE as usize] = system::callvalue; - table[CALLDATALOAD as usize] = system::calldataload; - table[CALLDATASIZE as usize] = system::calldatasize; - table[CALLDATACOPY as usize] = system::calldatacopy; - table[CODESIZE as usize] = system::codesize; - table[CODECOPY as usize] = system::codecopy; - - table[GASPRICE as usize] = tx_info::gasprice; - table[EXTCODESIZE as usize] = host::extcodesize; - table[EXTCODECOPY as usize] = host::extcodecopy; - table[RETURNDATASIZE as usize] = system::returndatasize; - table[RETURNDATACOPY as usize] = system::returndatacopy; - table[EXTCODEHASH as usize] = host::extcodehash; - table[BLOCKHASH as usize] = host::blockhash; - table[COINBASE as usize] = block_info::coinbase; - table[TIMESTAMP as usize] = block_info::timestamp; - table[NUMBER as usize] = block_info::block_number; - table[DIFFICULTY as usize] = block_info::difficulty; - table[GASLIMIT as usize] = block_info::gaslimit; - table[CHAINID as usize] = block_info::chainid; - table[SELFBALANCE as usize] = host::selfbalance; - table[BASEFEE as usize] = block_info::basefee; - table[BLOBHASH as usize] = tx_info::blob_hash; - table[BLOBBASEFEE as usize] = block_info::blob_basefee; - - table[POP as usize] = stack::pop; - table[MLOAD as usize] = memory::mload; - table[MSTORE as usize] = memory::mstore; - table[MSTORE8 as usize] = memory::mstore8; - table[SLOAD as usize] = host::sload; - table[SSTORE as usize] = host::sstore; - table[JUMP as usize] = control::jump; - table[JUMPI as usize] = control::jumpi; - table[PC as usize] = control::pc; - table[MSIZE as usize] = memory::msize; - table[GAS as usize] = system::gas; - table[JUMPDEST as usize] = control::jumpdest; - table[TLOAD as usize] = host::tload; - table[TSTORE as usize] = host::tstore; - table[MCOPY as usize] = memory::mcopy; - - table[PUSH0 as usize] = stack::push0; - table[PUSH1 as usize] = stack::push::<1, _>; - table[PUSH2 as usize] = stack::push::<2, _>; - table[PUSH3 as usize] = stack::push::<3, _>; - table[PUSH4 as usize] = stack::push::<4, _>; - table[PUSH5 as usize] = stack::push::<5, _>; - table[PUSH6 as usize] = stack::push::<6, _>; - table[PUSH7 as usize] = stack::push::<7, _>; - table[PUSH8 as usize] = stack::push::<8, _>; - table[PUSH9 as usize] = stack::push::<9, _>; - table[PUSH10 as usize] = stack::push::<10, _>; - table[PUSH11 as usize] = stack::push::<11, _>; - table[PUSH12 as usize] = stack::push::<12, _>; - table[PUSH13 as usize] = stack::push::<13, _>; - table[PUSH14 as usize] = stack::push::<14, _>; - table[PUSH15 as usize] = stack::push::<15, _>; - table[PUSH16 as usize] = stack::push::<16, _>; - table[PUSH17 as usize] = stack::push::<17, _>; - table[PUSH18 as usize] = stack::push::<18, _>; - table[PUSH19 as usize] = stack::push::<19, _>; - table[PUSH20 as usize] = stack::push::<20, _>; - table[PUSH21 as usize] = stack::push::<21, _>; - table[PUSH22 as usize] = stack::push::<22, _>; - table[PUSH23 as usize] = stack::push::<23, _>; - table[PUSH24 as usize] = stack::push::<24, _>; - table[PUSH25 as usize] = stack::push::<25, _>; - table[PUSH26 as usize] = stack::push::<26, _>; - table[PUSH27 as usize] = stack::push::<27, _>; - table[PUSH28 as usize] = stack::push::<28, _>; - table[PUSH29 as usize] = stack::push::<29, _>; - table[PUSH30 as usize] = stack::push::<30, _>; - table[PUSH31 as usize] = stack::push::<31, _>; - table[PUSH32 as usize] = stack::push::<32, _>; - - table[DUP1 as usize] = stack::dup::<1, _>; - table[DUP2 as usize] = stack::dup::<2, _>; - table[DUP3 as usize] = stack::dup::<3, _>; - table[DUP4 as usize] = stack::dup::<4, _>; - table[DUP5 as usize] = stack::dup::<5, _>; - table[DUP6 as usize] = stack::dup::<6, _>; - table[DUP7 as usize] = stack::dup::<7, _>; - table[DUP8 as usize] = stack::dup::<8, _>; - table[DUP9 as usize] = stack::dup::<9, _>; - table[DUP10 as usize] = stack::dup::<10, _>; - table[DUP11 as usize] = stack::dup::<11, _>; - table[DUP12 as usize] = stack::dup::<12, _>; - table[DUP13 as usize] = stack::dup::<13, _>; - table[DUP14 as usize] = stack::dup::<14, _>; - table[DUP15 as usize] = stack::dup::<15, _>; - table[DUP16 as usize] = stack::dup::<16, _>; - - table[SWAP1 as usize] = stack::swap::<1, _>; - table[SWAP2 as usize] = stack::swap::<2, _>; - table[SWAP3 as usize] = stack::swap::<3, _>; - table[SWAP4 as usize] = stack::swap::<4, _>; - table[SWAP5 as usize] = stack::swap::<5, _>; - table[SWAP6 as usize] = stack::swap::<6, _>; - table[SWAP7 as usize] = stack::swap::<7, _>; - table[SWAP8 as usize] = stack::swap::<8, _>; - table[SWAP9 as usize] = stack::swap::<9, _>; - table[SWAP10 as usize] = stack::swap::<10, _>; - table[SWAP11 as usize] = stack::swap::<11, _>; - table[SWAP12 as usize] = stack::swap::<12, _>; - table[SWAP13 as usize] = stack::swap::<13, _>; - table[SWAP14 as usize] = stack::swap::<14, _>; - table[SWAP15 as usize] = stack::swap::<15, _>; - table[SWAP16 as usize] = stack::swap::<16, _>; - - table[LOG0 as usize] = host::log::<0, _>; - table[LOG1 as usize] = host::log::<1, _>; - table[LOG2 as usize] = host::log::<2, _>; - table[LOG3 as usize] = host::log::<3, _>; - table[LOG4 as usize] = host::log::<4, _>; - - table[CREATE as usize] = contract::create::; - table[CALL as usize] = contract::call; - table[CALLCODE as usize] = contract::call_code; - table[RETURN as usize] = control::ret; - table[DELEGATECALL as usize] = contract::delegate_call; - table[CREATE2 as usize] = contract::create::; - - table[STATICCALL as usize] = contract::static_call; - table[REVERT as usize] = control::revert; - table[INVALID as usize] = control::invalid; - table[SELFDESTRUCT as usize] = host::selfdestruct; - table -} - -#[cfg(test)] -mod tests { - use super::instruction_table; - use revm::bytecode::opcode::*; - - #[test] - fn all_instructions_and_opcodes_used() { - // known unknown instruction we compare it with other instructions from table. - let unknown_instruction = 0x0C_usize; - - use crate::{exec::Stack, tests::Test, ContractBlob}; - let instr_table = instruction_table::<'static, Stack<'static, Test, ContractBlob>>(); - - let unknown_istr = instr_table[unknown_instruction]; - for (i, instr) in instr_table.iter().enumerate() { - let is_opcode_unknown = OpCode::new(i as u8).is_none(); - // - let is_instr_unknown = std::ptr::fn_addr_eq(*instr, unknown_istr); - assert_eq!(is_instr_unknown, is_opcode_unknown, "Opcode 0x{i:X?} is not handled",); - } - } -} +// /// Memory operations (MLOAD, MSTORE, MSIZE, etc.). +// mod memory; +// /// Stack operations (PUSH, POP, DUP, SWAP, etc.). +// mod stack; +// /// System information instructions (ADDRESS, CALLER, etc.). +// mod system; +// /// Transaction information instructions (ORIGIN, GASPRICE, etc.). +// mod tx_info; +// /// Utility functions and helpers for instruction implementation. +// mod utility; +// +// /// Returns the instruction table for the given spec. +// pub const fn instruction_table<'a, E: Ext>() -> [Instruction, DummyHost>; +// 256] { +// use revm::bytecode::opcode::*; +// let mut table = [control::unknown as Instruction, DummyHost>; 256]; +// +// table[STOP as usize] = control::stop; +// table[ADD as usize] = arithmetic::add; +// table[MUL as usize] = arithmetic::mul; +// table[SUB as usize] = arithmetic::sub; +// table[DIV as usize] = arithmetic::div; +// table[SDIV as usize] = arithmetic::sdiv; +// table[MOD as usize] = arithmetic::rem; +// table[SMOD as usize] = arithmetic::smod; +// table[ADDMOD as usize] = arithmetic::addmod; +// table[MULMOD as usize] = arithmetic::mulmod; +// table[EXP as usize] = arithmetic::exp; +// table[SIGNEXTEND as usize] = arithmetic::signextend; +// +// table[LT as usize] = bitwise::lt; +// table[GT as usize] = bitwise::gt; +// table[SLT as usize] = bitwise::slt; +// table[SGT as usize] = bitwise::sgt; +// table[EQ as usize] = bitwise::eq; +// table[ISZERO as usize] = bitwise::iszero; +// table[AND as usize] = bitwise::bitand; +// table[OR as usize] = bitwise::bitor; +// table[XOR as usize] = bitwise::bitxor; +// table[NOT as usize] = bitwise::not; +// table[BYTE as usize] = bitwise::byte; +// table[SHL as usize] = bitwise::shl; +// table[SHR as usize] = bitwise::shr; +// table[SAR as usize] = bitwise::sar; +// table[CLZ as usize] = bitwise::clz; +// +// table[KECCAK256 as usize] = system::keccak256; +// +// table[ADDRESS as usize] = system::address; +// table[BALANCE as usize] = host::balance; +// table[ORIGIN as usize] = tx_info::origin; +// table[CALLER as usize] = system::caller; +// table[CALLVALUE as usize] = system::callvalue; +// table[CALLDATALOAD as usize] = system::calldataload; +// table[CALLDATASIZE as usize] = system::calldatasize; +// table[CALLDATACOPY as usize] = system::calldatacopy; +// table[CODESIZE as usize] = system::codesize; +// table[CODECOPY as usize] = system::codecopy; +// +// table[GASPRICE as usize] = tx_info::gasprice; +// table[EXTCODESIZE as usize] = host::extcodesize; +// table[EXTCODECOPY as usize] = host::extcodecopy; +// table[RETURNDATASIZE as usize] = system::returndatasize; +// table[RETURNDATACOPY as usize] = system::returndatacopy; +// table[EXTCODEHASH as usize] = host::extcodehash; +// table[BLOCKHASH as usize] = host::blockhash; +// table[COINBASE as usize] = block_info::coinbase; +// table[TIMESTAMP as usize] = block_info::timestamp; +// table[NUMBER as usize] = block_info::block_number; +// table[DIFFICULTY as usize] = block_info::difficulty; +// table[GASLIMIT as usize] = block_info::gaslimit; +// table[CHAINID as usize] = block_info::chainid; +// table[SELFBALANCE as usize] = host::selfbalance; +// table[BASEFEE as usize] = block_info::basefee; +// table[BLOBHASH as usize] = tx_info::blob_hash; +// table[BLOBBASEFEE as usize] = block_info::blob_basefee; +// +// table[POP as usize] = stack::pop; +// table[MLOAD as usize] = memory::mload; +// table[MSTORE as usize] = memory::mstore; +// table[MSTORE8 as usize] = memory::mstore8; +// table[SLOAD as usize] = host::sload; +// table[SSTORE as usize] = host::sstore; +// table[JUMP as usize] = control::jump; +// table[JUMPI as usize] = control::jumpi; +// table[PC as usize] = control::pc; +// table[MSIZE as usize] = memory::msize; +// table[GAS as usize] = system::gas; +// table[JUMPDEST as usize] = control::jumpdest; +// table[TLOAD as usize] = host::tload; +// table[TSTORE as usize] = host::tstore; +// table[MCOPY as usize] = memory::mcopy; +// +// table[PUSH0 as usize] = stack::push0; +// table[PUSH1 as usize] = stack::push::<1, _>; +// table[PUSH2 as usize] = stack::push::<2, _>; +// table[PUSH3 as usize] = stack::push::<3, _>; +// table[PUSH4 as usize] = stack::push::<4, _>; +// table[PUSH5 as usize] = stack::push::<5, _>; +// table[PUSH6 as usize] = stack::push::<6, _>; +// table[PUSH7 as usize] = stack::push::<7, _>; +// table[PUSH8 as usize] = stack::push::<8, _>; +// table[PUSH9 as usize] = stack::push::<9, _>; +// table[PUSH10 as usize] = stack::push::<10, _>; +// table[PUSH11 as usize] = stack::push::<11, _>; +// table[PUSH12 as usize] = stack::push::<12, _>; +// table[PUSH13 as usize] = stack::push::<13, _>; +// table[PUSH14 as usize] = stack::push::<14, _>; +// table[PUSH15 as usize] = stack::push::<15, _>; +// table[PUSH16 as usize] = stack::push::<16, _>; +// table[PUSH17 as usize] = stack::push::<17, _>; +// table[PUSH18 as usize] = stack::push::<18, _>; +// table[PUSH19 as usize] = stack::push::<19, _>; +// table[PUSH20 as usize] = stack::push::<20, _>; +// table[PUSH21 as usize] = stack::push::<21, _>; +// table[PUSH22 as usize] = stack::push::<22, _>; +// table[PUSH23 as usize] = stack::push::<23, _>; +// table[PUSH24 as usize] = stack::push::<24, _>; +// table[PUSH25 as usize] = stack::push::<25, _>; +// table[PUSH26 as usize] = stack::push::<26, _>; +// table[PUSH27 as usize] = stack::push::<27, _>; +// table[PUSH28 as usize] = stack::push::<28, _>; +// table[PUSH29 as usize] = stack::push::<29, _>; +// table[PUSH30 as usize] = stack::push::<30, _>; +// table[PUSH31 as usize] = stack::push::<31, _>; +// table[PUSH32 as usize] = stack::push::<32, _>; +// +// table[DUP1 as usize] = stack::dup::<1, _>; +// table[DUP2 as usize] = stack::dup::<2, _>; +// table[DUP3 as usize] = stack::dup::<3, _>; +// table[DUP4 as usize] = stack::dup::<4, _>; +// table[DUP5 as usize] = stack::dup::<5, _>; +// table[DUP6 as usize] = stack::dup::<6, _>; +// table[DUP7 as usize] = stack::dup::<7, _>; +// table[DUP8 as usize] = stack::dup::<8, _>; +// table[DUP9 as usize] = stack::dup::<9, _>; +// table[DUP10 as usize] = stack::dup::<10, _>; +// table[DUP11 as usize] = stack::dup::<11, _>; +// table[DUP12 as usize] = stack::dup::<12, _>; +// table[DUP13 as usize] = stack::dup::<13, _>; +// table[DUP14 as usize] = stack::dup::<14, _>; +// table[DUP15 as usize] = stack::dup::<15, _>; +// table[DUP16 as usize] = stack::dup::<16, _>; +// +// table[SWAP1 as usize] = stack::swap::<1, _>; +// table[SWAP2 as usize] = stack::swap::<2, _>; +// table[SWAP3 as usize] = stack::swap::<3, _>; +// table[SWAP4 as usize] = stack::swap::<4, _>; +// table[SWAP5 as usize] = stack::swap::<5, _>; +// table[SWAP6 as usize] = stack::swap::<6, _>; +// table[SWAP7 as usize] = stack::swap::<7, _>; +// table[SWAP8 as usize] = stack::swap::<8, _>; +// table[SWAP9 as usize] = stack::swap::<9, _>; +// table[SWAP10 as usize] = stack::swap::<10, _>; +// table[SWAP11 as usize] = stack::swap::<11, _>; +// table[SWAP12 as usize] = stack::swap::<12, _>; +// table[SWAP13 as usize] = stack::swap::<13, _>; +// table[SWAP14 as usize] = stack::swap::<14, _>; +// table[SWAP15 as usize] = stack::swap::<15, _>; +// table[SWAP16 as usize] = stack::swap::<16, _>; +// +// table[LOG0 as usize] = host::log::<0, _>; +// table[LOG1 as usize] = host::log::<1, _>; +// table[LOG2 as usize] = host::log::<2, _>; +// table[LOG3 as usize] = host::log::<3, _>; +// table[LOG4 as usize] = host::log::<4, _>; +// +// table[CREATE as usize] = contract::create::; +// table[CALL as usize] = contract::call; +// table[CALLCODE as usize] = contract::call_code; +// table[RETURN as usize] = control::ret; +// table[DELEGATECALL as usize] = contract::delegate_call; +// table[CREATE2 as usize] = contract::create::; +// +// table[STATICCALL as usize] = contract::static_call; +// table[REVERT as usize] = control::revert; +// table[INVALID as usize] = control::invalid; +// table[SELFDESTRUCT as usize] = host::selfdestruct; +// table +// } +// +// #[cfg(test)] +// mod tests { +// use super::instruction_table; +// use revm::bytecode::opcode::*; +// +// #[test] +// fn all_instructions_and_opcodes_used() { +// // known unknown instruction we compare it with other instructions from table. +// let unknown_instruction = 0x0C_usize; +// +// use crate::{exec::Stack, tests::Test, ContractBlob}; +// let instr_table = instruction_table::<'static, Stack<'static, Test, ContractBlob>>(); +// +// let unknown_istr = instr_table[unknown_instruction]; +// for (i, instr) in instr_table.iter().enumerate() { +// let is_opcode_unknown = OpCode::new(i as u8).is_none(); +// // +// let is_instr_unknown = std::ptr::fn_addr_eq(*instr, unknown_istr); +// assert_eq!(is_instr_unknown, is_opcode_unknown, "Opcode 0x{i:X?} is not handled",); +// } +// } +// } diff --git a/substrate/frame/revive/src/vm/evm/interpreter.rs b/substrate/frame/revive/src/vm/evm/interpreter.rs index ddadebafdfd45..2fadb20042e10 100644 --- a/substrate/frame/revive/src/vm/evm/interpreter.rs +++ b/substrate/frame/revive/src/vm/evm/interpreter.rs @@ -1,40 +1,200 @@ //! Custom EVM interpreter implementation using sp_core types -use crate::vm::{ +use crate::{vm::{ evm::{memory::Memory, stack::Stack}, Ext, -}; +}, DispatchError, Error}; use alloc::vec::Vec; use core::marker::PhantomData; use revm::interpreter::interpreter::ExtBytecode; +/// Out of gas error variants from revm +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum OutOfGasError { + /// Basic out of gas error + Basic, + /// Out of gas during memory operations + Memory, + /// Out of gas due to memory limit exceeded + MemoryLimit, + /// Out of gas in precompile execution + Precompile, + /// Out of gas with invalid operand + InvalidOperand, + /// Out of gas in reentrancy sentry + ReentrancySentry, +} + +/// HaltReason enum from revm indicating that the EVM has experienced an exceptional halt +/// This causes execution to immediately end with all gas being consumed +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum HaltReason { + /// Out of gas error with specific variant + OutOfGas(OutOfGasError), + /// Opcode not found error + OpcodeNotFound, + /// Invalid FE opcode error + InvalidFEOpcode, + /// Invalid jump destination + InvalidJump, + /// The feature or opcode is not activated in hardfork + NotActivated, + /// Attempting to pop a value from an empty stack + StackUnderflow, + /// Attempting to push a value onto a full stack + StackOverflow, + /// Invalid memory or storage offset + OutOfOffset, + /// Address collision during contract creation + CreateCollision, + /// Error in precompile execution + PrecompileError, + /// Nonce overflow error + NonceOverflow, + /// Contract size limit exceeded during creation + CreateContractSizeLimit, + /// Contract creation starting with EF prefix + CreateContractStartingWithEF, + /// Init code size limit exceeded + CreateInitCodeSizeLimit, + /// Payment overflow error + OverflowPayment, + /// State change attempted during static call + StateChangeDuringStaticCall, + /// Call not allowed inside static context + CallNotAllowedInsideStatic, + /// Insufficient funds for operation + OutOfFunds, + /// Maximum call depth exceeded + CallTooDeep, +} + +impl HaltReason { + /// Returns `true` if this is an out of gas error + pub const fn is_out_of_gas(&self) -> bool { + matches!(self, Self::OutOfGas(_)) + } + + /// Returns the inner OutOfGasError if this is an OutOfGas variant + pub const fn as_out_of_gas(&self) -> Option { + match self { + Self::OutOfGas(err) => Some(*err), + _ => None, + } + } +} + +/// Convert from Error to HaltReason +/// This reverses the mapping logic from the commented `instruction_result_into_exec_error` function +impl From> for HaltReason { + fn from(error: Error) -> Self { + match error { + Error::OutOfGas => Self::OutOfGas(OutOfGasError::Basic), + Error::StaticMemoryTooLarge => Self::OutOfGas(OutOfGasError::MemoryLimit), + Error::InvalidInstruction => Self::OpcodeNotFound, + Error::StateChangeDenied => Self::StateChangeDuringStaticCall, + Error::ContractTrapped => Self::PrecompileError, + Error::OutOfBounds => Self::OutOfOffset, + Error::DuplicateContract => Self::CreateCollision, + Error::BalanceConversionFailed => Self::OverflowPayment, + Error::MaxCallDepthReached => Self::CallTooDeep, + Error::TransferFailed => Self::OutOfFunds, + // Map other common error cases + Error::CodeRejected => Self::PrecompileError, + Error::ValueTooLarge => Self::OverflowPayment, + Error::StorageDepositLimitExhausted => Self::OutOfGas(OutOfGasError::Basic), + Error::ExecutionFailed => Self::PrecompileError, + Error::InvalidCallFlags => Self::InvalidFEOpcode, + Error::DecodingFailed => Self::PrecompileError, + Error::InvalidSyscall => Self::OpcodeNotFound, + // Map specific error cases that weren't in the original commented logic + Error::ContractNotFound => Self::PrecompileError, + Error::CodeNotFound => Self::PrecompileError, + Error::CodeInfoNotFound => Self::PrecompileError, + Error::TerminatedWhileReentrant => Self::PrecompileError, + Error::InputForwarded => Self::PrecompileError, + Error::TooManyTopics => Self::PrecompileError, + Error::TerminatedInConstructor => Self::PrecompileError, + Error::ReentranceDenied => Self::PrecompileError, + Error::ReenteredPallet => Self::PrecompileError, + Error::StorageDepositNotEnoughFunds => Self::OutOfFunds, + Error::CodeInUse => Self::PrecompileError, + Error::ContractReverted => Self::PrecompileError, + Error::BlobTooLarge => Self::CreateContractSizeLimit, + Error::BasicBlockTooLarge => Self::PrecompileError, + Error::MaxDelegateDependenciesReached => Self::PrecompileError, + Error::DelegateDependencyNotFound => Self::PrecompileError, + Error::DelegateDependencyAlreadyExists => Self::CreateCollision, + Error::CannotAddSelfAsDelegateDependency => Self::PrecompileError, + Error::OutOfTransientStorage => Self::OutOfGas(OutOfGasError::Memory), + Error::InvalidStorageFlags => Self::InvalidFEOpcode, + // Default fallback for any unhandled errors - use PrecompileError as a general catch-all + _ => Self::PrecompileError, + } + } +} + +/// Convert from HaltReason to DispatchError +/// This implements the mapping logic from the commented `instruction_result_into_exec_error` function +impl From for DispatchError { + fn from(halt_reason: HaltReason) -> Self { + match halt_reason { + HaltReason::OutOfGas(OutOfGasError::Basic) | + HaltReason::OutOfGas(OutOfGasError::InvalidOperand) | + HaltReason::OutOfGas(OutOfGasError::ReentrancySentry) | + HaltReason::OutOfGas(OutOfGasError::Precompile) | + HaltReason::OutOfGas(OutOfGasError::Memory) => DispatchError::Other("OutOfGas"), + HaltReason::OutOfGas(OutOfGasError::MemoryLimit) => DispatchError::Other("StaticMemoryTooLarge"), + HaltReason::OpcodeNotFound | + HaltReason::InvalidJump | + HaltReason::NotActivated | + HaltReason::InvalidFEOpcode | + HaltReason::CreateContractStartingWithEF => DispatchError::Other("InvalidInstruction"), + HaltReason::CallNotAllowedInsideStatic | + HaltReason::StateChangeDuringStaticCall => DispatchError::Other("StateChangeDenied"), + HaltReason::StackUnderflow | + HaltReason::StackOverflow | + HaltReason::NonceOverflow | + HaltReason::PrecompileError => DispatchError::Other("ContractTrapped"), + HaltReason::OutOfOffset => DispatchError::Other("OutOfBounds"), + HaltReason::CreateCollision => DispatchError::Other("DuplicateContract"), + HaltReason::OverflowPayment => DispatchError::Other("BalanceConversionFailed"), + HaltReason::CreateContractSizeLimit | + HaltReason::CreateInitCodeSizeLimit => DispatchError::Other("StaticMemoryTooLarge"), + HaltReason::CallTooDeep => DispatchError::Other("MaxCallDepthReached"), + HaltReason::OutOfFunds => DispatchError::Other("TransferFailed"), + } + } +} + /// EVM interpreter state using sp_core types #[derive(Debug)] pub struct Interpreter<'a, E: Ext> { + pub ext: &'a mut E, /// The bytecode being executed pub bytecode: ExtBytecode, + /// Input data for the current call + pub input: Vec, /// The execution stack pub stack: Stack, /// Return data from the last call pub return_data: Vec, /// EVM memory pub memory: Memory, - /// Input data for the current call - pub input: Vec, - /// Phantom data for the Ext type - pub _phantom: PhantomData<&'a E>, } impl<'a, E: Ext> Interpreter<'a, E> { /// Create a new interpreter instance - pub fn new(bytecode: ExtBytecode, input: Vec) -> Self { + pub fn new(bytecode: ExtBytecode, input: Vec, ext: &'a mut E) -> Self { Self { + ext, bytecode, + input, stack: Stack::new(), return_data: Vec::new(), memory: Memory::new(), - input, - _phantom: Default::default(), } } } + +pub type InstructionTable = [fn(Interpreter<'_, E>); 256]; diff --git a/substrate/frame/revive/src/vm/evm/stack.rs b/substrate/frame/revive/src/vm/evm/stack.rs index ef75d10d68db1..d677f503c0d65 100644 --- a/substrate/frame/revive/src/vm/evm/stack.rs +++ b/substrate/frame/revive/src/vm/evm/stack.rs @@ -17,6 +17,7 @@ //! Custom EVM stack implementation using sp_core::U256 +use crate::vm::evm::interpreter::HaltReason; use alloc::vec::Vec; use sp_core::U256; @@ -25,232 +26,235 @@ use sp_core::U256; pub struct Stack(Vec); impl Stack { - /// Create a new empty stack - pub fn new() -> Self { - Self(Vec::with_capacity(32)) - } - - /// Push a value onto the stack - /// Returns true if successful, false if stack would overflow - pub fn push(&mut self, value: U256) -> bool { - if self.0.len() >= 1024 { - false - } else { - self.0.push(value); - true - } - } - - /// Pop a value from the stack - /// Returns Some(value) if successful, None if stack is empty - pub fn pop(&mut self) -> Option { - self.0.pop() - } - - /// Get a reference to the top stack item without removing it - pub fn top(&self) -> Option<&U256> { - self.0.last() - } - - /// Get the current stack size - pub fn len(&self) -> usize { - self.0.len() - } - - /// Check if stack is empty - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } - - /// Pop multiple values from the stack - /// Returns Some(array) if successful, None if not enough values on stack - pub fn popn(&mut self) -> Option<[U256; N]> { - if self.0.len() < N { - return None; - } - - let mut result: [U256; N] = [U256::zero(); N]; - for i in 0..N { - result[i] = self.0.pop()?; - } - Some(result) - } - - /// Pop multiple values and return them along with a mutable reference to the new top - /// This is used for operations that pop some values and modify the top of the stack - pub fn popn_top(&mut self) -> Option<([U256; N], &mut U256)> { - if self.0.len() < N + 1 { - return None; - } - - let mut popped: [U256; N] = [U256::zero(); N]; - for i in 0..N { - popped[i] = self.0.pop()?; - } - - // Get mutable reference to the new top - let top = self.0.last_mut()?; - Some((popped, top)) - } - - /// Duplicate the Nth item from the top and push it onto the stack - /// Returns true if successful, false if stack would overflow or index is invalid - pub fn dup(&mut self, n: usize) -> bool { - if n == 0 || n > self.0.len() || self.0.len() >= 1024 { - return false; - } - - let idx = self.0.len() - n; - let value = self.0[idx]; - self.0.push(value); - true - } - - /// Swap the top stack item with the Nth item from the top - /// Returns true if successful, false if indices are invalid - pub fn exchange(&mut self, i: usize, j: usize) -> bool { - let len = self.0.len(); - if i >= len || j >= len { - return false; - } - - let i_idx = len - 1 - i; - let j_idx = len - 1 - j; - self.0.swap(i_idx, j_idx); - true - } + /// Create a new empty stack + pub fn new() -> Self { + Self(Vec::with_capacity(32)) + } + + /// Push a value onto the stack + /// Returns Ok(()) if successful, Err(HaltReason::StackOverflow) if stack would overflow + pub fn push(&mut self, value: U256) -> Result<(), HaltReason> { + if self.0.len() >= 1024 { + Err(HaltReason::StackOverflow) + } else { + self.0.push(value); + Ok(()) + } + } + + /// Pop a value from the stack + /// Returns Ok(value) if successful, Err(HaltReason::StackUnderflow) if stack is empty + pub fn pop(&mut self) -> Result { + self.0.pop().ok_or(HaltReason::StackUnderflow) + } + + /// Get a reference to the top stack item without removing it + pub fn top(&self) -> Option<&U256> { + self.0.last() + } + + /// Get the current stack size + pub fn len(&self) -> usize { + self.0.len() + } + + /// Check if stack is empty + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + /// Pop multiple values from the stack + /// Returns Ok(array) if successful, Err(HaltReason::StackUnderflow) if not enough values on + /// stack + pub fn popn(&mut self) -> Result<[U256; N], HaltReason> { + if self.0.len() < N { + return Err(HaltReason::StackUnderflow); + } + + let mut result: [U256; N] = [U256::zero(); N]; + for i in 0..N { + result[i] = self.0.pop().ok_or(HaltReason::StackUnderflow)?; + } + Ok(result) + } + + /// Pop multiple values and return them along with a mutable reference to the new top + /// This is used for operations that pop some values and modify the top of the stack + pub fn popn_top(&mut self) -> Result<([U256; N], &mut U256), HaltReason> { + if self.0.len() < N + 1 { + return Err(HaltReason::StackUnderflow); + } + + let mut popped: [U256; N] = [U256::zero(); N]; + for i in 0..N { + popped[i] = self.0.pop().ok_or(HaltReason::StackUnderflow)?; + } + + // Get mutable reference to the new top + let top = self.0.last_mut().ok_or(HaltReason::StackUnderflow)?; + Ok((popped, top)) + } + + /// Duplicate the Nth item from the top and push it onto the stack + /// Returns Ok(()) if successful, Err(HaltReason) if stack would overflow or index is invalid + pub fn dup(&mut self, n: usize) -> Result<(), HaltReason> { + if n == 0 || n > self.0.len() { + return Err(HaltReason::StackUnderflow); + } + if self.0.len() >= 1024 { + return Err(HaltReason::StackOverflow); + } + + let idx = self.0.len() - n; + let value = self.0[idx]; + self.0.push(value); + Ok(()) + } + + /// Swap the top stack item with the Nth item from the top + /// Returns Ok(()) if successful, Err(HaltReason::StackUnderflow) if indices are invalid + pub fn exchange(&mut self, i: usize, j: usize) -> Result<(), HaltReason> { + let len = self.0.len(); + if i >= len || j >= len { + return Err(HaltReason::StackUnderflow); + } + + let i_idx = len - 1 - i; + let j_idx = len - 1 - j; + self.0.swap(i_idx, j_idx); + Ok(()) + } } impl Default for Stack { - fn default() -> Self { - Self::new() - } + fn default() -> Self { + Self::new() + } } #[cfg(test)] mod tests { - use super::*; - - #[test] - fn test_push_pop() { - let mut stack = Stack::new(); - - // Test push - assert!(stack.push(U256::from(42))); - assert_eq!(stack.len(), 1); - - // Test pop - assert_eq!(stack.pop(), Some(U256::from(42))); - assert_eq!(stack.len(), 0); - assert_eq!(stack.pop(), None); - } - - #[test] - fn test_popn() { - let mut stack = Stack::new(); - - // Push some values - stack.push(U256::from(1)); - stack.push(U256::from(2)); - stack.push(U256::from(3)); - - // Pop multiple values - let result: Option<[U256; 2]> = stack.popn(); - assert_eq!(result, Some([U256::from(3), U256::from(2)])); - assert_eq!(stack.len(), 1); - - // Try to pop more than available - let result: Option<[U256; 2]> = stack.popn(); - assert_eq!(result, None); - } - - #[test] - fn test_popn_top() { - let mut stack = Stack::new(); - - // Push some values - stack.push(U256::from(1)); - stack.push(U256::from(2)); - stack.push(U256::from(3)); - stack.push(U256::from(4)); - - // Pop 2 values and get mutable reference to new top - let result = stack.popn_top::<2>(); - assert!(result.is_some()); - let (popped, top_ref) = result.unwrap(); - assert_eq!(popped, [U256::from(4), U256::from(3)]); - assert_eq!(*top_ref, U256::from(2)); - - // Modify the top - *top_ref = U256::from(99); - assert_eq!(stack.top(), Some(&U256::from(99))); - } - - #[test] - fn test_dup() { - let mut stack = Stack::new(); - - stack.push(U256::from(1)); - stack.push(U256::from(2)); - - // Duplicate the top item (index 1) - assert!(stack.dup(1)); - assert_eq!(stack.0, vec![U256::from(1), U256::from(2), U256::from(2)]); - - // Duplicate the second item (index 2) - assert!(stack.dup(2)); - assert_eq!(stack.0, vec![U256::from(1), U256::from(2), U256::from(2), U256::from(2)]); - } - - #[test] - fn test_exchange() { - let mut stack = Stack::new(); - - stack.push(U256::from(1)); - stack.push(U256::from(2)); - stack.push(U256::from(3)); - - // Swap top (index 0) with second (index 1) - assert!(stack.exchange(0, 1)); - assert_eq!(stack.0, vec![U256::from(1), U256::from(3), U256::from(2)]); - } - - #[test] - fn test_stack_limit() { - let mut stack = Stack::new(); - - // Fill stack to limit - for i in 0..1024 { - assert!(stack.push(U256::from(i))); - } - - // Should fail to push one more - assert!(!stack.push(U256::from(9999))); - assert_eq!(stack.len(), 1024); - } - - #[test] - fn test_top() { - let mut stack = Stack::new(); - assert_eq!(stack.top(), None); - - stack.push(U256::from(42)); - assert_eq!(stack.top(), Some(&U256::from(42))); - - stack.push(U256::from(100)); - assert_eq!(stack.top(), Some(&U256::from(100))); - } - - #[test] - fn test_is_empty() { - let mut stack = Stack::new(); - assert!(stack.is_empty()); - - stack.push(U256::from(1)); - assert!(!stack.is_empty()); - - stack.pop(); - assert!(stack.is_empty()); - } + use super::*; + + #[test] + fn test_push_pop() { + let mut stack = Stack::new(); + + // Test push + assert!(stack.push(U256::from(42)).is_ok()); + assert_eq!(stack.len(), 1); + + // Test pop + assert_eq!(stack.pop(), Ok(U256::from(42))); + assert_eq!(stack.len(), 0); + assert_eq!(stack.pop(), Err(HaltReason::StackUnderflow)); + } + + #[test] + fn test_popn() { + let mut stack = Stack::new(); + + // Push some values + stack.push(U256::from(1)); + stack.push(U256::from(2)); + stack.push(U256::from(3)); + + // Pop multiple values + let result: Result<[U256; 2], _> = stack.popn(); + assert_eq!(result, Ok([U256::from(3), U256::from(2)])); + assert_eq!(stack.len(), 1); + + // Try to pop more than available + let result: Result<[U256; 2], _> = stack.popn(); + assert_eq!(result, Err(HaltReason::StackUnderflow)); + } + + #[test] + fn test_popn_top() { + let mut stack = Stack::new(); + + // Push some values + stack.push(U256::from(1)); + stack.push(U256::from(2)); + stack.push(U256::from(3)); + stack.push(U256::from(4)); + + // Pop 2 values and get mutable reference to new top + let result = stack.popn_top::<2>(); + assert!(result.is_ok()); + let (popped, top_ref) = result.unwrap(); + assert_eq!(popped, [U256::from(4), U256::from(3)]); + assert_eq!(*top_ref, U256::from(2)); + + // Modify the top + *top_ref = U256::from(99); + assert_eq!(stack.top(), Some(&U256::from(99))); + } + + #[test] + fn test_dup() { + let mut stack = Stack::new(); + + stack.push(U256::from(1)).unwrap(); + stack.push(U256::from(2)).unwrap(); + + // Duplicate the top item (index 1) + assert!(stack.dup(1).is_ok()); + assert_eq!(stack.0, vec![U256::from(1), U256::from(2), U256::from(2)]); + + // Duplicate the second item (index 2) + assert!(stack.dup(2).is_ok()); + assert_eq!(stack.0, vec![U256::from(1), U256::from(2), U256::from(2), U256::from(2)]); + } + + #[test] + fn test_exchange() { + let mut stack = Stack::new(); + + stack.push(U256::from(1)).unwrap(); + stack.push(U256::from(2)).unwrap(); + stack.push(U256::from(3)).unwrap(); + + // Swap top (index 0) with second (index 1) + assert!(stack.exchange(0, 1).is_ok()); + assert_eq!(stack.0, vec![U256::from(1), U256::from(3), U256::from(2)]); + } + + #[test] + fn test_stack_limit() { + let mut stack = Stack::new(); + + // Fill stack to limit + for i in 0..1024 { + assert!(stack.push(U256::from(i)).is_ok()); + } + + // Should fail to push one more + assert_eq!(stack.push(U256::from(9999)), Err(HaltReason::StackOverflow)); + assert_eq!(stack.len(), 1024); + } + + #[test] + fn test_top() { + let mut stack = Stack::new(); + assert_eq!(stack.top(), None); + + stack.push(U256::from(42)).unwrap(); + assert_eq!(stack.top(), Some(&U256::from(42))); + + stack.push(U256::from(100)).unwrap(); + assert_eq!(stack.top(), Some(&U256::from(100))); + } + + #[test] + fn test_is_empty() { + let mut stack = Stack::new(); + assert!(stack.is_empty()); + + stack.push(U256::from(1)).unwrap(); + assert!(!stack.is_empty()); + + stack.pop().unwrap(); + assert!(stack.is_empty()); + } } - diff --git a/substrate/frame/revive/src/vm/mod.rs b/substrate/frame/revive/src/vm/mod.rs index dc01b7879ec3e..0112929e50729 100644 --- a/substrate/frame/revive/src/vm/mod.rs +++ b/substrate/frame/revive/src/vm/mod.rs @@ -347,11 +347,12 @@ where self.prepare_call(pvm::Runtime::new(ext, input_data), function, 0)?; prepared_call.call() } else if T::AllowEVMBytecode::get() { - use crate::vm::evm::EVMInputs; - use revm::bytecode::Bytecode; - let inputs = EVMInputs::new(input_data); - let bytecode = Bytecode::new_raw(self.code.into()); - evm::call(bytecode, ext, inputs) + todo!() + // use crate::vm::evm::EVMInputs; + // use revm::bytecode::Bytecode; + // let inputs = EVMInputs::new(input_data); + // let bytecode = Bytecode::new_raw(self.code.into()); + // evm::call(bytecode, ext, inputs) } else { Err(Error::::CodeRejected.into()) } From cb9f79eea8a43461861c6f10b4bdcac3112f308b Mon Sep 17 00:00:00 2001 From: pgherveou Date: Thu, 18 Sep 2025 06:18:50 +0200 Subject: [PATCH 24/76] wip --- Cargo.lock | 1 + substrate/frame/revive/Cargo.toml | 3 + substrate/frame/revive/src/tests.rs | 2 +- .../src/vm/evm/instructions/arithmetic.rs | 144 +-- .../revive/src/vm/evm/instructions/bits.rs | 107 ++ .../revive/src/vm/evm/instructions/bitwise.rs | 939 +++++++++--------- .../revive/src/vm/evm/instructions/macros.rs | 2 +- .../revive/src/vm/evm/instructions/mod.rs | 10 +- .../revive/src/vm/evm/instructions/modular.rs | 169 ++++ 9 files changed, 847 insertions(+), 530 deletions(-) create mode 100644 substrate/frame/revive/src/vm/evm/instructions/bits.rs create mode 100644 substrate/frame/revive/src/vm/evm/instructions/modular.rs diff --git a/Cargo.lock b/Cargo.lock index 687935c21fd6d..9fd4d67dd7f4f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13048,6 +13048,7 @@ dependencies = [ "polkavm 0.27.0", "polkavm-common 0.27.0", "pretty_assertions", + "proptest", "rand 0.8.5", "rand_pcg", "revm", diff --git a/substrate/frame/revive/Cargo.toml b/substrate/frame/revive/Cargo.toml index 6a2078dd50c43..88762a7efa2f3 100644 --- a/substrate/frame/revive/Cargo.toml +++ b/substrate/frame/revive/Cargo.toml @@ -76,6 +76,9 @@ pallet-utility = { workspace = true, default-features = true } sp-keystore = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } + +proptest = "1" + [features] default = ["std"] std = [ diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index 9dda6f55cadb9..aa36aa102f1ff 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -18,7 +18,7 @@ mod pallet_dummy; mod precompiles; mod pvm; -mod sol; +// mod sol; use crate::{ self as pallet_revive, diff --git a/substrate/frame/revive/src/vm/evm/instructions/arithmetic.rs b/substrate/frame/revive/src/vm/evm/instructions/arithmetic.rs index 77859e680420a..7162ed1e84804 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/arithmetic.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/arithmetic.rs @@ -15,9 +15,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -use super::i256::{i256_div, i256_mod}; +use super::{ + i256::{i256_div, i256_mod}, + modular::Modular, +}; use crate::{ - vm::{evm::Interpreter, Ext}, + vm::{ + evm::{interpreter::HaltReason, Interpreter}, + Ext, + }, U256, }; use revm::interpreter::gas::{EXP, LOW, MID, VERYLOW}; @@ -109,75 +115,89 @@ pub fn mulmod<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchR } /// Implements the EXP instruction - exponentiates two values from stack. -// pub fn exp<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { -// let ([op1], op2) = interpreter.stack.popn_top()?; -// // Calculate gas cost based on exponent size - use approximate cost calculation -// // since we don't have access to SpecId here -// interpreter -// .ext -// .gas_meter_mut() -// .charge_evm_gas(revm_gas::exp_cost(spec_id, *op2))?; -// *op2 = op1.pow(*op2); -// Ok(()) -// } -// /// Implements the `SIGNEXTEND` opcode as defined in the Ethereum Yellow Paper. -// /// -// /// In the yellow paper `SIGNEXTEND` is defined to take two inputs, we will call them -// /// `x` and `y`, and produce one output. -// /// -// /// The first `t` bits of the output (numbering from the left, starting from 0) are -// /// equal to the `t`-th bit of `y`, where `t` is equal to `256 - 8(x + 1)`. -// /// -// /// The remaining bits of the output are equal to the corresponding bits of `y`. -// /// -// /// **Note**: If `x >= 32` then the output is equal to `y` since `t <= 0`. -// /// -// /// To efficiently implement this algorithm in the case `x < 32` we do the following. -// /// -// /// Let `b` be equal to the `t`-th bit of `y` and let `s = 255 - t = 8x + 7` -// /// (this is effectively the same index as `t`, but numbering the bits from the -// /// right instead of the left). -// /// -// /// We can create a bit mask which is all zeros up to and including the `t`-th bit, -// /// and all ones afterwards by computing the quantity `2^s - 1`. -// /// -// /// We can use this mask to compute the output depending on the value of `b`. -// /// -// /// If `b == 1` then the yellow paper says the output should be all ones up to -// /// and including the `t`-th bit, followed by the remaining bits of `y`; this is equal to -// /// `y | !mask` where `|` is the bitwise `OR` and `!` is bitwise negation. -// /// -// /// Similarly, if `b == 0` then the yellow paper says the output should start with all zeros, -// /// then end with bits from `b`; this is equal to `y & mask` where `&` is bitwise `AND`. -// pub fn signextend<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { -// interpreter.ext.gas_meter_mut().charge_evm_gas(LOW)?; -// let ([ext], x) = interpreter.stack.popn_top()?; -// // For 31 we also don't need to do anything. -// if ext < U256::from(31) { -// let ext_low = ext.low_u64() as usize; -// if ext_low < 32 { -// let bit_index = 8 * ext_low + 7; -// if bit_index < 256 { -// let bit = x.bit(bit_index); -// let mask = (U256::from(1) << bit_index) - U256::from(1); -// *x = if bit { *x | !mask } else { *x & mask }; -// } -// } -// } -// Ok(()) -// } +pub fn exp<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { + let ([op1], op2) = interpreter.stack.popn_top()?; + let gas_cost = exp_cost(*op2).ok_or_else(|| crate::Error::::OutOfGas)?; + interpreter.ext.gas_meter_mut().charge_evm_gas(gas_cost)?; + *op2 = op1.pow(*op2); + Ok(()) +} + +/// Implements the `SIGNEXTEND` opcode as defined in the Ethereum Yellow Paper. +/// +/// In the yellow paper `SIGNEXTEND` is defined to take two inputs, we will call them +/// `x` and `y`, and produce one output. +/// +/// The first `t` bits of the output (numbering from the left, starting from 0) are +/// equal to the `t`-th bit of `y`, where `t` is equal to `256 - 8(x + 1)`. +/// +/// The remaining bits of the output are equal to the corresponding bits of `y`. +/// +/// **Note**: If `x >= 32` then the output is equal to `y` since `t <= 0`. +/// +/// To efficiently implement this algorithm in the case `x < 32` we do the following. +/// +/// Let `b` be equal to the `t`-th bit of `y` and let `s = 255 - t = 8x + 7` +/// (this is effectively the same index as `t`, but numbering the bits from the +/// right instead of the left). +/// +/// We can create a bit mask which is all zeros up to and including the `t`-th bit, +/// and all ones afterwards by computing the quantity `2^s - 1`. +/// +/// We can use this mask to compute the output depending on the value of `b`. +/// +/// If `b == 1` then the yellow paper says the output should be all ones up to +/// and including the `t`-th bit, followed by the remaining bits of `y`; this is equal to +/// `y | !mask` where `|` is the bitwise `OR` and `!` is bitwise negation. +/// +/// Similarly, if `b == 0` then the yellow paper says the output should start with all zeros, +/// then end with bits from `b`; this is equal to `y & mask` where `&` is bitwise `AND`. +pub fn signextend<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { + interpreter.ext.gas_meter_mut().charge_evm_gas(LOW)?; + let ([ext], x) = interpreter.stack.popn_top()?; + // For 31 we also don't need to do anything. + if ext < U256::from(31) { + let ext = &ext.0[0]; + let bit_index = (8 * ext + 7) as usize; + let bit = x.bit(bit_index); + let mask = (U256::from(1) << bit_index) - U256::from(1); + *x = if bit { *x | !mask } else { *x & mask }; + } + Ok(()) +} /// `EXP` opcode cost calculation. -#[inline] -pub fn exp_cost(power: U256) -> Option { +fn exp_cost(power: U256) -> Option { if power.is_zero() { Some(EXP) } else { // EIP-160: EXP cost increase - let gas_byte = 50; + let gas_byte = U256::from(50); let gas = U256::from(EXP) .checked_add(gas_byte.checked_mul(U256::from(log2floor(power) / 8 + 1))?)?; u64::try_from(gas).ok() } } + +const fn log2floor(value: U256) -> u64 { + let mut l: u64 = 256; + let mut i = 3; + loop { + if value.0[i] == 0u64 { + l -= 64; + } else { + l -= value.0[i].leading_zeros() as u64; + if l == 0 { + return l; + } else { + return l - 1; + } + } + if i == 0 { + break; + } + i -= 1; + } + l +} diff --git a/substrate/frame/revive/src/vm/evm/instructions/bits.rs b/substrate/frame/revive/src/vm/evm/instructions/bits.rs new file mode 100644 index 0000000000000..3d307c17abc04 --- /dev/null +++ b/substrate/frame/revive/src/vm/evm/instructions/bits.rs @@ -0,0 +1,107 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use sp_core::U256; + +pub trait Bits { + /// Returns whether a specific bit is set. + /// + /// Returns `false` if `index` exceeds the bit width of the number. + #[must_use] + fn bit(&self, index: usize) -> bool; + + /// Arithmetic shift right by `rhs` bits. + #[must_use] + fn arithmetic_shr(self, rhs: usize) -> Self; +} + +impl Bits for U256 { + fn bit(&self, index: usize) -> bool { + const BITS: usize = 256; + if index >= BITS { + return false; + } + let (limbs, bits) = (index / 64, index % 64); + self.0[limbs] & (1 << bits) != 0 + } + + fn arithmetic_shr(self, rhs: usize) -> Self { + const BITS: usize = 256; + if BITS == 0 { + return Self::zero(); + } + let sign = self.bit(BITS - 1); + let mut r = self >> rhs; + if sign { + r |= U256::MAX << BITS.saturating_sub(rhs); + } + r + } +} + +#[cfg(test)] +mod tests { + use super::*; + use core::cmp::min; + use proptest::proptest; + + // #[test] + // fn test_arithmetic_shr() { + // proptest!(|(limbs: [u64; 4], shift in 0..=258)| { + // let value = U256(limbs); + // let shifted = value.arithmetic_shr(shift); + // let sign_bit = value.bit(255); + // if sign_bit { + // // For negative numbers, check that the sign is preserved + // assert_eq!(shifted.leading_ones(), min(256, shift)); + // } else { + // // For positive numbers, should behave like logical shift + // assert_eq!(shifted.leading_zeros(), min(256, value.leading_zeros() + shift)); + // } + // }); + // } + + #[test] + fn test_arithmetic_shr_positive() { + // Test positive number (MSB = 0) + let value = U256::from(0x7FFFFFFFu64); + let result = value.arithmetic_shr(4); + let expected = U256::from(0x07FFFFFFu64); + assert_eq!(result, expected); + } + + #[test] + fn test_arithmetic_shr_negative() { + // Test negative number (MSB = 1) + let value = U256::MAX; // All bits set + let result = value.arithmetic_shr(4); + // Should still be all bits set + assert_eq!(result, U256::MAX); + } + + #[test] + fn test_arithmetic_shr_large_shift() { + // Test shift larger than bit width + let value = U256::MAX; + let result = value.arithmetic_shr(300); + assert_eq!(result, U256::MAX); + + let positive = U256::from(123u64); + let result_pos = positive.arithmetic_shr(300); + assert_eq!(result_pos, U256::zero()); + } +} diff --git a/substrate/frame/revive/src/vm/evm/instructions/bitwise.rs b/substrate/frame/revive/src/vm/evm/instructions/bitwise.rs index 7eea29dca6358..0edcb228840f7 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/bitwise.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/bitwise.rs @@ -15,551 +15,566 @@ // See the License for the specific language governing permissions and // limitations under the License. -use super::{i256::i256_cmp, Context}; -use crate::vm::Ext; -use core::cmp::Ordering; -use revm::{ - interpreter::{gas as revm_gas, interpreter_types::StackTr}, - primitives::U256, +use super::i256::i256_cmp; +use crate::{ + vm::{evm::Interpreter, Ext}, + U256, }; +use core::cmp::Ordering; +use revm::interpreter::gas::VERYLOW; +use sp_runtime::DispatchResult; /// Implements the LT instruction - less than comparison. -pub fn lt<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::VERYLOW); - popn_top!([op1], op2, context.interpreter); - *op2 = U256::from(op1 < *op2); +pub fn lt<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { + interpreter.ext.gas_meter_mut().charge_evm_gas(VERYLOW)?; + let ([op1], op2) = interpreter.stack.popn_top()?; + *op2 = if op1 < *op2 { U256::one() } else { U256::zero() }; + Ok(()) } /// Implements the GT instruction - greater than comparison. -pub fn gt<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::VERYLOW); - popn_top!([op1], op2, context.interpreter); +pub fn gt<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { + interpreter.ext.gas_meter_mut().charge_evm_gas(VERYLOW)?; + let ([op1], op2) = interpreter.stack.popn_top()?; - *op2 = U256::from(op1 > *op2); + *op2 = if op1 > *op2 { U256::one() } else { U256::zero() }; + Ok(()) } /// Implements the CLZ instruction - count leading zeros. -pub fn clz<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::VERYLOW); - popn_top!([], op1, context.interpreter); +pub fn clz<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { + interpreter.ext.gas_meter_mut().charge_evm_gas(VERYLOW)?; + let ([], op1) = interpreter.stack.popn_top()?; let leading_zeros = op1.leading_zeros(); *op1 = U256::from(leading_zeros); + Ok(()) } /// Implements the SLT instruction. /// /// Signed less than comparison of two values from stack. -pub fn slt<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::VERYLOW); - popn_top!([op1], op2, context.interpreter); +pub fn slt<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { + interpreter.ext.gas_meter_mut().charge_evm_gas(VERYLOW)?; + let ([op1], op2) = interpreter.stack.popn_top()?; - *op2 = U256::from(i256_cmp(&op1, op2) == Ordering::Less); + *op2 = if i256_cmp(&op1, op2) == Ordering::Less { U256::one() } else { U256::zero() }; + Ok(()) } /// Implements the SGT instruction. /// /// Signed greater than comparison of two values from stack. -pub fn sgt<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::VERYLOW); - popn_top!([op1], op2, context.interpreter); +pub fn sgt<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { + interpreter.ext.gas_meter_mut().charge_evm_gas(VERYLOW)?; + let ([op1], op2) = interpreter.stack.popn_top()?; - *op2 = U256::from(i256_cmp(&op1, op2) == Ordering::Greater); + *op2 = if i256_cmp(&op1, op2) == Ordering::Greater { U256::one() } else { U256::zero() }; + Ok(()) } /// Implements the EQ instruction. /// /// Equality comparison of two values from stack. -pub fn eq<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::VERYLOW); - popn_top!([op1], op2, context.interpreter); +pub fn eq<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { + interpreter.ext.gas_meter_mut().charge_evm_gas(VERYLOW)?; + let ([op1], op2) = interpreter.stack.popn_top()?; - *op2 = U256::from(op1 == *op2); + *op2 = if op1 == *op2 { U256::one() } else { U256::zero() }; + Ok(()) } /// Implements the ISZERO instruction. /// /// Checks if the top stack value is zero. -pub fn iszero<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::VERYLOW); - popn_top!([], op1, context.interpreter); - *op1 = U256::from(op1.is_zero()); +pub fn iszero<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { + interpreter.ext.gas_meter_mut().charge_evm_gas(VERYLOW)?; + let ([], op1) = interpreter.stack.popn_top()?; + + *op1 = if op1.is_zero() { U256::one() } else { U256::zero() }; + Ok(()) } /// Implements the AND instruction. /// /// Bitwise AND of two values from stack. -pub fn bitand<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::VERYLOW); - popn_top!([op1], op2, context.interpreter); +pub fn bitand<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { + interpreter.ext.gas_meter_mut().charge_evm_gas(VERYLOW)?; + let ([op1], op2) = interpreter.stack.popn_top()?; *op2 = op1 & *op2; + Ok(()) } /// Implements the OR instruction. /// /// Bitwise OR of two values from stack. -pub fn bitor<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::VERYLOW); - popn_top!([op1], op2, context.interpreter); - +pub fn bitor<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { + interpreter.ext.gas_meter_mut().charge_evm_gas(VERYLOW)?; + let ([op1], op2) = interpreter.stack.popn_top()?; *op2 = op1 | *op2; + Ok(()) } /// Implements the XOR instruction. /// /// Bitwise XOR of two values from stack. -pub fn bitxor<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::VERYLOW); - popn_top!([op1], op2, context.interpreter); - +pub fn bitxor<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { + interpreter.ext.gas_meter_mut().charge_evm_gas(VERYLOW)?; + let ([op1], op2) = interpreter.stack.popn_top()?; *op2 = op1 ^ *op2; + Ok(()) } /// Implements the NOT instruction. /// /// Bitwise NOT (negation) of the top stack value. -pub fn not<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::VERYLOW); - popn_top!([], op1, context.interpreter); - +pub fn not<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { + interpreter.ext.gas_meter_mut().charge_evm_gas(VERYLOW)?; + let ([], op1) = interpreter.stack.popn_top()?; *op1 = !*op1; + Ok(()) } /// Implements the BYTE instruction. /// /// Extracts a single byte from a word at a given index. -pub fn byte<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::VERYLOW); - popn_top!([op1], op2, context.interpreter); +pub fn byte<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { + interpreter.ext.gas_meter_mut().charge_evm_gas(VERYLOW)?; + let ([op1], op2) = interpreter.stack.popn_top()?; - let o1 = as_usize_saturated!(op1); + let o1 = op1.as_usize(); *op2 = if o1 < 32 { // `31 - o1` because `byte` returns LE, while we want BE U256::from(op2.byte(31 - o1)) } else { - U256::ZERO + U256::zero() }; + Ok(()) } /// EIP-145: Bitwise shifting instructions in EVM -pub fn shl<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::VERYLOW); - popn_top!([op1], op2, context.interpreter); +pub fn shl<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { + interpreter.ext.gas_meter_mut().charge_evm_gas(VERYLOW)?; + let ([op1], op2) = interpreter.stack.popn_top()?; let shift = as_usize_saturated!(op1); - *op2 = if shift < 256 { *op2 << shift } else { U256::ZERO } + *op2 = if shift < 256 { *op2 << shift } else { U256::zero() }; + Ok(()) } /// EIP-145: Bitwise shifting instructions in EVM -pub fn shr<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::VERYLOW); - popn_top!([op1], op2, context.interpreter); +pub fn shr<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { + interpreter.ext.gas_meter_mut().charge_evm_gas(VERYLOW)?; + let ([op1], op2) = interpreter.stack.popn_top()?; let shift = as_usize_saturated!(op1); - *op2 = if shift < 256 { *op2 >> shift } else { U256::ZERO } + *op2 = if shift < 256 { *op2 >> shift } else { U256::zero() }; + Ok(()) } /// EIP-145: Bitwise shifting instructions in EVM -pub fn sar<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::VERYLOW); - popn_top!([op1], op2, context.interpreter); - - let shift = as_usize_saturated!(op1); - *op2 = if shift < 256 { - op2.arithmetic_shr(shift) - } else if op2.bit(255) { - U256::MAX - } else { - U256::ZERO - }; +pub fn sar<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { + todo!() + // interpreter.ext.gas_meter_mut().charge_evm_gas(VERYLOW)?; + // let ([op1], op2) = interpreter.stack.popn_top()?; + // + // let shift = as_usize_saturated!(op1); + // *op2 = if shift < 256 { + // op2.arithmetic_shr(shift) + // } else if op2.bit(255) { + // U256::MAX + // } else { + // U256::zero() + // }; + // Ok(()) } -#[cfg(test)] -mod tests { - use super::{byte, clz, sar, shl, shr}; - use revm::{ - interpreter::{host::DummyHost, InstructionContext}, - primitives::{hardfork::SpecId, uint, U256}, - }; - - pub fn test_interpreter() -> revm::interpreter::Interpreter< - crate::vm::evm::EVMInterpreter<'static, crate::exec::mock_ext::MockExt>, - > { - use crate::tests::Test; - use revm::{ - interpreter::{ - interpreter::{RuntimeFlags, SharedMemory}, - Interpreter, Stack, - }, - primitives::hardfork::SpecId, - }; - - let mock_ext = Box::leak(Box::new(crate::exec::mock_ext::MockExt::::new())); - - Interpreter { - gas: revm::interpreter::Gas::new(0), - bytecode: Default::default(), - stack: Stack::new(), - return_data: Default::default(), - memory: SharedMemory::new(), - input: crate::vm::evm::EVMInputs::default(), - runtime_flag: RuntimeFlags { is_static: false, spec_id: SpecId::default() }, - extend: mock_ext, - } - } - - #[test] - fn test_shift_left() { - let mut interpreter = test_interpreter(); - - struct TestCase { - value: U256, - shift: U256, - expected: U256, - } - - uint! { - let test_cases = [ - TestCase { - value: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, - shift: 0x00_U256, - expected: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, - }, - TestCase { - value: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, - shift: 0x01_U256, - expected: 0x0000000000000000000000000000000000000000000000000000000000000002_U256, - }, - TestCase { - value: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, - shift: 0xff_U256, - expected: 0x8000000000000000000000000000000000000000000000000000000000000000_U256, - }, - TestCase { - value: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, - shift: 0x0100_U256, - expected: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, - }, - TestCase { - value: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, - shift: 0x0101_U256, - expected: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, - }, - TestCase { - value: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, - shift: 0x00_U256, - expected: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, - }, - TestCase { - value: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, - shift: 0x01_U256, - expected: 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe_U256, - }, - TestCase { - value: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, - shift: 0xff_U256, - expected: 0x8000000000000000000000000000000000000000000000000000000000000000_U256, - }, - TestCase { - value: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, - shift: 0x0100_U256, - expected: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, - }, - TestCase { - value: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, - shift: 0x01_U256, - expected: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, - }, - TestCase { - value: 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, - shift: 0x01_U256, - expected: 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe_U256, - }, - ]; - } - - for test in test_cases { - push!(interpreter, test.value); - push!(interpreter, test.shift); - let context = - InstructionContext { host: &mut DummyHost, interpreter: &mut interpreter }; - shl(context); - let res = interpreter.stack.pop().unwrap(); - assert_eq!(res, test.expected); - } - } - - #[test] - fn test_logical_shift_right() { - let mut interpreter = test_interpreter(); - - struct TestCase { - value: U256, - shift: U256, - expected: U256, - } - - uint! { - let test_cases = [ - TestCase { - value: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, - shift: 0x00_U256, - expected: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, - }, - TestCase { - value: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, - shift: 0x01_U256, - expected: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, - }, - TestCase { - value: 0x8000000000000000000000000000000000000000000000000000000000000000_U256, - shift: 0x01_U256, - expected: 0x4000000000000000000000000000000000000000000000000000000000000000_U256, - }, - TestCase { - value: 0x8000000000000000000000000000000000000000000000000000000000000000_U256, - shift: 0xff_U256, - expected: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, - }, - TestCase { - value: 0x8000000000000000000000000000000000000000000000000000000000000000_U256, - shift: 0x0100_U256, - expected: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, - }, - TestCase { - value: 0x8000000000000000000000000000000000000000000000000000000000000000_U256, - shift: 0x0101_U256, - expected: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, - }, - TestCase { - value: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, - shift: 0x00_U256, - expected: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, - }, - TestCase { - value: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, - shift: 0x01_U256, - expected: 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, - }, - TestCase { - value: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, - shift: 0xff_U256, - expected: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, - }, - TestCase { - value: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, - shift: 0x0100_U256, - expected: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, - }, - TestCase { - value: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, - shift: 0x01_U256, - expected: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, - }, - ]; - } - - for test in test_cases { - push!(interpreter, test.value); - push!(interpreter, test.shift); - let context = - InstructionContext { host: &mut DummyHost, interpreter: &mut interpreter }; - shr(context); - let res = interpreter.stack.pop().unwrap(); - assert_eq!(res, test.expected); - } - } - - #[test] - fn test_arithmetic_shift_right() { - let mut interpreter = test_interpreter(); - - struct TestCase { - value: U256, - shift: U256, - expected: U256, - } - - uint! { - let test_cases = [ - TestCase { - value: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, - shift: 0x00_U256, - expected: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, - }, - TestCase { - value: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, - shift: 0x01_U256, - expected: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, - }, - TestCase { - value: 0x8000000000000000000000000000000000000000000000000000000000000000_U256, - shift: 0x01_U256, - expected: 0xc000000000000000000000000000000000000000000000000000000000000000_U256, - }, - TestCase { - value: 0x8000000000000000000000000000000000000000000000000000000000000000_U256, - shift: 0xff_U256, - expected: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, - }, - TestCase { - value: 0x8000000000000000000000000000000000000000000000000000000000000000_U256, - shift: 0x0100_U256, - expected: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, - }, - TestCase { - value: 0x8000000000000000000000000000000000000000000000000000000000000000_U256, - shift: 0x0101_U256, - expected: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, - }, - TestCase { - value: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, - shift: 0x00_U256, - expected: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, - }, - TestCase { - value: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, - shift: 0x01_U256, - expected: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, - }, - TestCase { - value: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, - shift: 0xff_U256, - expected: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, - }, - TestCase { - value: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, - shift: 0x0100_U256, - expected: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, - }, - TestCase { - value: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, - shift: 0x01_U256, - expected: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, - }, - TestCase { - value: 0x4000000000000000000000000000000000000000000000000000000000000000_U256, - shift: 0xfe_U256, - expected: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, - }, - TestCase { - value: 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, - shift: 0xf8_U256, - expected: 0x000000000000000000000000000000000000000000000000000000000000007f_U256, - }, - TestCase { - value: 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, - shift: 0xfe_U256, - expected: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, - }, - TestCase { - value: 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, - shift: 0xff_U256, - expected: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, - }, - TestCase { - value: 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, - shift: 0x0100_U256, - expected: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, - }, - ]; - } - - for test in test_cases { - push!(interpreter, test.value); - push!(interpreter, test.shift); - let context = - InstructionContext { host: &mut DummyHost, interpreter: &mut interpreter }; - sar(context); - let res = interpreter.stack.pop().unwrap(); - assert_eq!(res, test.expected); - } - } - - #[test] - fn test_byte() { - struct TestCase { - input: U256, - index: usize, - expected: U256, - } - - let mut interpreter = test_interpreter(); - - let input_value = U256::from(0x1234567890abcdef1234567890abcdef_u128); - let test_cases = (0..32) - .map(|i| { - let byte_pos = 31 - i; - - let shift_amount = U256::from(byte_pos * 8); - let byte_value = (input_value >> shift_amount) & U256::from(0xFF); - TestCase { input: input_value, index: i, expected: byte_value } - }) - .collect::>(); - - for test in test_cases.iter() { - push!(interpreter, test.input); - push!(interpreter, U256::from(test.index)); - let context = - InstructionContext { host: &mut DummyHost, interpreter: &mut interpreter }; - byte(context); - let res = interpreter.stack.pop().unwrap(); - assert_eq!(res, test.expected, "Failed at index: {}", test.index); - } - } - - #[test] - fn test_clz() { - let mut interpreter = test_interpreter(); - interpreter.runtime_flag.spec_id = SpecId::OSAKA; - - struct TestCase { - value: U256, - expected: U256, - } - - uint! { - let test_cases = [ - TestCase { value: 0x0_U256, expected: 256_U256 }, - TestCase { value: 0x1_U256, expected: 255_U256 }, - TestCase { value: 0x2_U256, expected: 254_U256 }, - TestCase { value: 0x3_U256, expected: 254_U256 }, - TestCase { value: 0x4_U256, expected: 253_U256 }, - TestCase { value: 0x7_U256, expected: 253_U256 }, - TestCase { value: 0x8_U256, expected: 252_U256 }, - TestCase { value: 0xff_U256, expected: 248_U256 }, - TestCase { value: 0x100_U256, expected: 247_U256 }, - TestCase { value: 0xffff_U256, expected: 240_U256 }, - TestCase { - value: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, // U256::MAX - expected: 0_U256, - }, - TestCase { - value: 0x8000000000000000000000000000000000000000000000000000000000000000_U256, // 1 << 255 - expected: 0_U256, - }, - TestCase { // Smallest value with 1 leading zero - value: 0x4000000000000000000000000000000000000000000000000000000000000000_U256, // 1 << 254 - expected: 1_U256, - }, - TestCase { // Value just below 1 << 255 - value: 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, - expected: 1_U256, - }, - ]; - } - - for test in test_cases { - push!(interpreter, test.value); - let context = - InstructionContext { host: &mut DummyHost, interpreter: &mut interpreter }; - clz(context); - let res = interpreter.stack.pop().unwrap(); - assert_eq!( - res, test.expected, - "CLZ for value {:#x} failed. Expected: {}, Got: {}", - test.value, test.expected, res - ); - } - } -} +// #[cfg(test)] +// mod tests { +// use super::{byte, clz, sar, shl, shr}; +// use revm::{ +// interpreter::{host::DummyHost, InstructionContext}, +// primitives::{hardfork::SpecId, uint, U256}, +// }; +// +// pub fn test_interpreter() -> revm::interpreter::Interpreter< +// crate::vm::evm::EVMInterpreter<'static, crate::exec::mock_ext::MockExt>, +// > { +// use crate::tests::Test; +// use revm::{ +// interpreter::{ +// interpreter::{RuntimeFlags, SharedMemory}, +// Interpreter, Stack, +// }, +// primitives::hardfork::SpecId, +// }; +// +// let mock_ext = Box::leak(Box::new(crate::exec::mock_ext::MockExt::::new())); +// +// Interpreter { +// gas: revm::interpreter::Gas::new(0), +// bytecode: Default::default(), +// stack: Stack::new(), +// return_data: Default::default(), +// memory: SharedMemory::new(), +// input: crate::vm::evm::EVMInputs::default(), +// runtime_flag: RuntimeFlags { is_static: false, spec_id: SpecId::default() }, +// extend: mock_ext, +// } +// } +// +// #[test] +// fn test_shift_left() { +// let mut interpreter = test_interpreter(); +// +// struct TestCase { +// value: U256, +// shift: U256, +// expected: U256, +// } +// +// uint! { +// let test_cases = [ +// TestCase { +// value: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, +// shift: 0x00_U256, +// expected: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, +// }, +// TestCase { +// value: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, +// shift: 0x01_U256, +// expected: 0x0000000000000000000000000000000000000000000000000000000000000002_U256, +// }, +// TestCase { +// value: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, +// shift: 0xff_U256, +// expected: 0x8000000000000000000000000000000000000000000000000000000000000000_U256, +// }, +// TestCase { +// value: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, +// shift: 0x0100_U256, +// expected: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, +// }, +// TestCase { +// value: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, +// shift: 0x0101_U256, +// expected: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, +// }, +// TestCase { +// value: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, +// shift: 0x00_U256, +// expected: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, +// }, +// TestCase { +// value: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, +// shift: 0x01_U256, +// expected: 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe_U256, +// }, +// TestCase { +// value: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, +// shift: 0xff_U256, +// expected: 0x8000000000000000000000000000000000000000000000000000000000000000_U256, +// }, +// TestCase { +// value: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, +// shift: 0x0100_U256, +// expected: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, +// }, +// TestCase { +// value: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, +// shift: 0x01_U256, +// expected: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, +// }, +// TestCase { +// value: 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, +// shift: 0x01_U256, +// expected: 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe_U256, +// }, +// ]; +// } +// +// for test in test_cases { +// push!(interpreter, test.value); +// push!(interpreter, test.shift); +// let context = +// InstructionContext { host: &mut DummyHost, interpreter: &mut interpreter }; +// shl(context); +// let res = interpreter.stack.pop().unwrap(); +// assert_eq!(res, test.expected); +// } +// } +// +// #[test] +// fn test_logical_shift_right() { +// let mut interpreter = test_interpreter(); +// +// struct TestCase { +// value: U256, +// shift: U256, +// expected: U256, +// } +// +// uint! { +// let test_cases = [ +// TestCase { +// value: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, +// shift: 0x00_U256, +// expected: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, +// }, +// TestCase { +// value: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, +// shift: 0x01_U256, +// expected: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, +// }, +// TestCase { +// value: 0x8000000000000000000000000000000000000000000000000000000000000000_U256, +// shift: 0x01_U256, +// expected: 0x4000000000000000000000000000000000000000000000000000000000000000_U256, +// }, +// TestCase { +// value: 0x8000000000000000000000000000000000000000000000000000000000000000_U256, +// shift: 0xff_U256, +// expected: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, +// }, +// TestCase { +// value: 0x8000000000000000000000000000000000000000000000000000000000000000_U256, +// shift: 0x0100_U256, +// expected: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, +// }, +// TestCase { +// value: 0x8000000000000000000000000000000000000000000000000000000000000000_U256, +// shift: 0x0101_U256, +// expected: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, +// }, +// TestCase { +// value: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, +// shift: 0x00_U256, +// expected: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, +// }, +// TestCase { +// value: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, +// shift: 0x01_U256, +// expected: 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, +// }, +// TestCase { +// value: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, +// shift: 0xff_U256, +// expected: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, +// }, +// TestCase { +// value: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, +// shift: 0x0100_U256, +// expected: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, +// }, +// TestCase { +// value: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, +// shift: 0x01_U256, +// expected: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, +// }, +// ]; +// } +// +// for test in test_cases { +// push!(interpreter, test.value); +// push!(interpreter, test.shift); +// let context = +// InstructionContext { host: &mut DummyHost, interpreter: &mut interpreter }; +// shr(context); +// let res = interpreter.stack.pop().unwrap(); +// assert_eq!(res, test.expected); +// } +// } +// +// #[test] +// fn test_arithmetic_shift_right() { +// let mut interpreter = test_interpreter(); +// +// struct TestCase { +// value: U256, +// shift: U256, +// expected: U256, +// } +// +// uint! { +// let test_cases = [ +// TestCase { +// value: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, +// shift: 0x00_U256, +// expected: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, +// }, +// TestCase { +// value: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, +// shift: 0x01_U256, +// expected: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, +// }, +// TestCase { +// value: 0x8000000000000000000000000000000000000000000000000000000000000000_U256, +// shift: 0x01_U256, +// expected: 0xc000000000000000000000000000000000000000000000000000000000000000_U256, +// }, +// TestCase { +// value: 0x8000000000000000000000000000000000000000000000000000000000000000_U256, +// shift: 0xff_U256, +// expected: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, +// }, +// TestCase { +// value: 0x8000000000000000000000000000000000000000000000000000000000000000_U256, +// shift: 0x0100_U256, +// expected: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, +// }, +// TestCase { +// value: 0x8000000000000000000000000000000000000000000000000000000000000000_U256, +// shift: 0x0101_U256, +// expected: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, +// }, +// TestCase { +// value: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, +// shift: 0x00_U256, +// expected: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, +// }, +// TestCase { +// value: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, +// shift: 0x01_U256, +// expected: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, +// }, +// TestCase { +// value: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, +// shift: 0xff_U256, +// expected: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, +// }, +// TestCase { +// value: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, +// shift: 0x0100_U256, +// expected: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, +// }, +// TestCase { +// value: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, +// shift: 0x01_U256, +// expected: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, +// }, +// TestCase { +// value: 0x4000000000000000000000000000000000000000000000000000000000000000_U256, +// shift: 0xfe_U256, +// expected: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, +// }, +// TestCase { +// value: 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, +// shift: 0xf8_U256, +// expected: 0x000000000000000000000000000000000000000000000000000000000000007f_U256, +// }, +// TestCase { +// value: 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, +// shift: 0xfe_U256, +// expected: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, +// }, +// TestCase { +// value: 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, +// shift: 0xff_U256, +// expected: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, +// }, +// TestCase { +// value: 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, +// shift: 0x0100_U256, +// expected: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, +// }, +// ]; +// } +// +// for test in test_cases { +// push!(interpreter, test.value); +// push!(interpreter, test.shift); +// let context = +// InstructionContext { host: &mut DummyHost, interpreter: &mut interpreter }; +// sar(context); +// let res = interpreter.stack.pop().unwrap(); +// assert_eq!(res, test.expected); +// } +// } +// +// #[test] +// fn test_byte() { +// struct TestCase { +// input: U256, +// index: usize, +// expected: U256, +// } +// +// let mut interpreter = test_interpreter(); +// +// let input_value = U256::from(0x1234567890abcdef1234567890abcdef_u128); +// let test_cases = (0..32) +// .map(|i| { +// let byte_pos = 31 - i; +// +// let shift_amount = U256::from(byte_pos * 8); +// let byte_value = (input_value >> shift_amount) & U256::from(0xFF); +// TestCase { input: input_value, index: i, expected: byte_value } +// }) +// .collect::>(); +// +// for test in test_cases.iter() { +// push!(interpreter, test.input); +// push!(interpreter, U256::from(test.index)); +// let context = +// InstructionContext { host: &mut DummyHost, interpreter: &mut interpreter }; +// byte(context); +// let res = interpreter.stack.pop().unwrap(); +// assert_eq!(res, test.expected, "Failed at index: {}", test.index); +// } +// } +// +// #[test] +// fn test_clz() { +// let mut interpreter = test_interpreter(); +// interpreter.runtime_flag.spec_id = SpecId::OSAKA; +// +// struct TestCase { +// value: U256, +// expected: U256, +// } +// +// uint! { +// let test_cases = [ +// TestCase { value: 0x0_U256, expected: 256_U256 }, +// TestCase { value: 0x1_U256, expected: 255_U256 }, +// TestCase { value: 0x2_U256, expected: 254_U256 }, +// TestCase { value: 0x3_U256, expected: 254_U256 }, +// TestCase { value: 0x4_U256, expected: 253_U256 }, +// TestCase { value: 0x7_U256, expected: 253_U256 }, +// TestCase { value: 0x8_U256, expected: 252_U256 }, +// TestCase { value: 0xff_U256, expected: 248_U256 }, +// TestCase { value: 0x100_U256, expected: 247_U256 }, +// TestCase { value: 0xffff_U256, expected: 240_U256 }, +// TestCase { +// value: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, // U256::MAX +// expected: 0_U256, +// }, +// TestCase { +// value: 0x8000000000000000000000000000000000000000000000000000000000000000_U256, // 1 << 255 +// expected: 0_U256, +// }, +// TestCase { // Smallest value with 1 leading zero +// value: 0x4000000000000000000000000000000000000000000000000000000000000000_U256, // 1 << 254 +// expected: 1_U256, +// }, +// TestCase { // Value just below 1 << 255 +// value: 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, +// expected: 1_U256, +// }, +// ]; +// } +// +// for test in test_cases { +// push!(interpreter, test.value); +// let context = +// InstructionContext { host: &mut DummyHost, interpreter: &mut interpreter }; +// clz(context); +// let res = interpreter.stack.pop().unwrap(); +// assert_eq!( +// res, test.expected, +// "CLZ for value {:#x} failed. Expected: {}, Got: {}", +// test.value, test.expected, res +// ); +// } +// } +// } diff --git a/substrate/frame/revive/src/vm/evm/instructions/macros.rs b/substrate/frame/revive/src/vm/evm/instructions/macros.rs index e686e3f4be2ce..e45ab28a2c908 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/macros.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/macros.rs @@ -171,7 +171,7 @@ macro_rules! push { #[macro_export] macro_rules! as_u64_saturated { ($v:expr) => { - match $v.as_limbs() { + match &$v.0 { x => if (x[1] == 0) & (x[2] == 0) & (x[3] == 0) { x[0] diff --git a/substrate/frame/revive/src/vm/evm/instructions/mod.rs b/substrate/frame/revive/src/vm/evm/instructions/mod.rs index 8b2e1cb00db3d..7c9d65e2acd7f 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/mod.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/mod.rs @@ -19,13 +19,15 @@ use super::interpreter::Interpreter; use crate::vm::Ext; +mod bits; +mod modular; -// #[macro_use] -// mod macros; +#[macro_use] +mod macros; // /// Arithmetic operations (ADD, SUB, MUL, DIV, etc.). mod arithmetic; -// /// Bitwise operations (AND, OR, XOR, NOT, etc.). -// mod bitwise; +/// Bitwise operations (AND, OR, XOR, NOT, etc.). +mod bitwise; // /// Block information instructions (COINBASE, TIMESTAMP, etc.). // mod block_info; // /// Contract operations (CALL, CREATE, DELEGATECALL, etc.). diff --git a/substrate/frame/revive/src/vm/evm/instructions/modular.rs b/substrate/frame/revive/src/vm/evm/instructions/modular.rs new file mode 100644 index 0000000000000..82472324a5f23 --- /dev/null +++ b/substrate/frame/revive/src/vm/evm/instructions/modular.rs @@ -0,0 +1,169 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +use alloy_core::primitives::ruint::algorithms; +use sp_core::U256; + +pub trait Modular { + /// Compute $\mod{\mathtt{self}}_{\mathtt{modulus}}$. + /// + /// Returns zero if the modulus is zero. + // FEATURE: Reduce larger bit-sizes to smaller ones. + #[must_use] + fn reduce_mod(self, modulus: Self) -> Self; + + /// Compute $\mod{\mathtt{self} + \mathtt{rhs}}_{\mathtt{modulus}}$. + /// + /// Returns zero if the modulus is zero. + #[must_use] + fn add_mod(self, rhs: Self, modulus: Self) -> Self; + + /// Compute $\mod{\mathtt{self} ⋅ \mathtt{rhs}}_{\mathtt{modulus}}$. + /// + /// Returns zero if the modulus is zero. + /// + /// See [`mul_redc`](Self::mul_redc) for a faster variant at the cost of + /// some pre-computation. + #[must_use] + fn mul_mod(self, rhs: Self, modulus: Self) -> Self; +} + +impl Modular for U256 { + fn reduce_mod(mut self, modulus: Self) -> Self { + if modulus.is_zero() { + return Self::zero(); + } + if self >= modulus { + self %= modulus; + } + self + } + + fn add_mod(self, rhs: Self, modulus: Self) -> Self { + // Reduce inputs + let lhs = self.reduce_mod(modulus); + let rhs = rhs.reduce_mod(modulus); + + // Compute the sum and conditionally subtract modulus once. + let (mut result, overflow) = lhs.overflowing_add(rhs); + if overflow || result >= modulus { + result = result.overflowing_sub(modulus).0; + } + result + } + + fn mul_mod(self, rhs: Self, mut modulus: Self) -> Self { + if modulus.is_zero() { + return Self::zero(); + } + + // Allocate at least `nlimbs(2 * BITS)` limbs to store the product. This array + // casting is a workaround for `generic_const_exprs` not being stable. + let mut product = [[0u64; 2]; 4]; + let product_len = 8; + // SAFETY: `[[u64; 2]; 4] = [u64; 8]`. + let product = unsafe { + core::slice::from_raw_parts_mut(product.as_mut_ptr().cast::(), product_len) + }; + + // Compute full product. + let overflow = algorithms::addmul(product, &self.0, &rhs.0); + debug_assert!(!overflow, "addmul overflowed for 256-bit inputs"); + + // Compute modulus using `div_rem`. + // This stores the remainder in the divisor, `modulus`. + algorithms::div(product, &mut modulus.0); + + modulus + } +} + +#[cfg(test)] +mod tests { + use super::*; + use proptest::{prop_assume, proptest, test_runner::Config}; + + #[test] + fn test_commutative() { + proptest!(|(a: [u64; 4], b: [u64; 4], m: [u64; 4])| { + let (a, b, m) = (U256(a), U256(b), U256(m)); + assert_eq!(a.mul_mod(b, m), b.mul_mod(a, m)); + }); + } + + #[test] + fn test_associative() { + proptest!(|(a: [u64; 4], b: [u64; 4], c: [u64; 4], m: [u64; 4])| { + let (a, b, c, m) = (U256(a), U256(b), U256(c), U256(m)); + assert_eq!(a.mul_mod(b.mul_mod(c, m), m), a.mul_mod(b, m).mul_mod(c, m)); + }); + } + + #[test] + fn test_distributive() { + proptest!(|(a: [u64; 4], b: [u64; 4], c: [u64; 4], m: [u64; 4])| { + let (a, b, c, m) = (U256(a), U256(b), U256(c), U256(m)); + assert_eq!(a.mul_mod(b.add_mod(c, m), m), a.mul_mod(b, m).add_mod(a.mul_mod(c, m), m)); + }); + } + + #[test] + fn mul_mod_works() { + let a = U256([ + 11233609967592832353, + 2955691110330445474, + 11347763303645825691, + 4193953206527047232, + ]); + let b = U256([ + 921947257126271203, + 1637924328112375332, + 9840126700073895953, + 606138112307409896, + ]); + let c = U256([ + 761033329349325528, + 1380792689741665104, + 4216216879054509766, + 7986343297638637159, + ]); + let m = U256([ + 8095825378722175146, + 4342717078815881141, + 5646255581126092077, + 17584322201065510488, + ]); + + assert_eq!(a.mul_mod(b.add_mod(c, m), m), a.mul_mod(b, m).add_mod(a.mul_mod(c, m), m)); + } + + #[test] + fn test_add_identity() { + proptest!(|(value: [u64; 4], m: [u64; 4])| { + let (value, m) = (U256(value), U256(m)); + assert_eq!(value.add_mod(U256::from(0), m), value.reduce_mod(m)); + }); + } + + #[test] + fn test_mul_identity() { + proptest!(|(value: [u64; 4], m: [u64; 4])| { + let (value, m) = (U256(value), U256(m)); + assert_eq!(value.mul_mod(U256::from(0), m), U256::zero()); + assert_eq!(value.mul_mod(U256::from(1), m), value.reduce_mod(m)); + }); + } +} From 7972e46aaa83fd54e522446b1ef76681cd4e6cd9 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Fri, 19 Sep 2025 09:32:18 +0200 Subject: [PATCH 25/76] wip --- substrate/frame/revive/src/gas.rs | 9 + .../revive/src/vm/evm/instructions/bitwise.rs | 933 ++++++++++-------- .../src/vm/evm/instructions/block_info.rs | 74 +- .../revive/src/vm/evm/instructions/control.rs | 150 +-- .../revive/src/vm/evm/instructions/mod.rs | 8 +- substrate/frame/revive/src/vm/evm/memory.rs | 4 + substrate/frame/revive/src/vm/evm/stack.rs | 30 +- 7 files changed, 705 insertions(+), 503 deletions(-) diff --git a/substrate/frame/revive/src/gas.rs b/substrate/frame/revive/src/gas.rs index 3793fe24067b5..b7024b6241275 100644 --- a/substrate/frame/revive/src/gas.rs +++ b/substrate/frame/revive/src/gas.rs @@ -231,6 +231,15 @@ impl GasMeter { Ok(()) } + /// Charge the specified amount of EVM gas. + pub fn charge_evm>( + &mut self, + token: Tok, + ) -> Result { + self.charge_evm_gas(1)?; + self.charge(token) + } + /// Adjust a previously charged amount down to its actual amount. /// /// This is when a maximum a priori amount was charged and then should be partially diff --git a/substrate/frame/revive/src/vm/evm/instructions/bitwise.rs b/substrate/frame/revive/src/vm/evm/instructions/bitwise.rs index 0edcb228840f7..2f3d9b4a72189 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/bitwise.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/bitwise.rs @@ -17,7 +17,10 @@ use super::i256::i256_cmp; use crate::{ - vm::{evm::Interpreter, Ext}, + vm::{ + evm::{instructions::bits::Bits, Interpreter}, + Ext, + }, U256, }; use core::cmp::Ordering; @@ -174,407 +177,531 @@ pub fn shr<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResu /// EIP-145: Bitwise shifting instructions in EVM pub fn sar<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { - todo!() - // interpreter.ext.gas_meter_mut().charge_evm_gas(VERYLOW)?; - // let ([op1], op2) = interpreter.stack.popn_top()?; - // - // let shift = as_usize_saturated!(op1); - // *op2 = if shift < 256 { - // op2.arithmetic_shr(shift) - // } else if op2.bit(255) { - // U256::MAX - // } else { - // U256::zero() - // }; - // Ok(()) + interpreter.ext.gas_meter_mut().charge_evm_gas(VERYLOW)?; + let ([op1], op2) = interpreter.stack.popn_top()?; + + let shift = as_usize_saturated!(op1); + *op2 = if shift < 256 { + op2.arithmetic_shr(shift) + } else if op2.bit(255) { + U256::MAX + } else { + U256::zero() + }; + Ok(()) } -// #[cfg(test)] -// mod tests { -// use super::{byte, clz, sar, shl, shr}; -// use revm::{ -// interpreter::{host::DummyHost, InstructionContext}, -// primitives::{hardfork::SpecId, uint, U256}, -// }; -// -// pub fn test_interpreter() -> revm::interpreter::Interpreter< -// crate::vm::evm::EVMInterpreter<'static, crate::exec::mock_ext::MockExt>, -// > { -// use crate::tests::Test; -// use revm::{ -// interpreter::{ -// interpreter::{RuntimeFlags, SharedMemory}, -// Interpreter, Stack, -// }, -// primitives::hardfork::SpecId, -// }; -// -// let mock_ext = Box::leak(Box::new(crate::exec::mock_ext::MockExt::::new())); -// -// Interpreter { -// gas: revm::interpreter::Gas::new(0), -// bytecode: Default::default(), -// stack: Stack::new(), -// return_data: Default::default(), -// memory: SharedMemory::new(), -// input: crate::vm::evm::EVMInputs::default(), -// runtime_flag: RuntimeFlags { is_static: false, spec_id: SpecId::default() }, -// extend: mock_ext, -// } -// } -// -// #[test] -// fn test_shift_left() { -// let mut interpreter = test_interpreter(); -// -// struct TestCase { -// value: U256, -// shift: U256, -// expected: U256, -// } -// -// uint! { -// let test_cases = [ -// TestCase { -// value: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, -// shift: 0x00_U256, -// expected: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, -// }, -// TestCase { -// value: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, -// shift: 0x01_U256, -// expected: 0x0000000000000000000000000000000000000000000000000000000000000002_U256, -// }, -// TestCase { -// value: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, -// shift: 0xff_U256, -// expected: 0x8000000000000000000000000000000000000000000000000000000000000000_U256, -// }, -// TestCase { -// value: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, -// shift: 0x0100_U256, -// expected: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, -// }, -// TestCase { -// value: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, -// shift: 0x0101_U256, -// expected: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, -// }, -// TestCase { -// value: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, -// shift: 0x00_U256, -// expected: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, -// }, -// TestCase { -// value: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, -// shift: 0x01_U256, -// expected: 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe_U256, -// }, -// TestCase { -// value: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, -// shift: 0xff_U256, -// expected: 0x8000000000000000000000000000000000000000000000000000000000000000_U256, -// }, -// TestCase { -// value: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, -// shift: 0x0100_U256, -// expected: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, -// }, -// TestCase { -// value: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, -// shift: 0x01_U256, -// expected: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, -// }, -// TestCase { -// value: 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, -// shift: 0x01_U256, -// expected: 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe_U256, -// }, -// ]; -// } -// -// for test in test_cases { -// push!(interpreter, test.value); -// push!(interpreter, test.shift); -// let context = -// InstructionContext { host: &mut DummyHost, interpreter: &mut interpreter }; -// shl(context); -// let res = interpreter.stack.pop().unwrap(); -// assert_eq!(res, test.expected); -// } -// } -// -// #[test] -// fn test_logical_shift_right() { -// let mut interpreter = test_interpreter(); -// -// struct TestCase { -// value: U256, -// shift: U256, -// expected: U256, -// } -// -// uint! { -// let test_cases = [ -// TestCase { -// value: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, -// shift: 0x00_U256, -// expected: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, -// }, -// TestCase { -// value: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, -// shift: 0x01_U256, -// expected: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, -// }, -// TestCase { -// value: 0x8000000000000000000000000000000000000000000000000000000000000000_U256, -// shift: 0x01_U256, -// expected: 0x4000000000000000000000000000000000000000000000000000000000000000_U256, -// }, -// TestCase { -// value: 0x8000000000000000000000000000000000000000000000000000000000000000_U256, -// shift: 0xff_U256, -// expected: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, -// }, -// TestCase { -// value: 0x8000000000000000000000000000000000000000000000000000000000000000_U256, -// shift: 0x0100_U256, -// expected: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, -// }, -// TestCase { -// value: 0x8000000000000000000000000000000000000000000000000000000000000000_U256, -// shift: 0x0101_U256, -// expected: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, -// }, -// TestCase { -// value: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, -// shift: 0x00_U256, -// expected: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, -// }, -// TestCase { -// value: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, -// shift: 0x01_U256, -// expected: 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, -// }, -// TestCase { -// value: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, -// shift: 0xff_U256, -// expected: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, -// }, -// TestCase { -// value: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, -// shift: 0x0100_U256, -// expected: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, -// }, -// TestCase { -// value: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, -// shift: 0x01_U256, -// expected: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, -// }, -// ]; -// } -// -// for test in test_cases { -// push!(interpreter, test.value); -// push!(interpreter, test.shift); -// let context = -// InstructionContext { host: &mut DummyHost, interpreter: &mut interpreter }; -// shr(context); -// let res = interpreter.stack.pop().unwrap(); -// assert_eq!(res, test.expected); -// } -// } -// -// #[test] -// fn test_arithmetic_shift_right() { -// let mut interpreter = test_interpreter(); -// -// struct TestCase { -// value: U256, -// shift: U256, -// expected: U256, -// } -// -// uint! { -// let test_cases = [ -// TestCase { -// value: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, -// shift: 0x00_U256, -// expected: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, -// }, -// TestCase { -// value: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, -// shift: 0x01_U256, -// expected: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, -// }, -// TestCase { -// value: 0x8000000000000000000000000000000000000000000000000000000000000000_U256, -// shift: 0x01_U256, -// expected: 0xc000000000000000000000000000000000000000000000000000000000000000_U256, -// }, -// TestCase { -// value: 0x8000000000000000000000000000000000000000000000000000000000000000_U256, -// shift: 0xff_U256, -// expected: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, -// }, -// TestCase { -// value: 0x8000000000000000000000000000000000000000000000000000000000000000_U256, -// shift: 0x0100_U256, -// expected: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, -// }, -// TestCase { -// value: 0x8000000000000000000000000000000000000000000000000000000000000000_U256, -// shift: 0x0101_U256, -// expected: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, -// }, -// TestCase { -// value: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, -// shift: 0x00_U256, -// expected: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, -// }, -// TestCase { -// value: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, -// shift: 0x01_U256, -// expected: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, -// }, -// TestCase { -// value: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, -// shift: 0xff_U256, -// expected: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, -// }, -// TestCase { -// value: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, -// shift: 0x0100_U256, -// expected: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, -// }, -// TestCase { -// value: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, -// shift: 0x01_U256, -// expected: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, -// }, -// TestCase { -// value: 0x4000000000000000000000000000000000000000000000000000000000000000_U256, -// shift: 0xfe_U256, -// expected: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, -// }, -// TestCase { -// value: 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, -// shift: 0xf8_U256, -// expected: 0x000000000000000000000000000000000000000000000000000000000000007f_U256, -// }, -// TestCase { -// value: 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, -// shift: 0xfe_U256, -// expected: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, -// }, -// TestCase { -// value: 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, -// shift: 0xff_U256, -// expected: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, -// }, -// TestCase { -// value: 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, -// shift: 0x0100_U256, -// expected: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, -// }, -// ]; -// } -// -// for test in test_cases { -// push!(interpreter, test.value); -// push!(interpreter, test.shift); -// let context = -// InstructionContext { host: &mut DummyHost, interpreter: &mut interpreter }; -// sar(context); -// let res = interpreter.stack.pop().unwrap(); -// assert_eq!(res, test.expected); -// } -// } -// -// #[test] -// fn test_byte() { -// struct TestCase { -// input: U256, -// index: usize, -// expected: U256, -// } -// -// let mut interpreter = test_interpreter(); -// -// let input_value = U256::from(0x1234567890abcdef1234567890abcdef_u128); -// let test_cases = (0..32) -// .map(|i| { -// let byte_pos = 31 - i; -// -// let shift_amount = U256::from(byte_pos * 8); -// let byte_value = (input_value >> shift_amount) & U256::from(0xFF); -// TestCase { input: input_value, index: i, expected: byte_value } -// }) -// .collect::>(); -// -// for test in test_cases.iter() { -// push!(interpreter, test.input); -// push!(interpreter, U256::from(test.index)); -// let context = -// InstructionContext { host: &mut DummyHost, interpreter: &mut interpreter }; -// byte(context); -// let res = interpreter.stack.pop().unwrap(); -// assert_eq!(res, test.expected, "Failed at index: {}", test.index); -// } -// } -// -// #[test] -// fn test_clz() { -// let mut interpreter = test_interpreter(); -// interpreter.runtime_flag.spec_id = SpecId::OSAKA; -// -// struct TestCase { -// value: U256, -// expected: U256, -// } -// -// uint! { -// let test_cases = [ -// TestCase { value: 0x0_U256, expected: 256_U256 }, -// TestCase { value: 0x1_U256, expected: 255_U256 }, -// TestCase { value: 0x2_U256, expected: 254_U256 }, -// TestCase { value: 0x3_U256, expected: 254_U256 }, -// TestCase { value: 0x4_U256, expected: 253_U256 }, -// TestCase { value: 0x7_U256, expected: 253_U256 }, -// TestCase { value: 0x8_U256, expected: 252_U256 }, -// TestCase { value: 0xff_U256, expected: 248_U256 }, -// TestCase { value: 0x100_U256, expected: 247_U256 }, -// TestCase { value: 0xffff_U256, expected: 240_U256 }, -// TestCase { -// value: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, // U256::MAX -// expected: 0_U256, -// }, -// TestCase { -// value: 0x8000000000000000000000000000000000000000000000000000000000000000_U256, // 1 << 255 -// expected: 0_U256, -// }, -// TestCase { // Smallest value with 1 leading zero -// value: 0x4000000000000000000000000000000000000000000000000000000000000000_U256, // 1 << 254 -// expected: 1_U256, -// }, -// TestCase { // Value just below 1 << 255 -// value: 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, -// expected: 1_U256, -// }, -// ]; -// } -// -// for test in test_cases { -// push!(interpreter, test.value); -// let context = -// InstructionContext { host: &mut DummyHost, interpreter: &mut interpreter }; -// clz(context); -// let res = interpreter.stack.pop().unwrap(); -// assert_eq!( -// res, test.expected, -// "CLZ for value {:#x} failed. Expected: {}, Got: {}", -// test.value, test.expected, res -// ); -// } -// } -// } +#[cfg(test)] +mod tests { + use super::{byte, clz, sar, shl, shr}; + use crate::{ + tests::Test, + vm::evm::{Bytecode, Interpreter}, + }; + use alloy_core::hex; + use sp_core::U256; + use sp_runtime::DispatchResult; + + macro_rules! test_interpreter { + ($interpreter: ident) => { + let mut mock_ext = crate::exec::mock_ext::MockExt::::new(); + let mut $interpreter = Interpreter::new(Default::default(), vec![], &mut mock_ext); + }; + } + + #[test] + fn test_shift_left() -> DispatchResult { + struct TestCase { + value: U256, + shift: U256, + expected: U256, + } + + let test_cases = [ + TestCase { + value: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000001" + )), + shift: U256::from_big_endian(&hex!("00")), + expected: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000001" + )), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000001" + )), + shift: U256::from_big_endian(&hex!("01")), + expected: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000002" + )), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000001" + )), + shift: U256::from_big_endian(&hex!("ff")), + expected: U256::from_big_endian(&hex!( + "8000000000000000000000000000000000000000000000000000000000000000" + )), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000001" + )), + shift: U256::from_big_endian(&hex!("0100")), + expected: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000001" + )), + shift: U256::from_big_endian(&hex!("0101")), + expected: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), + shift: U256::from_big_endian(&hex!("00")), + expected: U256::from_big_endian(&hex!( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), + shift: U256::from_big_endian(&hex!("01")), + expected: U256::from_big_endian(&hex!( + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe" + )), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), + shift: U256::from_big_endian(&hex!("ff")), + expected: U256::from_big_endian(&hex!( + "8000000000000000000000000000000000000000000000000000000000000000" + )), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), + shift: U256::from_big_endian(&hex!("0100")), + expected: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + shift: U256::from_big_endian(&hex!("01")), + expected: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), + shift: U256::from_big_endian(&hex!("01")), + expected: U256::from_big_endian(&hex!( + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe" + )), + }, + ]; + + test_interpreter!(interpreter); + for test in test_cases { + interpreter.stack.push(test.value)?; + interpreter.stack.push(test.shift)?; + shl(&mut interpreter)?; + let res = interpreter.stack.pop().unwrap(); + assert_eq!(res, test.expected); + } + + Ok(()) + } + + #[test] + fn test_logical_shift_right() -> DispatchResult { + struct TestCase { + value: U256, + shift: U256, + expected: U256, + } + + let test_cases = [ + TestCase { + value: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000001" + )), + shift: U256::from_big_endian(&hex!("00")), + expected: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000001" + )), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000001" + )), + shift: U256::from_big_endian(&hex!("01")), + expected: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "8000000000000000000000000000000000000000000000000000000000000000" + )), + shift: U256::from_big_endian(&hex!("01")), + expected: U256::from_big_endian(&hex!( + "4000000000000000000000000000000000000000000000000000000000000000" + )), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "8000000000000000000000000000000000000000000000000000000000000000" + )), + shift: U256::from_big_endian(&hex!("ff")), + expected: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000001" + )), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "8000000000000000000000000000000000000000000000000000000000000000" + )), + shift: U256::from_big_endian(&hex!("0100")), + expected: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "8000000000000000000000000000000000000000000000000000000000000000" + )), + shift: U256::from_big_endian(&hex!("0101")), + expected: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), + shift: U256::from_big_endian(&hex!("00")), + expected: U256::from_big_endian(&hex!( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), + shift: U256::from_big_endian(&hex!("01")), + expected: U256::from_big_endian(&hex!( + "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), + shift: U256::from_big_endian(&hex!("ff")), + expected: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000001" + )), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), + shift: U256::from_big_endian(&hex!("0100")), + expected: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + shift: U256::from_big_endian(&hex!("01")), + expected: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + }, + ]; + + test_interpreter!(interpreter); + for test in test_cases { + interpreter.stack.push(test.value)?; + interpreter.stack.push(test.shift)?; + shr(&mut interpreter)?; + let res = interpreter.stack.pop().unwrap(); + assert_eq!(res, test.expected); + } + Ok(()) + } + + #[test] + fn test_arithmetic_shift_right() -> DispatchResult { + struct TestCase { + value: U256, + shift: U256, + expected: U256, + } + + let test_cases = [ + TestCase { + value: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000001" + )), + shift: U256::from_big_endian(&hex!("00")), + expected: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000001" + )), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000001" + )), + shift: U256::from_big_endian(&hex!("01")), + expected: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "8000000000000000000000000000000000000000000000000000000000000000" + )), + shift: U256::from_big_endian(&hex!("01")), + expected: U256::from_big_endian(&hex!( + "c000000000000000000000000000000000000000000000000000000000000000" + )), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "8000000000000000000000000000000000000000000000000000000000000000" + )), + shift: U256::from_big_endian(&hex!("ff")), + expected: U256::from_big_endian(&hex!( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "8000000000000000000000000000000000000000000000000000000000000000" + )), + shift: U256::from_big_endian(&hex!("0100")), + expected: U256::from_big_endian(&hex!( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "8000000000000000000000000000000000000000000000000000000000000000" + )), + shift: U256::from_big_endian(&hex!("0101")), + expected: U256::from_big_endian(&hex!( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), + shift: U256::from_big_endian(&hex!("00")), + expected: U256::from_big_endian(&hex!( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), + shift: U256::from_big_endian(&hex!("01")), + expected: U256::from_big_endian(&hex!( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), + shift: U256::from_big_endian(&hex!("ff")), + expected: U256::from_big_endian(&hex!( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), + shift: U256::from_big_endian(&hex!("0100")), + expected: U256::from_big_endian(&hex!( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + shift: U256::from_big_endian(&hex!("01")), + expected: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "4000000000000000000000000000000000000000000000000000000000000000" + )), + shift: U256::from_big_endian(&hex!("fe")), + expected: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000001" + )), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), + shift: U256::from_big_endian(&hex!("f8")), + expected: U256::from_big_endian(&hex!( + "000000000000000000000000000000000000000000000000000000000000007f" + )), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), + shift: U256::from_big_endian(&hex!("fe")), + expected: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000001" + )), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), + shift: U256::from_big_endian(&hex!("ff")), + expected: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), + shift: U256::from_big_endian(&hex!("0100")), + expected: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + }, + ]; + + test_interpreter!(interpreter); + for test in test_cases { + interpreter.stack.push(test.value)?; + interpreter.stack.push(test.shift)?; + sar(&mut interpreter)?; + let res = interpreter.stack.pop().unwrap(); + assert_eq!(res, test.expected); + } + Ok(()) + } + + #[test] + fn test_byte() -> DispatchResult { + struct TestCase { + input: U256, + index: usize, + expected: U256, + } + + let input_value = U256::from_big_endian(&hex!("1234567890abcdef1234567890abcdef")); + let test_cases = (0..32) + .map(|i| { + let byte_pos = 31 - i; + let shift_amount = U256::from(byte_pos * 8); + let byte_value = (input_value >> shift_amount) & U256::from(0xFF); + TestCase { input: input_value, index: i, expected: byte_value } + }) + .collect::>(); + + test_interpreter!(interpreter); + for test in test_cases.iter() { + interpreter.stack.push(test.input)?; + interpreter.stack.push(U256::from(test.index))?; + byte(&mut interpreter)?; + let res = interpreter.stack.pop().unwrap(); + assert_eq!(res, test.expected, "Failed at index: {}", test.index); + } + Ok(()) + } + + #[test] + fn test_clz() -> DispatchResult { + struct TestCase { + value: U256, + expected: U256, + } + + let test_cases = [ + TestCase { value: U256::from_big_endian(&hex!("00")), expected: U256::from(256) }, + TestCase { value: U256::from_big_endian(&hex!("01")), expected: U256::from(255) }, + TestCase { value: U256::from_big_endian(&hex!("02")), expected: U256::from(254) }, + TestCase { value: U256::from_big_endian(&hex!("03")), expected: U256::from(254) }, + TestCase { value: U256::from_big_endian(&hex!("04")), expected: U256::from(253) }, + TestCase { value: U256::from_big_endian(&hex!("07")), expected: U256::from(253) }, + TestCase { value: U256::from_big_endian(&hex!("08")), expected: U256::from(252) }, + TestCase { value: U256::from_big_endian(&hex!("ff")), expected: U256::from(248) }, + TestCase { value: U256::from_big_endian(&hex!("0100")), expected: U256::from(247) }, + TestCase { value: U256::from_big_endian(&hex!("ffff")), expected: U256::from(240) }, + TestCase { + value: U256::from_big_endian(&hex!( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), + expected: U256::from(0), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "8000000000000000000000000000000000000000000000000000000000000000" + )), + expected: U256::from(0), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "4000000000000000000000000000000000000000000000000000000000000000" + )), + expected: U256::from(1), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), + expected: U256::from(1), + }, + ]; + + test_interpreter!(interpreter); + for test in test_cases.iter() { + interpreter.stack.push(test.value)?; + clz(&mut interpreter)?; + let res = interpreter.stack.pop().unwrap(); + assert_eq!( + res, test.expected, + "CLZ for value {:#x} failed. Expected: {}, Got: {}", + test.value, test.expected, res + ); + } + Ok(()) + } +} diff --git a/substrate/frame/revive/src/vm/evm/instructions/block_info.rs b/substrate/frame/revive/src/vm/evm/instructions/block_info.rs index 0f91828f7e65c..f1cee1de11d74 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/block_info.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/block_info.rs @@ -15,78 +15,82 @@ // See the License for the specific language governing permissions and // limitations under the License. -use super::Context; use crate::{ vm::{ - evm::{U256Converter, BASE_FEE, DIFFICULTY}, + evm::{interpreter::HaltReason, Interpreter, BASE_FEE, DIFFICULTY}, Ext, }, RuntimeCosts, }; -use revm::{ - interpreter::gas as revm_gas, - primitives::{Address, U256}, -}; -use sp_core::H160; + +use revm::{interpreter::gas::BASE, primitives::Address}; +use sp_core::{H160, U256}; +use sp_runtime::DispatchResult; /// EIP-1344: ChainID opcode -pub fn chainid<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::BASE); - push!(context.interpreter, U256::from(context.interpreter.extend.chain_id())); +pub fn chainid<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { + interpreter.ext.gas_meter_mut().charge_evm_gas(BASE)?; + interpreter.stack.push(interpreter.ext.chain_id())?; + Ok(()) } /// Implements the COINBASE instruction. /// /// Pushes the current block's beneficiary address onto the stack. -pub fn coinbase<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas!(context.interpreter, RuntimeCosts::BlockAuthor); - let coinbase: Address = - context.interpreter.extend.block_author().unwrap_or(H160::zero()).0.into(); - push!(context.interpreter, coinbase.into_word().into()); +pub fn coinbase<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { + interpreter.ext.gas_meter_mut().charge_evm(RuntimeCosts::BlockAuthor)?; + let coinbase = interpreter.ext.block_author().unwrap_or(H160::zero()); + interpreter.stack.push(coinbase)?; + Ok(()) } /// Implements the TIMESTAMP instruction. /// /// Pushes the current block's timestamp onto the stack. -pub fn timestamp<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas!(context.interpreter, RuntimeCosts::Now); - let timestamp = context.interpreter.extend.now(); - push!(context.interpreter, timestamp.into_revm_u256()); +pub fn timestamp<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { + interpreter.ext.gas_meter_mut().charge_evm(RuntimeCosts::Now)?; + let timestamp = interpreter.ext.now(); + interpreter.stack.push(timestamp)?; + Ok(()) } /// Implements the NUMBER instruction. /// /// Pushes the current block number onto the stack. -pub fn block_number<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas!(context.interpreter, RuntimeCosts::BlockNumber); - let block_number = context.interpreter.extend.block_number(); - push!(context.interpreter, block_number.into_revm_u256()); +pub fn block_number<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { + interpreter.ext.gas_meter_mut().charge_evm(RuntimeCosts::BlockNumber)?; + let block_number = interpreter.ext.block_number(); + interpreter.stack.push(block_number)?; + Ok(()) } /// Implements the DIFFICULTY/PREVRANDAO instruction. /// /// Pushes the block difficulty (pre-merge) or prevrandao (post-merge) onto the stack. -pub fn difficulty<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::BASE); - push!(context.interpreter, U256::from(DIFFICULTY)); +pub fn difficulty<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { + interpreter.ext.gas_meter_mut().charge_evm_gas(BASE)?; + interpreter.stack.push(U256::from(DIFFICULTY))?; + Ok(()) } /// Implements the GASLIMIT instruction. /// /// Pushes the current block's gas limit onto the stack. -pub fn gaslimit<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas!(context.interpreter, RuntimeCosts::GasLimit); - let gas_limit = context.interpreter.extend.gas_limit(); - push!(context.interpreter, U256::from(gas_limit)); +pub fn gaslimit<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { + interpreter.ext.gas_meter_mut().charge_evm(RuntimeCosts::GasLimit)?; + let gas_limit = interpreter.ext.gas_limit(); + interpreter.stack.push(U256::from(gas_limit))?; + Ok(()) } /// EIP-3198: BASEFEE opcode -pub fn basefee<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas!(context.interpreter, RuntimeCosts::BaseFee); - push!(context.interpreter, BASE_FEE.into_revm_u256()); +pub fn basefee<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { + interpreter.ext.gas_meter_mut().charge_evm(RuntimeCosts::BaseFee)?; + interpreter.stack.push(BASE_FEE)?; + Ok(()) } /// EIP-7516: BLOBBASEFEE opcode is not supported -pub fn blob_basefee<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - context.interpreter.halt(revm::interpreter::InstructionResult::NotActivated); +pub fn blob_basefee<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { + Err(HaltReason::NotActivated.into()) } diff --git a/substrate/frame/revive/src/vm/evm/instructions/control.rs b/substrate/frame/revive/src/vm/evm/instructions/control.rs index 11298ea1f35f2..4a19475f4ac31 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/control.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/control.rs @@ -15,132 +15,166 @@ // See the License for the specific language governing permissions and // limitations under the License. -use super::Context; -use crate::vm::Ext; +use crate::{ + vm::{ + evm::{ + interpreter::{HaltReason, OutOfGasError}, + Interpreter, + }, + Ext, + }, + RuntimeCosts, +}; use revm::{ interpreter::{ - gas as revm_gas, + gas::{BASE, HIGH, JUMPDEST, MID}, interpreter_action::InterpreterAction, interpreter_types::{Jumps, LoopControl, StackTr}, - InstructionResult, Interpreter, + InstructionResult, }, - primitives::{Bytes, U256}, + primitives::Bytes, }; +use sp_core::U256; +use sp_runtime::DispatchResult; /// Implements the JUMP instruction. /// /// Unconditional jump to a valid destination. -pub fn jump<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::MID); - let Some([target]) = <_ as StackTr>::popn(&mut context.interpreter.stack) else { - context.interpreter.halt(InstructionResult::StackUnderflow); - return; - }; - jump_inner(context.interpreter, target); +pub fn jump<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { + interpreter.ext.gas_meter_mut().charge_evm_gas(MID)?; + let [target] = interpreter.stack.popn()?; + jump_inner(interpreter, target)?; + Ok(()) } /// Implements the JUMPI instruction. /// /// Conditional jump to a valid destination if condition is true. -pub fn jumpi<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::HIGH); - let Some([target, cond]) = <_ as StackTr>::popn(&mut context.interpreter.stack) else { - context.interpreter.halt(InstructionResult::StackUnderflow); - return; - }; +pub fn jumpi<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { + interpreter.ext.gas_meter_mut().charge_evm_gas(HIGH)?; + let [target, cond] = interpreter.stack.popn()?; if !cond.is_zero() { - jump_inner(context.interpreter, target); + jump_inner(interpreter, target)?; } + Ok(()) } #[inline(always)] /// Internal helper function for jump operations. /// /// Validates jump target and performs the actual jump. -fn jump_inner( - interpreter: &mut Interpreter, - target: U256, -) { - let target = as_usize_or_fail!(interpreter, target, InstructionResult::InvalidJump); +fn jump_inner(interpreter: &mut Interpreter<'_, E>, target: U256) -> DispatchResult { + let target = as_usize_checked(target).ok_or(HaltReason::InvalidJump)?; if !interpreter.bytecode.is_valid_legacy_jump(target) { - interpreter.halt(InstructionResult::InvalidJump); - return; + return Err(HaltReason::InvalidJump.into()); } // SAFETY: `is_valid_jump` ensures that `dest` is in bounds. interpreter.bytecode.absolute_jump(target); + Ok(()) } /// Implements the JUMPDEST instruction. /// /// Marks a valid destination for jump operations. -pub fn jumpdest<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::JUMPDEST); +pub fn jumpdest<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { + interpreter.ext.gas_meter_mut().charge_evm_gas(JUMPDEST)?; + Ok(()) } /// Implements the PC instruction. /// /// Pushes the current program counter onto the stack. -pub fn pc<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::BASE); +pub fn pc<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { + interpreter.ext.gas_meter_mut().charge_evm_gas(BASE)?; // - 1 because we have already advanced the instruction pointer in `Interpreter::step` - push!(context.interpreter, U256::from(context.interpreter.bytecode.pc() - 1)); + interpreter.stack.push(U256::from(interpreter.bytecode.pc() - 1))?; + Ok(()) } #[inline] /// Internal helper function for return operations. /// /// Handles memory data retrieval and sets the return action. -fn return_inner<'a, E: Ext>( - interpreter: &mut Interpreter>, +fn return_inner( + interpreter: &mut Interpreter<'_, E>, instruction_result: InstructionResult, -) { +) -> DispatchResult { // Zero gas cost - // gas_legacy!(interpreter, revm_gas::ZERO) - let Some([offset, len]) = <_ as StackTr>::popn(&mut interpreter.stack) else { - interpreter.halt(InstructionResult::StackUnderflow); - return; - }; - let len = as_usize_or_fail!(interpreter, len); + let [offset, len] = interpreter.stack.popn()?; + let len = as_usize_checked(len).ok_or(HaltReason::OutOfGas(OutOfGasError::InvalidOperand))?; // Important: Offset must be ignored if len is zeros let mut output = Bytes::default(); if len != 0 { - let offset = as_usize_or_fail!(interpreter, offset); - resize_memory!(interpreter, offset, len); + let offset = + as_usize_checked(offset).ok_or(HaltReason::OutOfGas(OutOfGasError::InvalidOperand))?; + resize_memory_checked(interpreter, offset, len)?; output = interpreter.memory.slice_len(offset, len).to_vec().into() } - interpreter.bytecode.set_action(InterpreterAction::new_return( - instruction_result, - output, - interpreter.gas, - )); + todo!(); + // interpreter.bytecode.set_action(InterpreterAction::new_return( + // instruction_result, + // output, + // interpreter.gas, + // )); + // Ok(()) +} + +/// Helper function to resize memory with proper error handling +#[inline] +fn resize_memory_checked( + interpreter: &mut Interpreter<'_, E>, + offset: usize, + len: usize, +) -> DispatchResult { + let current_len = interpreter.memory.size(); + let target_len = revm::interpreter::num_words(offset.saturating_add(len)) * 32; + if target_len as u32 > crate::limits::code::BASELINE_MEMORY_LIMIT { + log::debug!(target: crate::LOG_TARGET, "check memory bounds failed: offset={} target_len={target_len} current_len={current_len}", offset); + return Err(HaltReason::OutOfGas(OutOfGasError::Memory).into()); + } + + if target_len > current_len { + interpreter.memory.resize(target_len); + } + Ok(()) } /// Implements the RETURN instruction. /// /// Halts execution and returns data from memory. -pub fn ret<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - return_inner(context.interpreter, InstructionResult::Return); +pub fn ret<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { + return_inner(interpreter, InstructionResult::Return) } /// EIP-140: REVERT instruction -pub fn revert<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - return_inner(context.interpreter, InstructionResult::Revert); +pub fn revert<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { + return_inner(interpreter, InstructionResult::Revert) } /// Stop opcode. This opcode halts the execution. -pub fn stop<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - context.interpreter.halt(InstructionResult::Stop); +pub fn stop<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { + return_inner(interpreter, InstructionResult::Stop) } /// Invalid opcode. This opcode halts the execution. -pub fn invalid<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - context.interpreter.extend.gas_meter_mut().consume_all(); - context.interpreter.halt(InstructionResult::InvalidFEOpcode); +pub fn invalid<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { + interpreter.ext.gas_meter_mut().consume_all(); + Err(HaltReason::InvalidFEOpcode.into()) } /// Unknown opcode. This opcode halts the execution. -pub fn unknown<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - context.interpreter.halt(InstructionResult::OpcodeNotFound); +pub fn unknown<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { + Err(HaltReason::OpcodeNotFound.into()) +} + +/// Helper function to convert U256 to usize, checking for overflow +fn as_usize_checked(value: U256) -> Option { + let limbs = value.0; + if (limbs[0] > usize::MAX as u64) | (limbs[1] != 0) | (limbs[2] != 0) | (limbs[3] != 0) { + None + } else { + Some(limbs[0] as usize) + } } diff --git a/substrate/frame/revive/src/vm/evm/instructions/mod.rs b/substrate/frame/revive/src/vm/evm/instructions/mod.rs index 7c9d65e2acd7f..7a9990acb09f1 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/mod.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/mod.rs @@ -28,12 +28,12 @@ mod macros; mod arithmetic; /// Bitwise operations (AND, OR, XOR, NOT, etc.). mod bitwise; -// /// Block information instructions (COINBASE, TIMESTAMP, etc.). -// mod block_info; +/// Block information instructions (COINBASE, TIMESTAMP, etc.). +mod block_info; // /// Contract operations (CALL, CREATE, DELEGATECALL, etc.). // mod contract; -// /// Control flow instructions (JUMP, JUMPI, REVERT, etc.). -// mod control; +/// Control flow instructions (JUMP, JUMPI, REVERT, etc.). +mod control; // /// Host environment interactions (SLOAD, SSTORE, LOG, etc.). // mod host; /// Signed 256-bit integer operations. diff --git a/substrate/frame/revive/src/vm/evm/memory.rs b/substrate/frame/revive/src/vm/evm/memory.rs index f77fa746962e7..a3ee0627cde9e 100644 --- a/substrate/frame/revive/src/vm/evm/memory.rs +++ b/substrate/frame/revive/src/vm/evm/memory.rs @@ -68,6 +68,10 @@ impl Memory { } } + pub fn slice_len(&self, offset: usize, len: usize) -> &[u8] { + self.0.get(offset..offset.saturating_add(len)).unwrap_or(&[]) + } + /// Copy data within memory from src to dst pub fn copy(&mut self, dst: usize, src: usize, len: usize) { if len == 0 { diff --git a/substrate/frame/revive/src/vm/evm/stack.rs b/substrate/frame/revive/src/vm/evm/stack.rs index d677f503c0d65..29eee81ba8b8d 100644 --- a/substrate/frame/revive/src/vm/evm/stack.rs +++ b/substrate/frame/revive/src/vm/evm/stack.rs @@ -19,12 +19,36 @@ use crate::vm::evm::interpreter::HaltReason; use alloc::vec::Vec; -use sp_core::U256; +use sp_core::{H160, H256, U256}; /// EVM stack implementation using sp_core types #[derive(Debug, Clone)] pub struct Stack(Vec); +/// A trait for converting types into an unsigned 256-bit integer (`U256`). +pub trait ToU256 { + /// Converts `self` into a `U256`. + fn to_u256(self) -> U256; +} + +impl ToU256 for U256 { + fn to_u256(self) -> U256 { + self + } +} + +impl ToU256 for u64 { + fn to_u256(self) -> U256 { + self.into() + } +} + +impl ToU256 for H160 { + fn to_u256(self) -> U256 { + U256::from_big_endian(H256::from(self).as_ref()) + } +} + impl Stack { /// Create a new empty stack pub fn new() -> Self { @@ -33,11 +57,11 @@ impl Stack { /// Push a value onto the stack /// Returns Ok(()) if successful, Err(HaltReason::StackOverflow) if stack would overflow - pub fn push(&mut self, value: U256) -> Result<(), HaltReason> { + pub fn push(&mut self, value: impl ToU256) -> Result<(), HaltReason> { if self.0.len() >= 1024 { Err(HaltReason::StackOverflow) } else { - self.0.push(value); + self.0.push(value.to_u256()); Ok(()) } } From 8aca032b0ab1011af364507f5d09e1963e3e17a2 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Mon, 22 Sep 2025 16:54:19 +0200 Subject: [PATCH 26/76] wip --- substrate/frame/revive/src/gas.rs | 19 +- substrate/frame/revive/src/vm/evm.rs | 116 +------ .../frame/revive/src/vm/evm/ext_bytecode.rs | 90 ++++++ .../src/vm/evm/instructions/arithmetic.rs | 62 ++-- .../evm/instructions/{ => arithmetic}/i256.rs | 0 .../instructions/{ => arithmetic}/modular.rs | 0 .../revive/src/vm/evm/instructions/bitwise.rs | 90 +++--- .../vm/evm/instructions/{ => bitwise}/bits.rs | 0 .../src/vm/evm/instructions/block_info.rs | 35 +- .../src/vm/evm/instructions/contract.rs | 302 +++++++++--------- .../evm/instructions/contract/call_helpers.rs | 98 +++--- .../revive/src/vm/evm/instructions/control.rs | 113 +++---- .../revive/src/vm/evm/instructions/mod.rs | 16 +- .../revive/src/vm/evm/instructions/utility.rs | 157 ++++----- .../frame/revive/src/vm/evm/interpreter.rs | 212 +++--------- substrate/frame/revive/src/vm/evm/memory.rs | 21 +- substrate/frame/revive/src/vm/evm/stack.rs | 115 ++++--- substrate/frame/revive/src/vm/evm/util.rs | 25 ++ 18 files changed, 659 insertions(+), 812 deletions(-) create mode 100644 substrate/frame/revive/src/vm/evm/ext_bytecode.rs rename substrate/frame/revive/src/vm/evm/instructions/{ => arithmetic}/i256.rs (100%) rename substrate/frame/revive/src/vm/evm/instructions/{ => arithmetic}/modular.rs (100%) rename substrate/frame/revive/src/vm/evm/instructions/{ => bitwise}/bits.rs (100%) create mode 100644 substrate/frame/revive/src/vm/evm/util.rs diff --git a/substrate/frame/revive/src/gas.rs b/substrate/frame/revive/src/gas.rs index b7024b6241275..be049bb933265 100644 --- a/substrate/frame/revive/src/gas.rs +++ b/substrate/frame/revive/src/gas.rs @@ -14,9 +14,8 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. - -use crate::{exec::ExecError, weights::WeightInfo, Config, Error}; -use core::marker::PhantomData; +use crate::{exec::ExecError, vm::evm::Halt, weights::WeightInfo, Config, Error}; +use core::{marker::PhantomData, ops::ControlFlow}; use frame_support::{ dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, PostDispatchInfo}, weights::Weight, @@ -222,22 +221,22 @@ impl GasMeter { /// Charge the specified amount of EVM gas. /// This is used for basic opcodes (e.g arithmetic, bitwise, ...) that don't have a dedicated /// benchmark - pub fn charge_evm_gas(&mut self, gas: u64) -> Result<(), DispatchError> { + + pub fn charge_evm_gas(&mut self, gas: u64) -> ControlFlow { let base_cost = T::WeightInfo::evm_opcode(1).saturating_sub(T::WeightInfo::evm_opcode(0)); self.gas_left = self .gas_left .checked_sub(&base_cost.saturating_mul(gas)) - .ok_or_else(|| Error::::OutOfGas)?; - Ok(()) + .map_or_else(|| ControlFlow::Break(Halt::OutOfGas), ControlFlow::Continue)?; + + ControlFlow::Continue(()) } /// Charge the specified amount of EVM gas. - pub fn charge_evm>( - &mut self, - token: Tok, - ) -> Result { + pub fn charge_evm>(&mut self, token: Tok) -> ControlFlow { self.charge_evm_gas(1)?; self.charge(token) + .map_or_else(|_| ControlFlow::Break(Halt::OutOfGas), ControlFlow::Continue) } /// Adjust a previously charged amount down to its actual amount. diff --git a/substrate/frame/revive/src/vm/evm.rs b/substrate/frame/revive/src/vm/evm.rs index 42290ea497bde..8dba501f108dc 100644 --- a/substrate/frame/revive/src/vm/evm.rs +++ b/substrate/frame/revive/src/vm/evm.rs @@ -25,7 +25,7 @@ use crate::{ use alloc::{boxed::Box, vec::Vec}; use core::cmp::min; use pallet_revive_uapi::ReturnFlags; -use revm::{bytecode::Bytecode, interpreter::interpreter::ExtBytecode, primitives::Bytes}; +use revm::{bytecode::Bytecode, primitives::Bytes}; use sp_core::H160; use sp_runtime::Weight; @@ -35,6 +35,8 @@ pub mod instructions; mod instructions; mod interpreter; +mod util; +pub use interpreter::Halt; use interpreter::{InstructionTable, Interpreter}; mod memory; @@ -43,6 +45,9 @@ use memory::Memory; mod stack; use stack::Stack; +mod ext_bytecode; +use ext_bytecode::ExtBytecode; + /// Hard-coded value returned by the EVM `DIFFICULTY` opcode. /// /// After Ethereum's Merge (Sept 2022), the `DIFFICULTY` opcode was redefined to return @@ -171,70 +176,6 @@ fn run<'a, E: Ext>( } } -fn run_call<'a, E: Ext>(interpreter: &mut Interpreter<'a, E>, call_input: &'a [u8]) { - todo!() - // let callee: H160 = if call_input.scheme.is_delegate_call() { - // call_input.bytecode_address.0 .0.into() - // } else { - // call_input.target_address.0 .0.into() - // }; - // - // let input = match &call_input.input { - // CallInput::Bytes(bytes) => bytes.to_vec(), - // CallInput::SharedBuffer(range) => interpreter.memory.global_slice(range.clone()).to_vec(), - // }; - // let call_result = match call_input.scheme { - // CallScheme::Call | CallScheme::StaticCall => interpreter.extend.call( - // Weight::from_parts(call_input.gas_limit, u64::MAX), - // U256::MAX, - // &callee, - // U256::from_revm_u256(&call_input.call_value()), - // input, - // true, - // call_input.is_static, - // ), - // CallScheme::CallCode => { - // unreachable!() - // }, - // CallScheme::DelegateCall => interpreter.extend.delegate_call( - // Weight::from_parts(call_input.gas_limit, u64::MAX), - // U256::MAX, - // callee, - // input, - // ), - // }; - // - // let (return_data, did_revert) = { - // let return_value = interpreter.extend.last_frame_output(); - // let return_data: Bytes = return_value.data.clone().into(); - // (return_data, return_value.did_revert()) - // }; - // - // let mem_length = call_input.return_memory_offset.len(); - // let mem_start = call_input.return_memory_offset.start; - // let returned_len = return_data.len(); - // let target_len = min(mem_length, returned_len); - // - // interpreter.return_data.set_buffer(return_data); - // - // match call_result { - // Ok(()) => { - // // success or revert - // gas!(interpreter, RuntimeCosts::CopyToContract(target_len as u32)); - // interpreter - // .memory - // .set(mem_start, &interpreter.return_data.buffer()[..target_len]); - // let _ = interpreter.stack.push(primitives::U256::from(!did_revert as u8)); - // }, - // Err(err) => { - // let _ = interpreter.stack.push(primitives::U256::ZERO); - // if let Some(reason) = exec_error_into_halt_reason::(err) { - // interpreter.halt(reason); - // } - // }, - // } -} - /// Re-implementation of REVM run_plain function to add trace logging to our EVM interpreter loop. /// NB: copied directly from revm tag v82 #[cfg(feature = "std")] @@ -314,51 +255,6 @@ fn run_plain_with_tracing<'a, E: Ext>( // interpreter.take_next_action() } -fn run_create<'a, E: Ext>(interpreter: &mut Interpreter<'a, E>, create_input: &[u8]) { - todo!() - // let value = U256::from_revm_u256(&create_input.value); - // - // let salt = match create_input.scheme { - // CreateScheme::Create => None, - // CreateScheme::Create2 { salt } => Some(salt.to_le_bytes()), - // CreateScheme::Custom { .. } => unreachable!("custom create schemes are not supported"), - // }; - // - // let call_result = interpreter.extend.instantiate( - // Weight::from_parts(create_input.gas_limit, u64::MAX), - // U256::MAX, - // Code::Upload(create_input.init_code.to_vec()), - // value, - // vec![], - // salt.as_ref(), - // ); - // - // let return_value = interpreter.extend.last_frame_output(); - // let return_data: Bytes = return_value.data.clone().into(); - // - // match call_result { - // Ok(address) => { - // if return_value.did_revert() { - // // Contract creation reverted — return data must be propagated - // gas!(interpreter, RuntimeCosts::CopyToContract(return_data.len() as u32)); - // interpreter.return_data.set_buffer(return_data); - // let _ = interpreter.stack.push(primitives::U256::ZERO); - // } else { - // // Otherwise clear it. Note that RETURN opcode should abort. - // interpreter.return_data.clear(); - // let stack_item: Address = address.0.into(); - // let _ = interpreter.stack.push(stack_item.into_word().into()); - // } - // }, - // Err(err) => { - // let _ = interpreter.stack.push(primitives::U256::ZERO); - // if let Some(reason) = exec_error_into_halt_reason::(err) { - // interpreter.halt(reason); - // } - // }, - // } -} - // /// Conversion of a `ExecError` to `ReturnErrorCode`. // /// // /// Used when converting the error returned from a subcall in order to map it to the diff --git a/substrate/frame/revive/src/vm/evm/ext_bytecode.rs b/substrate/frame/revive/src/vm/evm/ext_bytecode.rs new file mode 100644 index 0000000000000..dfabdc270b922 --- /dev/null +++ b/substrate/frame/revive/src/vm/evm/ext_bytecode.rs @@ -0,0 +1,90 @@ +use core::{ops::Deref, ptr}; +use revm::{ + bytecode::{utils::read_u16, Bytecode}, + interpreter::interpreter_types::{Immediates, Jumps}, +}; + +/// Extended bytecode structure that wraps base bytecode with additional execution metadata. +#[derive(Debug)] +pub struct ExtBytecode { + /// The base bytecode. + base: Bytecode, + /// The current instruction pointer. + instruction_pointer: *const u8, +} + +impl Deref for ExtBytecode { + type Target = Bytecode; + + fn deref(&self) -> &Self::Target { + &self.base + } +} + +impl Default for ExtBytecode { + fn default() -> Self { + Self::new(Bytecode::default()) + } +} + +impl ExtBytecode { + /// Create new extended bytecode and set the instruction pointer to the start of the bytecode. + pub fn new(base: Bytecode) -> Self { + let instruction_pointer = base.bytecode_ptr(); + Self { base, instruction_pointer } + } + + fn is_end(&self) -> bool { + self.instruction_pointer.is_null() + } +} + +impl Jumps for ExtBytecode { + fn relative_jump(&mut self, offset: isize) { + self.instruction_pointer = unsafe { self.instruction_pointer.offset(offset) }; + } + + fn absolute_jump(&mut self, offset: usize) { + self.instruction_pointer = unsafe { self.base.bytes_ref().as_ptr().add(offset) }; + } + + fn is_valid_legacy_jump(&mut self, offset: usize) -> bool { + self.base.legacy_jump_table().expect("Panic if not legacy").is_valid(offset) + } + + fn opcode(&self) -> u8 { + // SAFETY: `instruction_pointer` always point to bytecode. + unsafe { *self.instruction_pointer } + } + + fn pc(&self) -> usize { + // SAFETY: `instruction_pointer` should be at an offset from the start of the bytes. + // In practice this is always true unless a caller modifies the `instruction_pointer` field + // manually. + unsafe { self.instruction_pointer.offset_from(self.base.bytes_ref().as_ptr()) as usize } + } +} + +impl Immediates for ExtBytecode { + fn read_u16(&self) -> u16 { + unsafe { read_u16(self.instruction_pointer) } + } + + fn read_u8(&self) -> u8 { + unsafe { *self.instruction_pointer } + } + + fn read_slice(&self, len: usize) -> &[u8] { + unsafe { core::slice::from_raw_parts(self.instruction_pointer, len) } + } + + fn read_offset_u16(&self, offset: isize) -> u16 { + unsafe { + read_u16( + self.instruction_pointer + // Offset for max_index that is one byte + .offset(offset), + ) + } + } +} diff --git a/substrate/frame/revive/src/vm/evm/instructions/arithmetic.rs b/substrate/frame/revive/src/vm/evm/instructions/arithmetic.rs index 7162ed1e84804..84ae34d6c66c3 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/arithmetic.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/arithmetic.rs @@ -15,112 +15,116 @@ // See the License for the specific language governing permissions and // limitations under the License. -use super::{ - i256::{i256_div, i256_mod}, - modular::Modular, -}; +pub mod i256; +use i256::{i256_div, i256_mod}; + +mod modular; +use modular::Modular; + use crate::{ vm::{ - evm::{interpreter::HaltReason, Interpreter}, + evm::{interpreter::Halt, Interpreter}, Ext, }, U256, }; +use core::ops::ControlFlow; use revm::interpreter::gas::{EXP, LOW, MID, VERYLOW}; -use sp_runtime::DispatchResult; /// Implements the ADD instruction - adds two values from stack. -pub fn add<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { +pub fn add<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { interpreter.ext.gas_meter_mut().charge_evm_gas(VERYLOW)?; let ([op1], op2) = interpreter.stack.popn_top()?; *op2 = op1.saturating_add(*op2); - Ok(()) + ControlFlow::Continue(()) } /// Implements the MUL instruction - multiplies two values from stack. -pub fn mul<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { +pub fn mul<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { interpreter.ext.gas_meter_mut().charge_evm_gas(LOW)?; let ([op1], op2) = interpreter.stack.popn_top()?; *op2 = op1.saturating_mul(*op2); - Ok(()) + ControlFlow::Continue(()) } /// Implements the SUB instruction - subtracts two values from stack. -pub fn sub<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { +pub fn sub<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { interpreter.ext.gas_meter_mut().charge_evm_gas(VERYLOW)?; let ([op1], op2) = interpreter.stack.popn_top()?; *op2 = op1.saturating_sub(*op2); - Ok(()) + ControlFlow::Continue(()) } /// Implements the DIV instruction - divides two values from stack. -pub fn div<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { +pub fn div<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { interpreter.ext.gas_meter_mut().charge_evm_gas(LOW)?; let ([op1], op2) = interpreter.stack.popn_top()?; if !op2.is_zero() { *op2 = op1 / *op2; } - Ok(()) + ControlFlow::Continue(()) } /// Implements the SDIV instruction. /// /// Performs signed division of two values from stack. -pub fn sdiv<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { +pub fn sdiv<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { interpreter.ext.gas_meter_mut().charge_evm_gas(LOW)?; let ([op1], op2) = interpreter.stack.popn_top()?; *op2 = i256_div(op1, *op2); - Ok(()) + ControlFlow::Continue(()) } /// Implements the MOD instruction. /// /// Pops two values from stack and pushes the remainder of their division. -pub fn rem<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { +pub fn rem<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { interpreter.ext.gas_meter_mut().charge_evm_gas(LOW)?; let ([op1], op2) = interpreter.stack.popn_top()?; if !op2.is_zero() { *op2 = op1 % *op2; } - Ok(()) + ControlFlow::Continue(()) } /// Implements the SMOD instruction. /// /// Performs signed modulo of two values from stack. -pub fn smod<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { +pub fn smod<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { interpreter.ext.gas_meter_mut().charge_evm_gas(LOW)?; let ([op1], op2) = interpreter.stack.popn_top()?; *op2 = i256_mod(op1, *op2); - Ok(()) + ControlFlow::Continue(()) } /// Implements the ADDMOD instruction. /// /// Pops three values from stack and pushes (a + b) % n. -pub fn addmod<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { +pub fn addmod<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { interpreter.ext.gas_meter_mut().charge_evm_gas(MID)?; let ([op1, op2], op3) = interpreter.stack.popn_top()?; *op3 = op1.add_mod(op2, *op3); - Ok(()) + ControlFlow::Continue(()) } /// Implements the MULMOD instruction. /// /// Pops three values from stack and pushes (a * b) % n. -pub fn mulmod<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { +pub fn mulmod<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { interpreter.ext.gas_meter_mut().charge_evm_gas(MID)?; let ([op1, op2], op3) = interpreter.stack.popn_top()?; *op3 = op1.mul_mod(op2, *op3); - Ok(()) + ControlFlow::Continue(()) } /// Implements the EXP instruction - exponentiates two values from stack. -pub fn exp<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { +pub fn exp<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { let ([op1], op2) = interpreter.stack.popn_top()?; - let gas_cost = exp_cost(*op2).ok_or_else(|| crate::Error::::OutOfGas)?; + let Some(gas_cost) = exp_cost(*op2) else { + return ControlFlow::Break(Halt::OutOfGas); + }; interpreter.ext.gas_meter_mut().charge_evm_gas(gas_cost)?; *op2 = op1.pow(*op2); - Ok(()) + ControlFlow::Continue(()) } /// Implements the `SIGNEXTEND` opcode as defined in the Ethereum Yellow Paper. @@ -152,7 +156,7 @@ pub fn exp<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResu /// /// Similarly, if `b == 0` then the yellow paper says the output should start with all zeros, /// then end with bits from `b`; this is equal to `y & mask` where `&` is bitwise `AND`. -pub fn signextend<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { +pub fn signextend<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { interpreter.ext.gas_meter_mut().charge_evm_gas(LOW)?; let ([ext], x) = interpreter.stack.popn_top()?; // For 31 we also don't need to do anything. @@ -163,7 +167,7 @@ pub fn signextend<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> Dispa let mask = (U256::from(1) << bit_index) - U256::from(1); *x = if bit { *x | !mask } else { *x & mask }; } - Ok(()) + ControlFlow::Continue(()) } /// `EXP` opcode cost calculation. diff --git a/substrate/frame/revive/src/vm/evm/instructions/i256.rs b/substrate/frame/revive/src/vm/evm/instructions/arithmetic/i256.rs similarity index 100% rename from substrate/frame/revive/src/vm/evm/instructions/i256.rs rename to substrate/frame/revive/src/vm/evm/instructions/arithmetic/i256.rs diff --git a/substrate/frame/revive/src/vm/evm/instructions/modular.rs b/substrate/frame/revive/src/vm/evm/instructions/arithmetic/modular.rs similarity index 100% rename from substrate/frame/revive/src/vm/evm/instructions/modular.rs rename to substrate/frame/revive/src/vm/evm/instructions/arithmetic/modular.rs diff --git a/substrate/frame/revive/src/vm/evm/instructions/bitwise.rs b/substrate/frame/revive/src/vm/evm/instructions/bitwise.rs index 2f3d9b4a72189..6ac46b3752fee 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/bitwise.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/bitwise.rs @@ -15,133 +15,135 @@ // See the License for the specific language governing permissions and // limitations under the License. -use super::i256::i256_cmp; +mod bits; + +use super::arithmetic::i256::i256_cmp; use crate::{ vm::{ - evm::{instructions::bits::Bits, Interpreter}, + evm::{interpreter::Halt, Interpreter}, Ext, }, U256, }; -use core::cmp::Ordering; +use bits::Bits; +use core::{cmp::Ordering, ops::ControlFlow}; use revm::interpreter::gas::VERYLOW; -use sp_runtime::DispatchResult; /// Implements the LT instruction - less than comparison. -pub fn lt<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { +pub fn lt<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { interpreter.ext.gas_meter_mut().charge_evm_gas(VERYLOW)?; let ([op1], op2) = interpreter.stack.popn_top()?; *op2 = if op1 < *op2 { U256::one() } else { U256::zero() }; - Ok(()) + ControlFlow::Continue(()) } /// Implements the GT instruction - greater than comparison. -pub fn gt<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { +pub fn gt<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { interpreter.ext.gas_meter_mut().charge_evm_gas(VERYLOW)?; let ([op1], op2) = interpreter.stack.popn_top()?; *op2 = if op1 > *op2 { U256::one() } else { U256::zero() }; - Ok(()) + ControlFlow::Continue(()) } /// Implements the CLZ instruction - count leading zeros. -pub fn clz<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { +pub fn clz<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { interpreter.ext.gas_meter_mut().charge_evm_gas(VERYLOW)?; let ([], op1) = interpreter.stack.popn_top()?; let leading_zeros = op1.leading_zeros(); *op1 = U256::from(leading_zeros); - Ok(()) + ControlFlow::Continue(()) } /// Implements the SLT instruction. /// /// Signed less than comparison of two values from stack. -pub fn slt<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { +pub fn slt<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { interpreter.ext.gas_meter_mut().charge_evm_gas(VERYLOW)?; let ([op1], op2) = interpreter.stack.popn_top()?; *op2 = if i256_cmp(&op1, op2) == Ordering::Less { U256::one() } else { U256::zero() }; - Ok(()) + ControlFlow::Continue(()) } /// Implements the SGT instruction. /// /// Signed greater than comparison of two values from stack. -pub fn sgt<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { +pub fn sgt<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { interpreter.ext.gas_meter_mut().charge_evm_gas(VERYLOW)?; let ([op1], op2) = interpreter.stack.popn_top()?; *op2 = if i256_cmp(&op1, op2) == Ordering::Greater { U256::one() } else { U256::zero() }; - Ok(()) + ControlFlow::Continue(()) } /// Implements the EQ instruction. /// /// Equality comparison of two values from stack. -pub fn eq<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { +pub fn eq<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { interpreter.ext.gas_meter_mut().charge_evm_gas(VERYLOW)?; let ([op1], op2) = interpreter.stack.popn_top()?; *op2 = if op1 == *op2 { U256::one() } else { U256::zero() }; - Ok(()) + ControlFlow::Continue(()) } /// Implements the ISZERO instruction. /// /// Checks if the top stack value is zero. -pub fn iszero<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { +pub fn iszero<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { interpreter.ext.gas_meter_mut().charge_evm_gas(VERYLOW)?; let ([], op1) = interpreter.stack.popn_top()?; *op1 = if op1.is_zero() { U256::one() } else { U256::zero() }; - Ok(()) + ControlFlow::Continue(()) } /// Implements the AND instruction. /// /// Bitwise AND of two values from stack. -pub fn bitand<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { +pub fn bitand<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { interpreter.ext.gas_meter_mut().charge_evm_gas(VERYLOW)?; let ([op1], op2) = interpreter.stack.popn_top()?; *op2 = op1 & *op2; - Ok(()) + ControlFlow::Continue(()) } /// Implements the OR instruction. /// /// Bitwise OR of two values from stack. -pub fn bitor<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { +pub fn bitor<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { interpreter.ext.gas_meter_mut().charge_evm_gas(VERYLOW)?; let ([op1], op2) = interpreter.stack.popn_top()?; *op2 = op1 | *op2; - Ok(()) + ControlFlow::Continue(()) } /// Implements the XOR instruction. /// /// Bitwise XOR of two values from stack. -pub fn bitxor<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { +pub fn bitxor<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { interpreter.ext.gas_meter_mut().charge_evm_gas(VERYLOW)?; let ([op1], op2) = interpreter.stack.popn_top()?; *op2 = op1 ^ *op2; - Ok(()) + ControlFlow::Continue(()) } /// Implements the NOT instruction. /// /// Bitwise NOT (negation) of the top stack value. -pub fn not<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { +pub fn not<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { interpreter.ext.gas_meter_mut().charge_evm_gas(VERYLOW)?; let ([], op1) = interpreter.stack.popn_top()?; *op1 = !*op1; - Ok(()) + ControlFlow::Continue(()) } /// Implements the BYTE instruction. /// /// Extracts a single byte from a word at a given index. -pub fn byte<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { +pub fn byte<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { interpreter.ext.gas_meter_mut().charge_evm_gas(VERYLOW)?; let ([op1], op2) = interpreter.stack.popn_top()?; @@ -152,31 +154,31 @@ pub fn byte<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchRes } else { U256::zero() }; - Ok(()) + ControlFlow::Continue(()) } /// EIP-145: Bitwise shifting instructions in EVM -pub fn shl<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { +pub fn shl<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { interpreter.ext.gas_meter_mut().charge_evm_gas(VERYLOW)?; let ([op1], op2) = interpreter.stack.popn_top()?; let shift = as_usize_saturated!(op1); *op2 = if shift < 256 { *op2 << shift } else { U256::zero() }; - Ok(()) + ControlFlow::Continue(()) } /// EIP-145: Bitwise shifting instructions in EVM -pub fn shr<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { +pub fn shr<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { interpreter.ext.gas_meter_mut().charge_evm_gas(VERYLOW)?; let ([op1], op2) = interpreter.stack.popn_top()?; let shift = as_usize_saturated!(op1); *op2 = if shift < 256 { *op2 >> shift } else { U256::zero() }; - Ok(()) + ControlFlow::Continue(()) } /// EIP-145: Bitwise shifting instructions in EVM -pub fn sar<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { +pub fn sar<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { interpreter.ext.gas_meter_mut().charge_evm_gas(VERYLOW)?; let ([op1], op2) = interpreter.stack.popn_top()?; @@ -188,7 +190,7 @@ pub fn sar<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResu } else { U256::zero() }; - Ok(()) + ControlFlow::Continue(()) } #[cfg(test)] @@ -210,7 +212,7 @@ mod tests { } #[test] - fn test_shift_left() -> DispatchResult { + fn test_shift_left() -> ControlFlow { struct TestCase { value: U256, shift: U256, @@ -328,11 +330,11 @@ mod tests { assert_eq!(res, test.expected); } - Ok(()) + ControlFlow::Continue(()) } #[test] - fn test_logical_shift_right() -> DispatchResult { + fn test_logical_shift_right() -> ControlFlow { struct TestCase { value: U256, shift: U256, @@ -449,11 +451,11 @@ mod tests { let res = interpreter.stack.pop().unwrap(); assert_eq!(res, test.expected); } - Ok(()) + ControlFlow::Continue(()) } #[test] - fn test_arithmetic_shift_right() -> DispatchResult { + fn test_arithmetic_shift_right() -> ControlFlow { struct TestCase { value: U256, shift: U256, @@ -615,11 +617,11 @@ mod tests { let res = interpreter.stack.pop().unwrap(); assert_eq!(res, test.expected); } - Ok(()) + ControlFlow::Continue(()) } #[test] - fn test_byte() -> DispatchResult { + fn test_byte() -> ControlFlow { struct TestCase { input: U256, index: usize, @@ -644,11 +646,11 @@ mod tests { let res = interpreter.stack.pop().unwrap(); assert_eq!(res, test.expected, "Failed at index: {}", test.index); } - Ok(()) + ControlFlow::Continue(()) } #[test] - fn test_clz() -> DispatchResult { + fn test_clz() -> ControlFlow { struct TestCase { value: U256, expected: U256, @@ -702,6 +704,6 @@ mod tests { test.value, test.expected, res ); } - Ok(()) + ControlFlow::Continue(()) } } diff --git a/substrate/frame/revive/src/vm/evm/instructions/bits.rs b/substrate/frame/revive/src/vm/evm/instructions/bitwise/bits.rs similarity index 100% rename from substrate/frame/revive/src/vm/evm/instructions/bits.rs rename to substrate/frame/revive/src/vm/evm/instructions/bitwise/bits.rs diff --git a/substrate/frame/revive/src/vm/evm/instructions/block_info.rs b/substrate/frame/revive/src/vm/evm/instructions/block_info.rs index f1cee1de11d74..dd707b8e6a748 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/block_info.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/block_info.rs @@ -17,80 +17,81 @@ use crate::{ vm::{ - evm::{interpreter::HaltReason, Interpreter, BASE_FEE, DIFFICULTY}, + evm::{interpreter::Halt, Interpreter, BASE_FEE, DIFFICULTY}, Ext, }, RuntimeCosts, }; +use core::ops::ControlFlow; use revm::{interpreter::gas::BASE, primitives::Address}; use sp_core::{H160, U256}; use sp_runtime::DispatchResult; /// EIP-1344: ChainID opcode -pub fn chainid<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { +pub fn chainid<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { interpreter.ext.gas_meter_mut().charge_evm_gas(BASE)?; interpreter.stack.push(interpreter.ext.chain_id())?; - Ok(()) + ControlFlow::Continue(()) } /// Implements the COINBASE instruction. /// /// Pushes the current block's beneficiary address onto the stack. -pub fn coinbase<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { +pub fn coinbase<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { interpreter.ext.gas_meter_mut().charge_evm(RuntimeCosts::BlockAuthor)?; let coinbase = interpreter.ext.block_author().unwrap_or(H160::zero()); interpreter.stack.push(coinbase)?; - Ok(()) + ControlFlow::Continue(()) } /// Implements the TIMESTAMP instruction. /// /// Pushes the current block's timestamp onto the stack. -pub fn timestamp<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { +pub fn timestamp<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { interpreter.ext.gas_meter_mut().charge_evm(RuntimeCosts::Now)?; let timestamp = interpreter.ext.now(); interpreter.stack.push(timestamp)?; - Ok(()) + ControlFlow::Continue(()) } /// Implements the NUMBER instruction. /// /// Pushes the current block number onto the stack. -pub fn block_number<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { +pub fn block_number<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { interpreter.ext.gas_meter_mut().charge_evm(RuntimeCosts::BlockNumber)?; let block_number = interpreter.ext.block_number(); interpreter.stack.push(block_number)?; - Ok(()) + ControlFlow::Continue(()) } /// Implements the DIFFICULTY/PREVRANDAO instruction. /// /// Pushes the block difficulty (pre-merge) or prevrandao (post-merge) onto the stack. -pub fn difficulty<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { +pub fn difficulty<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { interpreter.ext.gas_meter_mut().charge_evm_gas(BASE)?; interpreter.stack.push(U256::from(DIFFICULTY))?; - Ok(()) + ControlFlow::Continue(()) } /// Implements the GASLIMIT instruction. /// /// Pushes the current block's gas limit onto the stack. -pub fn gaslimit<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { +pub fn gaslimit<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { interpreter.ext.gas_meter_mut().charge_evm(RuntimeCosts::GasLimit)?; let gas_limit = interpreter.ext.gas_limit(); interpreter.stack.push(U256::from(gas_limit))?; - Ok(()) + ControlFlow::Continue(()) } /// EIP-3198: BASEFEE opcode -pub fn basefee<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { +pub fn basefee<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { interpreter.ext.gas_meter_mut().charge_evm(RuntimeCosts::BaseFee)?; interpreter.stack.push(BASE_FEE)?; - Ok(()) + ControlFlow::Continue(()) } /// EIP-7516: BLOBBASEFEE opcode is not supported -pub fn blob_basefee<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { - Err(HaltReason::NotActivated.into()) +pub fn blob_basefee<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { + ControlFlow::Break(Halt::NotActivated) } diff --git a/substrate/frame/revive/src/vm/evm/instructions/contract.rs b/substrate/frame/revive/src/vm/evm/instructions/contract.rs index e96588fd9b0e9..1837bcab9ecb3 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/contract.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/contract.rs @@ -17,130 +17,123 @@ mod call_helpers; -use super::{utility::IntoAddress, Context}; +use super::utility::IntoAddress; use crate::{ - vm::{evm::U256Converter, Ext, RuntimeCosts}, - Pallet, + vm::{ + evm::{interpreter::Halt, util::as_usize_or_halt_with, Interpreter}, + Ext, RuntimeCosts, + }, + Code, Pallet, Weight, H160, U256, }; -use alloc::boxed::Box; pub use call_helpers::{calc_call_gas, get_memory_input_and_out_ranges}; +use core::{ + cmp::min, + ops::{ControlFlow, Range}, +}; use revm::{ context_interface::CreateScheme, - interpreter::{ - gas as revm_gas, - interpreter_action::{ - CallInputs, CallScheme, CallValue, CreateInputs, FrameInput, InterpreterAction, - }, - interpreter_types::{LoopControl, RuntimeFlag, StackTr}, - CallInput, InstructionResult, - }, - primitives::{Address, Bytes, B256, U256}, + interpreter::{interpreter_action::CallScheme, interpreter_types::LoopControl}, }; /// Implements the CREATE/CREATE2 instruction. /// /// Creates a new contract with provided bytecode. -pub fn create<'ext, const IS_CREATE2: bool, E: Ext>(context: Context<'_, 'ext, E>) { - if context.interpreter.extend.is_read_only() { - context.interpreter.halt(InstructionResult::Revert); - return; +pub fn create<'ext, const IS_CREATE2: bool, E: Ext>( + interpreter: &mut Interpreter<'ext, E>, +) -> ControlFlow { + if interpreter.ext.is_read_only() { + return ControlFlow::Break(Halt::StateChangeDuringStaticCall); } - popn!([value, code_offset, len], context.interpreter); - let len = as_usize_or_fail!(context.interpreter, len); + let [value, code_offset, len] = interpreter.stack.popn()?; + let len = as_usize_or_halt_with(len, || Halt::InvalidOperandOOG)?; // TODO: We do not charge for the new code in storage. When implementing the new gas: // Introduce EthInstantiateWithCode, which shall charge gas based on the code length. // See #9577 for more context. - let val = crate::U256::from_revm_u256(&value); - gas!( - context.interpreter, - RuntimeCosts::Instantiate { - input_data_len: len as u32, // We charge for initcode execution - balance_transfer: Pallet::::has_balance(val), - dust_transfer: Pallet::::has_dust(val), - } - ); + interpreter.ext.gas_meter_mut().charge_evm(RuntimeCosts::Instantiate { + input_data_len: len as u32, // We charge for initcode execution + balance_transfer: Pallet::::has_balance(value), + dust_transfer: Pallet::::has_dust(value), + })?; - let mut code = Bytes::new(); + let mut code = Vec::new(); if len != 0 { // EIP-3860: Limit initcode if len > revm::primitives::eip3860::MAX_INITCODE_SIZE { - context.interpreter.halt(InstructionResult::CreateInitCodeSizeLimit); - return; + return ControlFlow::Break(Halt::CreateInitCodeSizeLimit); } - let code_offset = as_usize_or_fail!(context.interpreter, code_offset); - resize_memory!(context.interpreter, code_offset, len); - code = - Bytes::copy_from_slice(context.interpreter.memory.slice_len(code_offset, len).as_ref()); + let code_offset = as_usize_or_halt_with(code_offset, || Halt::InvalidOperandOOG)?; + interpreter.memory.resize(code_offset, len)?; + code = interpreter.memory.slice_len(code_offset, len).to_vec(); } - // EIP-1014: Skinny CREATE2 - let scheme = if IS_CREATE2 { - popn!([salt], context.interpreter); - CreateScheme::Create2 { salt } + let salt = if IS_CREATE2 { + let [salt] = interpreter.stack.popn()?; + Some(salt.to_big_endian()) } else { - gas_legacy!(context.interpreter, revm_gas::CREATE); - CreateScheme::Create + None }; - // Call host to interact with target contract - context - .interpreter - .bytecode - .set_action(InterpreterAction::NewFrame(FrameInput::Create(Box::new(CreateInputs { - caller: context.interpreter.extend.address().0.into(), - scheme, - value, - init_code: code, - gas_limit: u64::MAX, // TODO: set the right limit - })))); + let call_result = interpreter.ext.instantiate( + Weight::from_parts(u64::MAX, u64::MAX), // TODO: set the right limit + U256::MAX, + Code::Upload(code.to_vec()), + value, + vec![], + salt.as_ref(), + ); + + match call_result { + Ok(address) => { + let return_value = interpreter.ext.last_frame_output(); + if return_value.did_revert() { + // Contract creation reverted — return data must be propagated + let len = return_value.data.len() as u32; + interpreter.ext.gas_meter_mut().charge_evm(RuntimeCosts::CopyToContract(len))?; + interpreter.stack.push(U256::zero()) + } else { + // Otherwise clear it. Note that RETURN opcode should abort. + *interpreter.ext.last_frame_output_mut() = Default::default(); + interpreter.stack.push(address) + } + }, + Err(err) => { + interpreter.stack.push(U256::zero())?; + return err.into() + }, + } } /// Implements the CALL instruction. /// /// Message call with value transfer to another account. -pub fn call<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - popn!([local_gas_limit, to, value], context.interpreter); +pub fn call<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { + let [local_gas_limit, to, value] = interpreter.stack.popn()?; let to = to.into_address(); // TODO: Max gas limit is not possible in a real Ethereum situation. This issue will be // addressed in #9577. let _local_gas_limit = u64::try_from(local_gas_limit).unwrap_or(u64::MAX); let has_transfer = !value.is_zero(); - if context.interpreter.runtime_flag.is_static() && has_transfer { - context.interpreter.halt(InstructionResult::CallNotAllowedInsideStatic); - return; + if interpreter.ext.is_read_only() && has_transfer { + return ControlFlow::Break(Halt::CallNotAllowedInsideStatic); } - let Some((input, return_memory_offset)) = get_memory_input_and_out_ranges(context.interpreter) - else { - return; - }; - + let (input, return_memory_offset) = get_memory_input_and_out_ranges(interpreter)?; let scheme = CallScheme::Call; - let input = CallInput::SharedBuffer(input); - - let Some(gas_limit) = calc_call_gas(context.interpreter, to, scheme, input.len(), value) else { - return; - }; - - // Call host to interact with target contract - context - .interpreter - .bytecode - .set_action(InterpreterAction::NewFrame(FrameInput::Call(Box::new(CallInputs { - input, - gas_limit, - target_address: to, - caller: Address::default(), - bytecode_address: to, - value: CallValue::Transfer(value), - scheme, - is_static: context.interpreter.runtime_flag.is_static(), - return_memory_offset, - })))); + let gas_limit = calc_call_gas(interpreter, to, scheme, input.len(), value)?; + + run_call( + interpreter, + to, + interpreter.memory.slice(input).to_vec(), + scheme, + Weight::from_parts(gas_limit, u64::MAX), + value, + return_memory_offset, + ) } /// Implements the CALLCODE instruction. @@ -149,86 +142,109 @@ pub fn call<'ext, E: Ext>(context: Context<'_, 'ext, E>) { /// /// Isn't supported yet: [`solc` no longer emits it since Solidity v0.3.0 in 2016] /// (https://soliditylang.org/blog/2016/03/11/solidity-0.3.0-release-announcement/). -pub fn call_code<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - context.interpreter.halt(revm::interpreter::InstructionResult::NotActivated); +pub fn call_code<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { + ControlFlow::Break(Halt::NotActivated) } /// Implements the DELEGATECALL instruction. /// /// Message call with alternative account's code but same sender and value. -pub fn delegate_call<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - popn!([local_gas_limit, to], context.interpreter); - let to = Address::from_word(B256::from(to)); +pub fn delegate_call<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { + let [local_gas_limit, to] = interpreter.stack.popn()?; + let to = to.into_address(); // TODO: Max gas limit is not possible in a real Ethereum situation. This issue will be // addressed in #9577. let _local_gas_limit = u64::try_from(local_gas_limit).unwrap_or(u64::MAX); - let Some((input, return_memory_offset)) = get_memory_input_and_out_ranges(context.interpreter) - else { - return; - }; - + let (input, return_memory_offset) = get_memory_input_and_out_ranges(interpreter)?; let scheme = CallScheme::DelegateCall; - let input = CallInput::SharedBuffer(input); - - let Some(gas_limit) = calc_call_gas(context.interpreter, to, scheme, input.len(), U256::ZERO) - else { - return; - }; - - // Call host to interact with target contract - context - .interpreter - .bytecode - .set_action(InterpreterAction::NewFrame(FrameInput::Call(Box::new(CallInputs { - input, - gas_limit, - target_address: Default::default(), - caller: Default::default(), - bytecode_address: to, - value: CallValue::Apparent(Default::default()), - scheme, - is_static: context.interpreter.runtime_flag.is_static(), - return_memory_offset, - })))); + let value = U256::zero(); + let gas_limit = calc_call_gas(interpreter, to, scheme, input.len(), value)?; + + run_call( + interpreter, + to, + interpreter.memory.slice(input).to_vec(), + scheme, + Weight::from_parts(gas_limit, u64::MAX), + value, + return_memory_offset, + ) } /// Implements the STATICCALL instruction. /// /// Static message call (cannot modify state). -pub fn static_call<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - popn!([local_gas_limit, to], context.interpreter); - let to = Address::from_word(B256::from(to)); +pub fn static_call<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { + let [local_gas_limit, to] = interpreter.stack.popn()?; + let to = to.into_address(); // TODO: Max gas limit is not possible in a real Ethereum situation. This issue will be // addressed in #9577. let _local_gas_limit = u64::try_from(local_gas_limit).unwrap_or(u64::MAX); - - let Some((input, return_memory_offset)) = get_memory_input_and_out_ranges(context.interpreter) - else { - return; - }; - + let (input, return_memory_offset) = get_memory_input_and_out_ranges(interpreter)?; let scheme = CallScheme::StaticCall; - let input = CallInput::SharedBuffer(input); + let value = U256::zero(); + let gas_limit = calc_call_gas(interpreter, to, scheme, input.len(), value)?; + + run_call( + interpreter, + to, + interpreter.memory.slice(input).to_vec(), + scheme, + Weight::from_parts(gas_limit, u64::MAX), + value, + return_memory_offset, + ) +} - let Some(gas_limit) = calc_call_gas(context.interpreter, to, scheme, input.len(), U256::ZERO) - else { - return; +fn run_call<'a, E: Ext>( + interpreter: &mut Interpreter<'a, E>, + callee: H160, + input: Vec, + scheme: CallScheme, + gas_limit: Weight, + value: U256, + return_memory_offset: Range, +) -> ControlFlow { + let call_result = match scheme { + CallScheme::Call | CallScheme::StaticCall => interpreter.ext.call( + gas_limit, + U256::MAX, + &callee, + value, + input, + true, + scheme.is_static_call(), + ), + CallScheme::DelegateCall => + interpreter.ext.delegate_call(gas_limit, U256::MAX, callee, input), + CallScheme::CallCode => { + unreachable!() + }, }; - // Call host to interact with target contract - context - .interpreter - .bytecode - .set_action(InterpreterAction::NewFrame(FrameInput::Call(Box::new(CallInputs { - input, - gas_limit, - target_address: to, - caller: Default::default(), - bytecode_address: to, - value: CallValue::Transfer(U256::ZERO), - scheme, - is_static: true, - return_memory_offset, - })))); + match call_result { + Ok(()) => { + let mem_start = return_memory_offset.start; + let mem_length = return_memory_offset.len(); + let returned_len = interpreter.ext.last_frame_output().data.len(); + let target_len = min(mem_length, returned_len); + + // success or revert + interpreter + .ext + .gas_meter_mut() + .charge_evm(RuntimeCosts::CopyToContract(target_len as u32))?; + + let return_value = interpreter.ext.last_frame_output(); + let return_data = &return_value.data; + let did_revert = return_value.did_revert(); + interpreter.memory.set(mem_start, &return_data[..target_len]); + interpreter.stack.push(U256::from(!did_revert as u8)) + }, + Err(err) => { + interpreter.stack.push(U256::zero())?; + err.into() + }, + } } diff --git a/substrate/frame/revive/src/vm/evm/instructions/contract/call_helpers.rs b/substrate/frame/revive/src/vm/evm/instructions/contract/call_helpers.rs index 3a3d7284d2786..9ed8a2b6b2c71 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/contract/call_helpers.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/contract/call_helpers.rs @@ -17,102 +17,88 @@ use crate::{ precompiles::{All as AllPrecompiles, Precompiles}, - vm::{evm::U256Converter, Ext}, - Pallet, RuntimeCosts, -}; -use core::ops::Range; -use revm::{ - interpreter::{ - interpreter_action::CallScheme, - interpreter_types::{MemoryTr, StackTr}, - Interpreter, + vm::{ + evm::{interpreter::Halt, util::as_usize_or_halt_with, Interpreter}, + Ext, }, - primitives::{Address, U256}, + Pallet, RuntimeCosts, }; -use sp_core::H160; +use core::ops::{ControlFlow, Range}; +use revm::interpreter::interpreter_action::CallScheme; +use sp_core::{H160, U256}; /// Gets memory input and output ranges for call instructions. -#[inline] pub fn get_memory_input_and_out_ranges<'a, E: Ext>( - interpreter: &mut Interpreter>, -) -> Option<(Range, Range)> { - popn!([in_offset, in_len, out_offset, out_len], interpreter, None); - - let mut in_range = resize_memory(interpreter, in_offset, in_len)?; - - if !in_range.is_empty() { - let offset = <_ as MemoryTr>::local_memory_offset(&interpreter.memory); - in_range = in_range.start.saturating_add(offset)..in_range.end.saturating_add(offset); - } - + interpreter: &mut Interpreter<'a, E>, +) -> ControlFlow, Range)> { + let [in_offset, in_len, out_offset, out_len] = interpreter.stack.popn()?; + let in_range = resize_memory(interpreter, in_offset, in_len)?; let ret_range = resize_memory(interpreter, out_offset, out_len)?; - Some((in_range, ret_range)) + ControlFlow::Continue((in_range, ret_range)) } /// Resize memory and return range of memory. /// If `len` is 0 dont touch memory and return `usize::MAX` as offset and 0 as length. -#[inline] pub fn resize_memory<'a, E: Ext>( - interpreter: &mut Interpreter>, + interpreter: &mut Interpreter<'a, E>, offset: U256, len: U256, -) -> Option> { - let len = as_usize_or_fail_ret!(interpreter, len, None); - let offset = if len != 0 { - let offset = as_usize_or_fail_ret!(interpreter, offset, None); - resize_memory!(interpreter, offset, len, None); - offset +) -> ControlFlow> { + let len = as_usize_or_halt_with(len, || Halt::InvalidOperandOOG)?; + if len != 0 { + let offset = as_usize_or_halt_with(offset, || Halt::InvalidOperandOOG)?; + interpreter.memory.resize(offset, len)?; + ControlFlow::Continue(offset..offset + len) } else { - usize::MAX //unrealistic value so we are sure it is not used - }; - Some(offset..offset + len) + //unrealistic value so we are sure it is not used + ControlFlow::Continue(usize::MAX..usize::MAX) + } } /// Calculates gas cost and limit for call instructions. -#[inline] pub fn calc_call_gas<'a, E: Ext>( - interpreter: &mut Interpreter>, - callee: Address, + interpreter: &mut Interpreter<'a, E>, + callee: H160, scheme: CallScheme, input_len: usize, value: U256, -) -> Option { - let callee: H160 = callee.0 .0.into(); +) -> ControlFlow { let precompile = >::get::(&callee.as_fixed_bytes()); match precompile { Some(precompile) => { // Base cost depending on contract info - let base_cost = if precompile.has_contract_info() { + interpreter.ext.gas_meter_mut().charge_evm(if precompile.has_contract_info() { RuntimeCosts::PrecompileWithInfoBase } else { RuntimeCosts::PrecompileBase - }; - gas!(interpreter, base_cost, None); + })?; // Cost for decoding input - gas!(interpreter, RuntimeCosts::PrecompileDecode(input_len as u32), None); + interpreter + .ext + .gas_meter_mut() + .charge_evm(RuntimeCosts::PrecompileDecode(input_len as u32))?; }, None => { // Regular CALL / DELEGATECALL base cost / CALLCODE not supported - let base_cost = if scheme.is_delegate_call() { + interpreter.ext.gas_meter_mut().charge_evm(if scheme.is_delegate_call() { RuntimeCosts::DelegateCallBase } else { RuntimeCosts::CallBase - }; - gas!(interpreter, base_cost, None); + })?; - gas!(interpreter, RuntimeCosts::CopyFromContract(input_len as u32), None); + interpreter + .ext + .gas_meter_mut() + .charge_evm(RuntimeCosts::CopyFromContract(input_len as u32))?; }, }; if !value.is_zero() { - gas!( - interpreter, - RuntimeCosts::CallTransferSurcharge { - dust_transfer: Pallet::::has_dust(crate::U256::from_revm_u256(&value)), - }, - None - ); + interpreter.ext.gas_meter_mut().charge_evm(RuntimeCosts::CallTransferSurcharge { + dust_transfer: Pallet::::has_dust(value), + }); } - Some(u64::MAX) // TODO: Set the right gas limit + + ControlFlow::Continue(u64::MAX) // TODO: Set the right gas limit } diff --git a/substrate/frame/revive/src/vm/evm/instructions/control.rs b/substrate/frame/revive/src/vm/evm/instructions/control.rs index 4a19475f4ac31..7c6385945d0b8 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/control.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/control.rs @@ -17,79 +17,74 @@ use crate::{ vm::{ - evm::{ - interpreter::{HaltReason, OutOfGasError}, - Interpreter, - }, + evm::{interpreter::Halt, util::as_usize_or_halt_with, Interpreter}, Ext, }, - RuntimeCosts, + RuntimeCosts, U256, }; +use core::ops::ControlFlow; use revm::{ interpreter::{ gas::{BASE, HIGH, JUMPDEST, MID}, - interpreter_action::InterpreterAction, - interpreter_types::{Jumps, LoopControl, StackTr}, - InstructionResult, + interpreter_types::{Immediates, Jumps}, }, primitives::Bytes, }; -use sp_core::U256; -use sp_runtime::DispatchResult; /// Implements the JUMP instruction. /// /// Unconditional jump to a valid destination. -pub fn jump<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { +pub fn jump<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { interpreter.ext.gas_meter_mut().charge_evm_gas(MID)?; let [target] = interpreter.stack.popn()?; jump_inner(interpreter, target)?; - Ok(()) + ControlFlow::Continue(()) } /// Implements the JUMPI instruction. /// /// Conditional jump to a valid destination if condition is true. -pub fn jumpi<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { +pub fn jumpi<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { interpreter.ext.gas_meter_mut().charge_evm_gas(HIGH)?; let [target, cond] = interpreter.stack.popn()?; if !cond.is_zero() { jump_inner(interpreter, target)?; } - Ok(()) + ControlFlow::Continue(()) } #[inline(always)] /// Internal helper function for jump operations. /// /// Validates jump target and performs the actual jump. -fn jump_inner(interpreter: &mut Interpreter<'_, E>, target: U256) -> DispatchResult { - let target = as_usize_checked(target).ok_or(HaltReason::InvalidJump)?; +fn jump_inner(interpreter: &mut Interpreter<'_, E>, target: U256) -> ControlFlow { + let target = as_usize_or_halt_with(target, || Halt::InvalidJump)?; + if !interpreter.bytecode.is_valid_legacy_jump(target) { - return Err(HaltReason::InvalidJump.into()); + return ControlFlow::Break(Halt::InvalidJump); } // SAFETY: `is_valid_jump` ensures that `dest` is in bounds. interpreter.bytecode.absolute_jump(target); - Ok(()) + ControlFlow::Continue(()) } /// Implements the JUMPDEST instruction. /// /// Marks a valid destination for jump operations. -pub fn jumpdest<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { +pub fn jumpdest<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { interpreter.ext.gas_meter_mut().charge_evm_gas(JUMPDEST)?; - Ok(()) + ControlFlow::Continue(()) } /// Implements the PC instruction. /// /// Pushes the current program counter onto the stack. -pub fn pc<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { +pub fn pc<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { interpreter.ext.gas_meter_mut().charge_evm_gas(BASE)?; // - 1 because we have already advanced the instruction pointer in `Interpreter::step` interpreter.stack.push(U256::from(interpreter.bytecode.pc() - 1))?; - Ok(()) + ControlFlow::Continue(()) } #[inline] @@ -98,83 +93,47 @@ pub fn pc<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResul /// Handles memory data retrieval and sets the return action. fn return_inner( interpreter: &mut Interpreter<'_, E>, - instruction_result: InstructionResult, -) -> DispatchResult { + halt: impl Fn(Vec) -> Halt, +) -> ControlFlow { // Zero gas cost let [offset, len] = interpreter.stack.popn()?; - let len = as_usize_checked(len).ok_or(HaltReason::OutOfGas(OutOfGasError::InvalidOperand))?; + let len = as_usize_or_halt_with(len, || Halt::InvalidOperandOOG)?; + // Important: Offset must be ignored if len is zeros - let mut output = Bytes::default(); + let mut output = Default::default(); if len != 0 { - let offset = - as_usize_checked(offset).ok_or(HaltReason::OutOfGas(OutOfGasError::InvalidOperand))?; - resize_memory_checked(interpreter, offset, len)?; - output = interpreter.memory.slice_len(offset, len).to_vec().into() - } - - todo!(); - // interpreter.bytecode.set_action(InterpreterAction::new_return( - // instruction_result, - // output, - // interpreter.gas, - // )); - // Ok(()) -} - -/// Helper function to resize memory with proper error handling -#[inline] -fn resize_memory_checked( - interpreter: &mut Interpreter<'_, E>, - offset: usize, - len: usize, -) -> DispatchResult { - let current_len = interpreter.memory.size(); - let target_len = revm::interpreter::num_words(offset.saturating_add(len)) * 32; - if target_len as u32 > crate::limits::code::BASELINE_MEMORY_LIMIT { - log::debug!(target: crate::LOG_TARGET, "check memory bounds failed: offset={} target_len={target_len} current_len={current_len}", offset); - return Err(HaltReason::OutOfGas(OutOfGasError::Memory).into()); + let offset = as_usize_or_halt_with(offset, || Halt::InvalidOperandOOG)?; + interpreter.memory.resize(offset, len)?; + output = interpreter.memory.slice_len(offset, len).to_vec() } - if target_len > current_len { - interpreter.memory.resize(target_len); - } - Ok(()) + ControlFlow::Break(halt(output)) } /// Implements the RETURN instruction. /// /// Halts execution and returns data from memory. -pub fn ret<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { - return_inner(interpreter, InstructionResult::Return) +pub fn ret<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { + return_inner(interpreter, Halt::Return) } /// EIP-140: REVERT instruction -pub fn revert<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { - return_inner(interpreter, InstructionResult::Revert) +pub fn revert<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { + return_inner(interpreter, Halt::Revert) } /// Stop opcode. This opcode halts the execution. -pub fn stop<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { - return_inner(interpreter, InstructionResult::Stop) +pub fn stop<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { + return_inner(interpreter, |_| Halt::Stop) } /// Invalid opcode. This opcode halts the execution. -pub fn invalid<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { +pub fn invalid<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { interpreter.ext.gas_meter_mut().consume_all(); - Err(HaltReason::InvalidFEOpcode.into()) + ControlFlow::Break(Halt::InvalidFEOpcode) } /// Unknown opcode. This opcode halts the execution. -pub fn unknown<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> DispatchResult { - Err(HaltReason::OpcodeNotFound.into()) -} - -/// Helper function to convert U256 to usize, checking for overflow -fn as_usize_checked(value: U256) -> Option { - let limbs = value.0; - if (limbs[0] > usize::MAX as u64) | (limbs[1] != 0) | (limbs[2] != 0) | (limbs[3] != 0) { - None - } else { - Some(limbs[0] as usize) - } +pub fn unknown<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { + ControlFlow::Break(Halt::OpcodeNotFound) } diff --git a/substrate/frame/revive/src/vm/evm/instructions/mod.rs b/substrate/frame/revive/src/vm/evm/instructions/mod.rs index 7a9990acb09f1..f2d143059c97d 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/mod.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/mod.rs @@ -19,25 +19,23 @@ use super::interpreter::Interpreter; use crate::vm::Ext; -mod bits; -mod modular; #[macro_use] mod macros; -// /// Arithmetic operations (ADD, SUB, MUL, DIV, etc.). +/// Arithmetic operations (ADD, SUB, MUL, DIV, etc.). mod arithmetic; /// Bitwise operations (AND, OR, XOR, NOT, etc.). mod bitwise; /// Block information instructions (COINBASE, TIMESTAMP, etc.). mod block_info; -// /// Contract operations (CALL, CREATE, DELEGATECALL, etc.). -// mod contract; +/// Contract operations (CALL, CREATE, DELEGATECALL, etc.). +mod contract; /// Control flow instructions (JUMP, JUMPI, REVERT, etc.). mod control; // /// Host environment interactions (SLOAD, SSTORE, LOG, etc.). // mod host; -/// Signed 256-bit integer operations. -mod i256; +// /// Signed 256-bit integer operations. +// mod i256; // /// Memory operations (MLOAD, MSTORE, MSIZE, etc.). // mod memory; // /// Stack operations (PUSH, POP, DUP, SWAP, etc.). @@ -46,8 +44,8 @@ mod i256; // mod system; // /// Transaction information instructions (ORIGIN, GASPRICE, etc.). // mod tx_info; -// /// Utility functions and helpers for instruction implementation. -// mod utility; +/// Utility functions and helpers for instruction implementation. +mod utility; // // /// Returns the instruction table for the given spec. // pub const fn instruction_table<'a, E: Ext>() -> [Instruction, DummyHost>; diff --git a/substrate/frame/revive/src/vm/evm/instructions/utility.rs b/substrate/frame/revive/src/vm/evm/instructions/utility.rs index 62f77674cf4ec..dbefdb4e88102 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/utility.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/utility.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use revm::primitives::{Address, B256, U256}; +use sp_core::{H160, U256}; /// Pushes an arbitrary length slice of bytes onto the stack, padding the last word with zeros /// if necessary. @@ -23,106 +23,73 @@ use revm::primitives::{Address, B256, U256}; /// # Panics /// /// Panics if slice is longer than 32 bytes. -#[inline] -pub fn cast_slice_to_u256(slice: &[u8], dest: &mut U256) { - if slice.is_empty() { - return; - } - assert!(slice.len() <= 32, "slice too long"); - - let n_words = slice.len().div_ceil(32); - - // SAFETY: Length checked above. - unsafe { - //let dst = self.data.as_mut_ptr().add(self.data.len()).cast::(); - //self.data.set_len(new_len); - let dst = dest.as_limbs_mut().as_mut_ptr(); - - let mut i = 0; - - // Write full words - let words = slice.chunks_exact(32); - let partial_last_word = words.remainder(); - for word in words { - // Note: We unroll `U256::from_be_bytes` here to write directly into the buffer, - // instead of creating a 32 byte array on the stack and then copying it over. - for l in word.rchunks_exact(8) { - dst.add(i).write(u64::from_be_bytes(l.try_into().unwrap())); - i += 1; - } - } - - if partial_last_word.is_empty() { - return; - } - - // Write limbs of partial last word - let limbs = partial_last_word.rchunks_exact(8); - let partial_last_limb = limbs.remainder(); - for l in limbs { - dst.add(i).write(u64::from_be_bytes(l.try_into().unwrap())); - i += 1; - } - - // Write partial last limb by padding with zeros - if !partial_last_limb.is_empty() { - let mut tmp = [0u8; 8]; - tmp[8 - partial_last_limb.len()..].copy_from_slice(partial_last_limb); - dst.add(i).write(u64::from_be_bytes(tmp)); - i += 1; - } - - debug_assert_eq!(i.div_ceil(4), n_words, "wrote too much"); - - // Zero out upper bytes of last word - let m = i % 4; // 32 / 8 - if m != 0 { - dst.add(i).write_bytes(0, 4 - m); - } - } -} - -/// Trait for converting types into U256 values. -pub trait IntoU256 { - /// Converts the implementing type into a U256 value. - fn into_u256(self) -> U256; -} - -impl IntoU256 for Address { - fn into_u256(self) -> U256 { - self.into_word().into_u256() - } -} - -impl IntoU256 for B256 { - fn into_u256(self) -> U256 { - U256::from_be_bytes(self.0) - } -} +// pub fn cast_slice_to_u256(slice: &[u8], dest: &mut U256) { +// use revm::primitives::{Address, B256, U256}; +// if slice.is_empty() { +// return; +// } +// assert!(slice.len() <= 32, "slice too long"); +// +// let n_words = slice.len().div_ceil(32); +// +// // SAFETY: Length checked above. +// unsafe { +// //let dst = self.data.as_mut_ptr().add(self.data.len()).cast::(); +// //self.data.set_len(new_len); +// let dst = dest.as_limbs_mut().as_mut_ptr(); +// +// let mut i = 0; +// +// // Write full words +// let words = slice.chunks_exact(32); +// let partial_last_word = words.remainder(); +// for word in words { +// // Note: We unroll `U256::from_be_bytes` here to write directly into the buffer, +// // instead of creating a 32 byte array on the stack and then copying it over. +// for l in word.rchunks_exact(8) { +// dst.add(i).write(u64::from_be_bytes(l.try_into().unwrap())); +// i += 1; +// } +// } +// +// if partial_last_word.is_empty() { +// return; +// } +// +// // Write limbs of partial last word +// let limbs = partial_last_word.rchunks_exact(8); +// let partial_last_limb = limbs.remainder(); +// for l in limbs { +// dst.add(i).write(u64::from_be_bytes(l.try_into().unwrap())); +// i += 1; +// } +// +// // Write partial last limb by padding with zeros +// if !partial_last_limb.is_empty() { +// let mut tmp = [0u8; 8]; +// tmp[8 - partial_last_limb.len()..].copy_from_slice(partial_last_limb); +// dst.add(i).write(u64::from_be_bytes(tmp)); +// i += 1; +// } +// +// debug_assert_eq!(i.div_ceil(4), n_words, "wrote too much"); +// +// // Zero out upper bytes of last word +// let m = i % 4; // 32 / 8 +// if m != 0 { +// dst.add(i).write_bytes(0, 4 - m); +// } +// } +// } /// Trait for converting types into Address values. pub trait IntoAddress { /// Converts the implementing type into an Address value. - fn into_address(self) -> Address; + fn into_address(self) -> H160; } impl IntoAddress for U256 { - fn into_address(self) -> Address { - Address::from_word(B256::from(self.to_be_bytes())) - } -} - -#[cfg(test)] -mod tests { - use revm::primitives::address; - - use super::*; - - #[test] - fn test_into_u256() { - let addr = address!("0x0000000000000000000000000000000000000001"); - let u256 = addr.into_u256(); - assert_eq!(u256, U256::from(0x01)); - assert_eq!(u256.into_address(), addr); + fn into_address(self) -> H160 { + H160::from_slice(&self.to_big_endian()[12..]) } } diff --git a/substrate/frame/revive/src/vm/evm/interpreter.rs b/substrate/frame/revive/src/vm/evm/interpreter.rs index 2fadb20042e10..273b61d64bc23 100644 --- a/substrate/frame/revive/src/vm/evm/interpreter.rs +++ b/substrate/frame/revive/src/vm/evm/interpreter.rs @@ -1,175 +1,64 @@ //! Custom EVM interpreter implementation using sp_core types -use crate::{vm::{ - evm::{memory::Memory, stack::Stack}, - Ext, -}, DispatchError, Error}; +use super::ExtBytecode; +use crate::{ + vm::{ + evm::{memory::Memory, stack::Stack}, + Ext, + }, + DispatchError, Error, +}; use alloc::vec::Vec; -use core::marker::PhantomData; -use revm::interpreter::interpreter::ExtBytecode; +use core::ops::ControlFlow; -/// Out of gas error variants from revm -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum OutOfGasError { - /// Basic out of gas error - Basic, - /// Out of gas during memory operations - Memory, - /// Out of gas due to memory limit exceeded - MemoryLimit, - /// Out of gas in precompile execution - Precompile, - /// Out of gas with invalid operand - InvalidOperand, - /// Out of gas in reentrancy sentry - ReentrancySentry, -} +#[derive(Debug, PartialEq)] +pub enum Halt { + // Successful termination with output + Return(Vec), // InstructionResult::Return + Revert(Vec), // InstructionResult::Revert -/// HaltReason enum from revm indicating that the EVM has experienced an exceptional halt -/// This causes execution to immediately end with all gas being consumed -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum HaltReason { - /// Out of gas error with specific variant - OutOfGas(OutOfGasError), - /// Opcode not found error - OpcodeNotFound, - /// Invalid FE opcode error - InvalidFEOpcode, - /// Invalid jump destination - InvalidJump, - /// The feature or opcode is not activated in hardfork - NotActivated, - /// Attempting to pop a value from an empty stack - StackUnderflow, - /// Attempting to push a value onto a full stack - StackOverflow, - /// Invalid memory or storage offset - OutOfOffset, - /// Address collision during contract creation - CreateCollision, - /// Error in precompile execution - PrecompileError, - /// Nonce overflow error - NonceOverflow, - /// Contract size limit exceeded during creation - CreateContractSizeLimit, - /// Contract creation starting with EF prefix - CreateContractStartingWithEF, - /// Init code size limit exceeded - CreateInitCodeSizeLimit, - /// Payment overflow error - OverflowPayment, - /// State change attempted during static call - StateChangeDuringStaticCall, - /// Call not allowed inside static context - CallNotAllowedInsideStatic, - /// Insufficient funds for operation - OutOfFunds, - /// Maximum call depth exceeded - CallTooDeep, -} + // Successful termination without output + Stop, // InstructionResult::Stop + SelfDestruct, // InstructionResult::SelfDestruct -impl HaltReason { - /// Returns `true` if this is an out of gas error - pub const fn is_out_of_gas(&self) -> bool { - matches!(self, Self::OutOfGas(_)) - } + // Resource limit errors + OutOfGas, // InstructionResult::OutOfGas + StackOverflow, // InstructionResult::StackOverflow + StackUnderflow, // InstructionResult::StackUnderflow + MemoryOOG, // InstructionResult::MemoryOOG + InvalidOperandOOG, - /// Returns the inner OutOfGasError if this is an OutOfGas variant - pub const fn as_out_of_gas(&self) -> Option { - match self { - Self::OutOfGas(err) => Some(*err), - _ => None, - } - } -} + // Invalid operation errors + InvalidJump, // InstructionResult::InvalidJump + OpcodeNotFound, // InstructionResult::OpcodeNotFound + InvalidFEOpcode, // InstructionResult::InvalidFEOpcode -/// Convert from Error to HaltReason -/// This reverses the mapping logic from the commented `instruction_result_into_exec_error` function -impl From> for HaltReason { - fn from(error: Error) -> Self { - match error { - Error::OutOfGas => Self::OutOfGas(OutOfGasError::Basic), - Error::StaticMemoryTooLarge => Self::OutOfGas(OutOfGasError::MemoryLimit), - Error::InvalidInstruction => Self::OpcodeNotFound, - Error::StateChangeDenied => Self::StateChangeDuringStaticCall, - Error::ContractTrapped => Self::PrecompileError, - Error::OutOfBounds => Self::OutOfOffset, - Error::DuplicateContract => Self::CreateCollision, - Error::BalanceConversionFailed => Self::OverflowPayment, - Error::MaxCallDepthReached => Self::CallTooDeep, - Error::TransferFailed => Self::OutOfFunds, - // Map other common error cases - Error::CodeRejected => Self::PrecompileError, - Error::ValueTooLarge => Self::OverflowPayment, - Error::StorageDepositLimitExhausted => Self::OutOfGas(OutOfGasError::Basic), - Error::ExecutionFailed => Self::PrecompileError, - Error::InvalidCallFlags => Self::InvalidFEOpcode, - Error::DecodingFailed => Self::PrecompileError, - Error::InvalidSyscall => Self::OpcodeNotFound, - // Map specific error cases that weren't in the original commented logic - Error::ContractNotFound => Self::PrecompileError, - Error::CodeNotFound => Self::PrecompileError, - Error::CodeInfoNotFound => Self::PrecompileError, - Error::TerminatedWhileReentrant => Self::PrecompileError, - Error::InputForwarded => Self::PrecompileError, - Error::TooManyTopics => Self::PrecompileError, - Error::TerminatedInConstructor => Self::PrecompileError, - Error::ReentranceDenied => Self::PrecompileError, - Error::ReenteredPallet => Self::PrecompileError, - Error::StorageDepositNotEnoughFunds => Self::OutOfFunds, - Error::CodeInUse => Self::PrecompileError, - Error::ContractReverted => Self::PrecompileError, - Error::BlobTooLarge => Self::CreateContractSizeLimit, - Error::BasicBlockTooLarge => Self::PrecompileError, - Error::MaxDelegateDependenciesReached => Self::PrecompileError, - Error::DelegateDependencyNotFound => Self::PrecompileError, - Error::DelegateDependencyAlreadyExists => Self::CreateCollision, - Error::CannotAddSelfAsDelegateDependency => Self::PrecompileError, - Error::OutOfTransientStorage => Self::OutOfGas(OutOfGasError::Memory), - Error::InvalidStorageFlags => Self::InvalidFEOpcode, - // Default fallback for any unhandled errors - use PrecompileError as a general catch-all - _ => Self::PrecompileError, - } - } + // EVM rule violations + StateChangeDuringStaticCall, // InstructionResult::StateChangeDuringStaticCall + CallDepthExceeded, // InstructionResult::CallTooDeep + CreateInitCodeSizeLimit, // InstructionResult::CreateInitCodeSizeLimit + CallNotAllowedInsideStatic, // InstructionResult::CallNotAllowedInsideStatic + + // External/system errors + FatalExternalError, // InstructionResult::FatalExternalError + ReentrancyGuard, // InstructionResult::ReentrancySentryOOG + + // Additional REVM errors you might want + OutOfOffset, // InstructionResult::OutOfOffset + NotActivated, // InstructionResult::NotActivated (for EIP activation) + EOFOpcodeDisabledInLegacy, // InstructionResult::EOFOpcodeDisabledInLegacy } -/// Convert from HaltReason to DispatchError -/// This implements the mapping logic from the commented `instruction_result_into_exec_error` function -impl From for DispatchError { - fn from(halt_reason: HaltReason) -> Self { - match halt_reason { - HaltReason::OutOfGas(OutOfGasError::Basic) | - HaltReason::OutOfGas(OutOfGasError::InvalidOperand) | - HaltReason::OutOfGas(OutOfGasError::ReentrancySentry) | - HaltReason::OutOfGas(OutOfGasError::Precompile) | - HaltReason::OutOfGas(OutOfGasError::Memory) => DispatchError::Other("OutOfGas"), - HaltReason::OutOfGas(OutOfGasError::MemoryLimit) => DispatchError::Other("StaticMemoryTooLarge"), - HaltReason::OpcodeNotFound | - HaltReason::InvalidJump | - HaltReason::NotActivated | - HaltReason::InvalidFEOpcode | - HaltReason::CreateContractStartingWithEF => DispatchError::Other("InvalidInstruction"), - HaltReason::CallNotAllowedInsideStatic | - HaltReason::StateChangeDuringStaticCall => DispatchError::Other("StateChangeDenied"), - HaltReason::StackUnderflow | - HaltReason::StackOverflow | - HaltReason::NonceOverflow | - HaltReason::PrecompileError => DispatchError::Other("ContractTrapped"), - HaltReason::OutOfOffset => DispatchError::Other("OutOfBounds"), - HaltReason::CreateCollision => DispatchError::Other("DuplicateContract"), - HaltReason::OverflowPayment => DispatchError::Other("BalanceConversionFailed"), - HaltReason::CreateContractSizeLimit | - HaltReason::CreateInitCodeSizeLimit => DispatchError::Other("StaticMemoryTooLarge"), - HaltReason::CallTooDeep => DispatchError::Other("MaxCallDepthReached"), - HaltReason::OutOfFunds => DispatchError::Other("TransferFailed"), - } +impl From for ControlFlow { + fn from(err: crate::ExecError) -> Self { + todo!() } } /// EVM interpreter state using sp_core types #[derive(Debug)] pub struct Interpreter<'a, E: Ext> { + /// Access to the environment pub ext: &'a mut E, /// The bytecode being executed pub bytecode: ExtBytecode, @@ -177,8 +66,6 @@ pub struct Interpreter<'a, E: Ext> { pub input: Vec, /// The execution stack pub stack: Stack, - /// Return data from the last call - pub return_data: Vec, /// EVM memory pub memory: Memory, } @@ -186,14 +73,7 @@ pub struct Interpreter<'a, E: Ext> { impl<'a, E: Ext> Interpreter<'a, E> { /// Create a new interpreter instance pub fn new(bytecode: ExtBytecode, input: Vec, ext: &'a mut E) -> Self { - Self { - ext, - bytecode, - input, - stack: Stack::new(), - return_data: Vec::new(), - memory: Memory::new(), - } + Self { ext, bytecode, input, stack: Stack::new(), memory: Memory::new() } } } diff --git a/substrate/frame/revive/src/vm/evm/memory.rs b/substrate/frame/revive/src/vm/evm/memory.rs index a3ee0627cde9e..ff6ac9d11bcfc 100644 --- a/substrate/frame/revive/src/vm/evm/memory.rs +++ b/substrate/frame/revive/src/vm/evm/memory.rs @@ -17,8 +17,9 @@ //! Custom EVM memory implementation using standard Vec +use crate::vm::evm::Halt; use alloc::vec::Vec; -use core::ops::Range; +use core::ops::{ControlFlow, Range}; /// EVM memory implementation using standard Vec and sp_core::U256 #[derive(Debug, Clone)] @@ -42,12 +43,20 @@ impl Memory { self.0.len() } - /// Resize memory to at least the given size - pub fn resize(&mut self, new_size: usize) -> bool { - if new_size > self.0.len() { - self.0.resize(new_size, 0); + /// Resize memory to accommodate the given offset and length + pub fn resize(&mut self, offset: usize, len: usize) -> ControlFlow { + let current_len = self.0.len(); + let target_len = revm::interpreter::num_words(offset.saturating_add(len)) * 32; + if target_len as u32 > crate::limits::code::BASELINE_MEMORY_LIMIT { + log::debug!(target: crate::LOG_TARGET, "check memory bounds failed: offset={offset} target_len={target_len} current_len={current_len}"); + return ControlFlow::Break(Halt::MemoryOOG); } - true + + if target_len > current_len { + self.0.resize(target_len, 0); + }; + + ControlFlow::Continue(()) } /// Set memory at the given offset with the provided data diff --git a/substrate/frame/revive/src/vm/evm/stack.rs b/substrate/frame/revive/src/vm/evm/stack.rs index 29eee81ba8b8d..22381402c332f 100644 --- a/substrate/frame/revive/src/vm/evm/stack.rs +++ b/substrate/frame/revive/src/vm/evm/stack.rs @@ -17,8 +17,9 @@ //! Custom EVM stack implementation using sp_core::U256 -use crate::vm::evm::interpreter::HaltReason; +use crate::vm::evm::interpreter::Halt; use alloc::vec::Vec; +use core::ops::ControlFlow; use sp_core::{H160, H256, U256}; /// EVM stack implementation using sp_core types @@ -56,20 +57,23 @@ impl Stack { } /// Push a value onto the stack - /// Returns Ok(()) if successful, Err(HaltReason::StackOverflow) if stack would overflow - pub fn push(&mut self, value: impl ToU256) -> Result<(), HaltReason> { + /// Returns Continue(()) if successful, Break(Halt::StackOverflow) if stack would overflow + pub fn push(&mut self, value: impl ToU256) -> ControlFlow { if self.0.len() >= 1024 { - Err(HaltReason::StackOverflow) + ControlFlow::Break(Halt::StackOverflow) } else { self.0.push(value.to_u256()); - Ok(()) + ControlFlow::Continue(()) } } /// Pop a value from the stack - /// Returns Ok(value) if successful, Err(HaltReason::StackUnderflow) if stack is empty - pub fn pop(&mut self) -> Result { - self.0.pop().ok_or(HaltReason::StackUnderflow) + /// Returns Continue(value) if successful, Break(Halt::StackUnderflow) if stack is empty + pub fn pop(&mut self) -> ControlFlow { + match self.0.pop() { + Some(value) => ControlFlow::Continue(value), + None => ControlFlow::Break(Halt::StackUnderflow), + } } /// Get a reference to the top stack item without removing it @@ -88,65 +92,73 @@ impl Stack { } /// Pop multiple values from the stack - /// Returns Ok(array) if successful, Err(HaltReason::StackUnderflow) if not enough values on + /// Returns Continue(array) if successful, Break(Halt::StackUnderflow) if not enough values on /// stack - pub fn popn(&mut self) -> Result<[U256; N], HaltReason> { + pub fn popn(&mut self) -> ControlFlow { if self.0.len() < N { - return Err(HaltReason::StackUnderflow); + return ControlFlow::Break(Halt::StackUnderflow); } let mut result: [U256; N] = [U256::zero(); N]; for i in 0..N { - result[i] = self.0.pop().ok_or(HaltReason::StackUnderflow)?; + match self.0.pop() { + Some(value) => result[i] = value, + None => return ControlFlow::Break(Halt::StackUnderflow), + } } - Ok(result) + ControlFlow::Continue(result) } /// Pop multiple values and return them along with a mutable reference to the new top /// This is used for operations that pop some values and modify the top of the stack - pub fn popn_top(&mut self) -> Result<([U256; N], &mut U256), HaltReason> { + pub fn popn_top(&mut self) -> ControlFlow { if self.0.len() < N + 1 { - return Err(HaltReason::StackUnderflow); + return ControlFlow::Break(Halt::StackUnderflow); } let mut popped: [U256; N] = [U256::zero(); N]; for i in 0..N { - popped[i] = self.0.pop().ok_or(HaltReason::StackUnderflow)?; + match self.0.pop() { + Some(value) => popped[i] = value, + None => return ControlFlow::Break(Halt::StackUnderflow), + } } // Get mutable reference to the new top - let top = self.0.last_mut().ok_or(HaltReason::StackUnderflow)?; - Ok((popped, top)) + match self.0.last_mut() { + Some(top) => ControlFlow::Continue((popped, top)), + None => ControlFlow::Break(Halt::StackUnderflow), + } } /// Duplicate the Nth item from the top and push it onto the stack - /// Returns Ok(()) if successful, Err(HaltReason) if stack would overflow or index is invalid - pub fn dup(&mut self, n: usize) -> Result<(), HaltReason> { + /// Returns Continue(()) if successful, Break(Halt) if stack would overflow or index is invalid + pub fn dup(&mut self, n: usize) -> ControlFlow { if n == 0 || n > self.0.len() { - return Err(HaltReason::StackUnderflow); + return ControlFlow::Break(Halt::StackUnderflow); } if self.0.len() >= 1024 { - return Err(HaltReason::StackOverflow); + return ControlFlow::Break(Halt::StackOverflow); } let idx = self.0.len() - n; let value = self.0[idx]; self.0.push(value); - Ok(()) + ControlFlow::Continue(()) } /// Swap the top stack item with the Nth item from the top - /// Returns Ok(()) if successful, Err(HaltReason::StackUnderflow) if indices are invalid - pub fn exchange(&mut self, i: usize, j: usize) -> Result<(), HaltReason> { + /// Returns Continue(()) if successful, Break(Halt::StackUnderflow) if indices are invalid + pub fn exchange(&mut self, i: usize, j: usize) -> ControlFlow { let len = self.0.len(); if i >= len || j >= len { - return Err(HaltReason::StackUnderflow); + return ControlFlow::Break(Halt::StackUnderflow); } let i_idx = len - 1 - i; let j_idx = len - 1 - j; self.0.swap(i_idx, j_idx); - Ok(()) + ControlFlow::Continue(()) } } @@ -165,13 +177,13 @@ mod tests { let mut stack = Stack::new(); // Test push - assert!(stack.push(U256::from(42)).is_ok()); + assert!(matches!(stack.push(U256::from(42)), ControlFlow::Continue(()))); assert_eq!(stack.len(), 1); // Test pop - assert_eq!(stack.pop(), Ok(U256::from(42))); + assert_eq!(stack.pop(), ControlFlow::Continue(U256::from(42))); assert_eq!(stack.len(), 0); - assert_eq!(stack.pop(), Err(HaltReason::StackUnderflow)); + assert_eq!(stack.pop(), ControlFlow::Break(Halt::StackUnderflow)); } #[test] @@ -184,13 +196,13 @@ mod tests { stack.push(U256::from(3)); // Pop multiple values - let result: Result<[U256; 2], _> = stack.popn(); - assert_eq!(result, Ok([U256::from(3), U256::from(2)])); + let result: ControlFlow<_, [U256; 2]> = stack.popn(); + assert_eq!(result, ControlFlow::Continue([U256::from(3), U256::from(2)])); assert_eq!(stack.len(), 1); // Try to pop more than available - let result: Result<[U256; 2], _> = stack.popn(); - assert_eq!(result, Err(HaltReason::StackUnderflow)); + let result: ControlFlow<_, [U256; 2]> = stack.popn(); + assert_eq!(result, ControlFlow::Break(Halt::StackUnderflow)); } #[test] @@ -205,8 +217,11 @@ mod tests { // Pop 2 values and get mutable reference to new top let result = stack.popn_top::<2>(); - assert!(result.is_ok()); - let (popped, top_ref) = result.unwrap(); + assert!(matches!(result, ControlFlow::Continue(_))); + let (popped, top_ref) = match result { + ControlFlow::Continue(val) => val, + ControlFlow::Break(_) => panic!("Expected Continue"), + }; assert_eq!(popped, [U256::from(4), U256::from(3)]); assert_eq!(*top_ref, U256::from(2)); @@ -219,15 +234,15 @@ mod tests { fn test_dup() { let mut stack = Stack::new(); - stack.push(U256::from(1)).unwrap(); - stack.push(U256::from(2)).unwrap(); + let _ = stack.push(U256::from(1)); + let _ = stack.push(U256::from(2)); // Duplicate the top item (index 1) - assert!(stack.dup(1).is_ok()); + assert!(matches!(stack.dup(1), ControlFlow::Continue(()))); assert_eq!(stack.0, vec![U256::from(1), U256::from(2), U256::from(2)]); // Duplicate the second item (index 2) - assert!(stack.dup(2).is_ok()); + assert!(matches!(stack.dup(2), ControlFlow::Continue(()))); assert_eq!(stack.0, vec![U256::from(1), U256::from(2), U256::from(2), U256::from(2)]); } @@ -235,12 +250,12 @@ mod tests { fn test_exchange() { let mut stack = Stack::new(); - stack.push(U256::from(1)).unwrap(); - stack.push(U256::from(2)).unwrap(); - stack.push(U256::from(3)).unwrap(); + let _ = stack.push(U256::from(1)); + let _ = stack.push(U256::from(2)); + let _ = stack.push(U256::from(3)); // Swap top (index 0) with second (index 1) - assert!(stack.exchange(0, 1).is_ok()); + assert!(matches!(stack.exchange(0, 1), ControlFlow::Continue(()))); assert_eq!(stack.0, vec![U256::from(1), U256::from(3), U256::from(2)]); } @@ -250,11 +265,11 @@ mod tests { // Fill stack to limit for i in 0..1024 { - assert!(stack.push(U256::from(i)).is_ok()); + assert!(matches!(stack.push(U256::from(i)), ControlFlow::Continue(()))); } // Should fail to push one more - assert_eq!(stack.push(U256::from(9999)), Err(HaltReason::StackOverflow)); + assert_eq!(stack.push(U256::from(9999)), ControlFlow::Break(Halt::StackOverflow)); assert_eq!(stack.len(), 1024); } @@ -263,10 +278,10 @@ mod tests { let mut stack = Stack::new(); assert_eq!(stack.top(), None); - stack.push(U256::from(42)).unwrap(); + let _ = stack.push(U256::from(42)); assert_eq!(stack.top(), Some(&U256::from(42))); - stack.push(U256::from(100)).unwrap(); + let _ = stack.push(U256::from(100)); assert_eq!(stack.top(), Some(&U256::from(100))); } @@ -275,10 +290,10 @@ mod tests { let mut stack = Stack::new(); assert!(stack.is_empty()); - stack.push(U256::from(1)).unwrap(); + let _ = stack.push(U256::from(1)); assert!(!stack.is_empty()); - stack.pop().unwrap(); + let _ = stack.pop(); assert!(stack.is_empty()); } } diff --git a/substrate/frame/revive/src/vm/evm/util.rs b/substrate/frame/revive/src/vm/evm/util.rs new file mode 100644 index 0000000000000..cf74b56ed1eed --- /dev/null +++ b/substrate/frame/revive/src/vm/evm/util.rs @@ -0,0 +1,25 @@ +use crate::{ + vm::{ + evm::{interpreter::Halt, Interpreter}, + Ext, + }, + RuntimeCosts, U256, +}; +use core::ops::ControlFlow; +use revm::{ + interpreter::{ + gas::{BASE, HIGH, JUMPDEST, MID}, + interpreter_types::{Immediates, Jumps}, + }, + primitives::Bytes, +}; + +/// Helper function to convert U256 to usize, checking for overflow +pub fn as_usize_or_halt_with(value: U256, halt: impl Fn() -> Halt) -> ControlFlow { + let limbs = value.0; + if (limbs[0] > usize::MAX as u64) | (limbs[1] != 0) | (limbs[2] != 0) | (limbs[3] != 0) { + ControlFlow::Break(halt()) + } else { + ControlFlow::Continue(limbs[0] as usize) + } +} From b7667473f8cd33f1fc835ae1fb638ccb3759055e Mon Sep 17 00:00:00 2001 From: pgherveou Date: Mon, 22 Sep 2025 18:01:06 +0200 Subject: [PATCH 27/76] wip --- .../revive/src/vm/evm/instructions/host.rs | 247 +++++++++--------- .../revive/src/vm/evm/instructions/mod.rs | 4 +- substrate/frame/revive/src/vm/evm/memory.rs | 10 + 3 files changed, 135 insertions(+), 126 deletions(-) diff --git a/substrate/frame/revive/src/vm/evm/instructions/host.rs b/substrate/frame/revive/src/vm/evm/instructions/host.rs index dbb65c89de64a..48fddd0926bd4 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/host.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/host.rs @@ -14,247 +14,246 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. - -use super::Context; - use crate::{ storage::WriteOutcome, vec::Vec, - vm::{evm::U256Converter, Ext}, - DispatchError, Key, RuntimeCosts, -}; -use revm::{ - interpreter::{interpreter_types::StackTr, InstructionResult}, - primitives::{Bytes, U256}, + vm::{ + evm::{ + instructions::utility::IntoAddress, interpreter::Halt, util::as_usize_or_halt_with, + Interpreter, + }, + Ext, + }, + DispatchError, Key, RuntimeCosts, U256, }; +use core::ops::ControlFlow; /// Implements the BALANCE instruction. /// /// Gets the balance of the given account. -pub fn balance<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas!(context.interpreter, RuntimeCosts::BalanceOf); - popn_top!([], top, context.interpreter); - let h160 = sp_core::H160::from_slice(&top.to_be_bytes::<32>()[12..]); - *top = context.interpreter.extend.balance_of(&h160).into_revm_u256(); +pub fn balance<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { + interpreter.ext.gas_meter_mut().charge_evm(RuntimeCosts::BalanceOf)?; + let ([], top) = interpreter.stack.popn_top()?; + *top = interpreter.ext.balance_of(&top.into_address()); + ControlFlow::Continue(()) } /// EIP-1884: Repricing for trie-size-dependent opcodes -pub fn selfbalance<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas!(context.interpreter, RuntimeCosts::Balance); - let balance = context.interpreter.extend.balance(); - push!(context.interpreter, balance.into_revm_u256()); +pub fn selfbalance<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { + interpreter.ext.gas_meter_mut().charge_evm(RuntimeCosts::Balance)?; + let balance = interpreter.ext.balance(); + interpreter.stack.push(balance) } /// Implements the EXTCODESIZE instruction. /// /// Gets the size of an account's code. -pub fn extcodesize<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - popn_top!([], top, context.interpreter); - gas!(context.interpreter, RuntimeCosts::CodeSize); - let h160 = sp_core::H160::from_slice(&top.to_be_bytes::<32>()[12..]); - let code_size = context.interpreter.extend.code_size(&h160); +pub fn extcodesize<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { + let ([], top) = interpreter.stack.popn_top()?; + interpreter.ext.gas_meter_mut().charge_evm(RuntimeCosts::CodeSize)?; + let code_size = interpreter.ext.code_size(&top.into_address()); *top = U256::from(code_size); + ControlFlow::Continue(()) } /// EIP-1052: EXTCODEHASH opcode -pub fn extcodehash<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - popn_top!([], top, context.interpreter); - gas!(context.interpreter, RuntimeCosts::CodeHash); - let h160 = sp_core::H160::from_slice(&top.to_be_bytes::<32>()[12..]); - let code_hash = context.interpreter.extend.code_hash(&h160); - *top = U256::from_be_bytes(code_hash.0); +pub fn extcodehash<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { + let ([], top) = interpreter.stack.popn_top()?; + interpreter.ext.gas_meter_mut().charge_evm(RuntimeCosts::CodeHash)?; + let code_hash = interpreter.ext.code_hash(&top.into_address()); + *top = U256::from_big_endian(&code_hash.0); + ControlFlow::Continue(()) } /// Implements the EXTCODECOPY instruction. /// /// Copies a portion of an account's code to memory. -pub fn extcodecopy<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - popn!([address, memory_offset, code_offset, len_u256], context.interpreter); - let len = as_usize_or_fail!(context.interpreter, len_u256); +pub fn extcodecopy<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { + let [address, memory_offset, code_offset, len] = interpreter.stack.popn()?; + let len = as_usize_or_halt_with(len, || Halt::InvalidOperandOOG)?; - gas!(context.interpreter, RuntimeCosts::ExtCodeCopy(len as u32)); - let address = sp_core::H160::from_slice(&address.to_be_bytes::<32>()[12..]); + interpreter + .ext + .gas_meter_mut() + .charge_evm(RuntimeCosts::ExtCodeCopy(len as u32))?; + let address = address.into_address(); if len == 0 { - return; + return ControlFlow::Continue(()); } - let memory_offset = as_usize_or_fail!(context.interpreter, memory_offset); - let code_offset = as_usize_saturated!(code_offset); + let memory_offset = as_usize_or_halt_with(memory_offset, || Halt::InvalidOperandOOG)?; + let code_offset = as_usize_or_halt_with(code_offset, || Halt::InvalidOperandOOG)?; - resize_memory!(context.interpreter, memory_offset, len); + interpreter.memory.resize(memory_offset, len)?; - let mut buf = context.interpreter.memory.slice_mut(memory_offset, len); + let mut buf = interpreter.memory.slice_mut(memory_offset, len); // Note: This can't panic because we resized memory to fit. - context.interpreter.extend.copy_code_slice(&mut buf, &address, code_offset); + interpreter.ext.copy_code_slice(&mut buf, &address, code_offset); + ControlFlow::Continue(()) } /// Implements the BLOCKHASH instruction. /// /// Gets the hash of one of the 256 most recent complete blocks. -pub fn blockhash<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas!(context.interpreter, RuntimeCosts::BlockHash); - popn_top!([], number, context.interpreter); - let requested_number = ::from_revm_u256(&number); +pub fn blockhash<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { + interpreter.ext.gas_meter_mut().charge_evm(RuntimeCosts::BlockHash)?; + let ([], number) = interpreter.stack.popn_top()?; // blockhash should push zero if number is not within valid range. - if let Some(hash) = context.interpreter.extend.block_hash(requested_number) { - *number = U256::from_be_bytes(hash.0) + if let Some(hash) = interpreter.ext.block_hash(*number) { + *number = U256::from_big_endian(&hash.0) } else { - *number = U256::ZERO + *number = U256::zero() }; + ControlFlow::Continue(()) } /// Implements the SLOAD instruction. /// /// Loads a word from storage. -pub fn sload<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - popn_top!([], index, context.interpreter); +pub fn sload<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { + let ([], index) = interpreter.stack.popn_top()?; // NB: SLOAD loads 32 bytes from storage (i.e. U256). - gas!(context.interpreter, RuntimeCosts::GetStorage(32)); - let key = Key::Fix(index.to_be_bytes()); - let value = context.interpreter.extend.get_storage(&key); + interpreter.ext.gas_meter_mut().charge_evm(RuntimeCosts::GetStorage(32))?; + let key = Key::Fix(index.to_big_endian()); + let value = interpreter.ext.get_storage(&key); *index = if let Some(storage_value) = value { // sload always reads a word let Ok::<[u8; 32], _>(bytes) = storage_value.try_into() else { - context.interpreter.halt(InstructionResult::FatalExternalError); - return + return ControlFlow::Break(Halt::FatalExternalError); }; - U256::from_be_bytes(bytes) + U256::from_big_endian(&bytes) } else { // the key was never written before - U256::ZERO + U256::zero() }; + ControlFlow::Continue(()) } fn store_helper<'ext, E: Ext>( - context: Context<'_, 'ext, E>, + interpreter: &mut Interpreter<'ext, E>, cost_before: RuntimeCosts, set_function: fn(&mut E, &Key, Option>, bool) -> Result, adjust_cost: fn(new_bytes: u32, old_bytes: u32) -> RuntimeCosts, -) { - if context.interpreter.extend.is_read_only() { - context.interpreter.halt(InstructionResult::Revert); - return; +) -> ControlFlow { + if interpreter.ext.is_read_only() { + return ControlFlow::Break(Halt::Revert(Vec::new())); } - popn!([index, value], context.interpreter); + let [index, value] = interpreter.stack.popn()?; // Charge gas before set_storage and later adjust it down to the true gas cost - let Ok(charged_amount) = context.interpreter.extend.gas_meter_mut().charge(cost_before) else { - context.interpreter.halt(InstructionResult::OutOfGas); - return; - }; - - let key = Key::Fix(index.to_be_bytes()); + let charged_amount = interpreter.ext.gas_meter_mut().charge_evm(cost_before)?; + let key = Key::Fix(index.to_big_endian()); let take_old = false; - let Ok(write_outcome) = set_function( - context.interpreter.extend, - &key, - Some(value.to_be_bytes::<32>().to_vec()), - take_old, - ) else { - context.interpreter.halt(InstructionResult::FatalExternalError); - return; + let Ok(write_outcome) = + set_function(interpreter.ext, &key, Some(value.to_big_endian().to_vec()), take_old) + else { + return ControlFlow::Break(Halt::FatalExternalError); }; - context - .interpreter - .extend + interpreter + .ext .gas_meter_mut() .adjust_gas(charged_amount, adjust_cost(32, write_outcome.old_len())); + + ControlFlow::Continue(()) } /// Implements the SSTORE instruction. /// /// Stores a word to storage. -pub fn sstore<'ext, E: Ext>(context: Context<'_, 'ext, E>) { +pub fn sstore<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { store_helper( - context, + interpreter, RuntimeCosts::SetStorage { new_bytes: 32, old_bytes: 0 }, |ext, key, value, take_old| ext.set_storage(key, value, take_old), |new_bytes, old_bytes| RuntimeCosts::SetStorage { new_bytes, old_bytes }, - ); + ) } /// EIP-1153: Transient storage opcodes /// Store value to transient storage -pub fn tstore<'ext, E: Ext>(context: Context<'_, 'ext, E>) { +pub fn tstore<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { store_helper( - context, + interpreter, RuntimeCosts::SetTransientStorage { new_bytes: 32, old_bytes: 0 }, |ext, key, value, take_old| ext.set_transient_storage(key, value, take_old), |new_bytes, old_bytes| RuntimeCosts::SetTransientStorage { new_bytes, old_bytes }, - ); + ) } /// EIP-1153: Transient storage opcodes /// Load value from transient storage -pub fn tload<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - popn_top!([], index, context.interpreter); - gas!(context.interpreter, RuntimeCosts::GetTransientStorage(32)); +pub fn tload<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { + let ([], index) = interpreter.stack.popn_top()?; + interpreter + .ext + .gas_meter_mut() + .charge_evm(RuntimeCosts::GetTransientStorage(32))?; + + let key = Key::Fix(index.to_big_endian()); + let bytes = interpreter.ext.get_transient_storage(&key); - let key = Key::Fix(index.to_be_bytes()); - let bytes = context.interpreter.extend.get_transient_storage(&key); *index = if let Some(storage_value) = bytes { if storage_value.len() != 32 { // tload always reads a word - context.interpreter.halt(InstructionResult::FatalExternalError); - return; + return ControlFlow::Break(Halt::FatalExternalError); } - let mut bytes = [0u8; 32]; - bytes.copy_from_slice(&storage_value); - U256::from_be_bytes(bytes) + + let Ok::<[u8; 32], _>(bytes) = storage_value.try_into() else { + return ControlFlow::Break(Halt::FatalExternalError); + }; + U256::from_big_endian(&bytes) } else { // the key was never written before - U256::ZERO + U256::zero() }; + ControlFlow::Continue(()) } /// Implements the LOG0-LOG4 instructions. /// /// Appends log record with N topics. -pub fn log<'ext, const N: usize, E: Ext>(context: Context<'_, 'ext, E>) { - if context.interpreter.extend.is_read_only() { - context.interpreter.halt(InstructionResult::Revert); - return; +pub fn log<'ext, const N: usize, E: Ext>( + interpreter: &mut Interpreter<'ext, E>, +) -> ControlFlow { + if interpreter.ext.is_read_only() { + return ControlFlow::Break(Halt::Revert(Vec::new())); } - popn!([offset, len], context.interpreter); - let len = as_usize_or_fail!(context.interpreter, len); - if len as u32 > context.interpreter.extend.max_value_size() { - context - .interpreter - .halt(revm::interpreter::InstructionResult::InvalidOperandOOG); - return; + let [offset, len] = interpreter.stack.popn()?; + let len = as_usize_or_halt_with(len, || Halt::InvalidOperandOOG)?; + if len as u32 > interpreter.ext.max_value_size() { + return ControlFlow::Break(Halt::InvalidOperandOOG); } - gas!(context.interpreter, RuntimeCosts::DepositEvent { num_topic: N as u32, len: len as u32 }); + interpreter + .ext + .gas_meter_mut() + .charge_evm(RuntimeCosts::DepositEvent { num_topic: N as u32, len: len as u32 })?; let data = if len == 0 { - Bytes::new() + Vec::new() } else { - let offset = as_usize_or_fail!(context.interpreter, offset); - resize_memory!(context.interpreter, offset, len); - Bytes::copy_from_slice(context.interpreter.memory.slice_len(offset, len).as_ref()) + let offset = as_usize_or_halt_with(offset, || Halt::InvalidOperandOOG)?; + interpreter.memory.resize(offset, len)?; + interpreter.memory.slice(offset..offset + len).to_vec() }; - if context.interpreter.stack.len() < N { - context.interpreter.halt(InstructionResult::StackUnderflow); - return; + if interpreter.stack.len() < N { + return ControlFlow::Break(Halt::StackUnderflow); } - let Some(topics) = <_ as StackTr>::popn::(&mut context.interpreter.stack) else { - context.interpreter.halt(InstructionResult::StackUnderflow); - return; - }; - - let topics = topics.into_iter().map(|v| sp_core::H256::from(v.to_be_bytes())).collect(); + let topics = interpreter.stack.popn::()?; + let topics = topics.into_iter().map(|v| sp_core::H256::from(v.to_big_endian())).collect(); - context.interpreter.extend.deposit_event(topics, data.to_vec()); + interpreter.ext.deposit_event(topics, data.to_vec()); + ControlFlow::Continue(()) } /// Implements the SELFDESTRUCT instruction. /// /// Halt execution and register account for later deletion. -pub fn selfdestruct<'ext, E: Ext>(context: Context<'_, 'ext, E>) { +pub fn selfdestruct<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { // TODO: for now this instruction is not supported - context.interpreter.halt(InstructionResult::NotActivated); + ControlFlow::Break(Halt::NotActivated) } diff --git a/substrate/frame/revive/src/vm/evm/instructions/mod.rs b/substrate/frame/revive/src/vm/evm/instructions/mod.rs index f2d143059c97d..130a0cb1f9b09 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/mod.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/mod.rs @@ -32,8 +32,8 @@ mod block_info; mod contract; /// Control flow instructions (JUMP, JUMPI, REVERT, etc.). mod control; -// /// Host environment interactions (SLOAD, SSTORE, LOG, etc.). -// mod host; +/// Host environment interactions (SLOAD, SSTORE, LOG, etc.). +mod host; // /// Signed 256-bit integer operations. // mod i256; // /// Memory operations (MLOAD, MSTORE, MSIZE, etc.). diff --git a/substrate/frame/revive/src/vm/evm/memory.rs b/substrate/frame/revive/src/vm/evm/memory.rs index ff6ac9d11bcfc..54bbeebdbfdf7 100644 --- a/substrate/frame/revive/src/vm/evm/memory.rs +++ b/substrate/frame/revive/src/vm/evm/memory.rs @@ -32,12 +32,22 @@ impl Memory { } /// Get a slice of memory for the given range + /// TODO same as slice_mut? pub fn slice(&self, range: Range) -> &[u8] { let end = core::cmp::min(range.end, self.0.len()); let start = core::cmp::min(range.start, end); &self.0[start..end] } + /// Returns a byte slice of the memory region at the given offset. + /// + /// # Panics + /// + /// Panics on out of bounds. + pub fn slice_mut(&mut self, offset: usize, len: usize) -> &mut [u8] { + &mut self.0[offset..offset + len] + } + /// Get the current memory size in bytes pub fn size(&self) -> usize { self.0.len() From 88d93d29ab4562c11ef220ded0c9a881ccb11775 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Mon, 22 Sep 2025 21:14:26 +0200 Subject: [PATCH 28/76] wip --- .../frame/revive/src/vm/evm/ext_bytecode.rs | 4 + .../revive/src/vm/evm/instructions/memory.rs | 83 ++++--- .../revive/src/vm/evm/instructions/mod.rs | 18 +- .../revive/src/vm/evm/instructions/stack.rs | 67 +++--- .../revive/src/vm/evm/instructions/system.rs | 225 ++++++++---------- .../revive/src/vm/evm/instructions/tx_info.rs | 42 ++-- .../revive/src/vm/evm/instructions/utility.rs | 112 +++++---- 7 files changed, 266 insertions(+), 285 deletions(-) diff --git a/substrate/frame/revive/src/vm/evm/ext_bytecode.rs b/substrate/frame/revive/src/vm/evm/ext_bytecode.rs index dfabdc270b922..2943f48f4d51f 100644 --- a/substrate/frame/revive/src/vm/evm/ext_bytecode.rs +++ b/substrate/frame/revive/src/vm/evm/ext_bytecode.rs @@ -37,6 +37,10 @@ impl ExtBytecode { fn is_end(&self) -> bool { self.instruction_pointer.is_null() } + + pub fn bytecode_slice(&self) -> &[u8] { + self.base.original_byte_slice() + } } impl Jumps for ExtBytecode { diff --git a/substrate/frame/revive/src/vm/evm/instructions/memory.rs b/substrate/frame/revive/src/vm/evm/instructions/memory.rs index 3feaf89be6181..33edc1ae7cf69 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/memory.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/memory.rs @@ -15,77 +15,82 @@ // See the License for the specific language governing permissions and // limitations under the License. -use super::Context; -use crate::vm::Ext; -use core::cmp::max; -use revm::{ - interpreter::{ - gas as revm_gas, - interpreter_types::{MemoryTr, StackTr}, +use crate::{ + vm::{ + evm::{interpreter::Halt, util::as_usize_or_halt_with, Interpreter}, + Ext, }, - primitives::U256, + U256, }; +use core::{cmp::max, ops::ControlFlow}; +use revm::interpreter::gas::{copy_cost_verylow, BASE, VERYLOW}; /// Implements the MLOAD instruction. /// /// Loads a 32-byte word from memory. -pub fn mload<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::VERYLOW); - popn_top!([], top, context.interpreter); - let offset: usize = as_usize_or_fail!(context.interpreter, top); - resize_memory!(context.interpreter, offset, 32); - *top = - U256::try_from_be_slice(context.interpreter.memory.slice_len(offset, 32).as_ref()).unwrap() +pub fn mload<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { + interpreter.ext.gas_meter_mut().charge_evm_gas(VERYLOW)?; + let ([], top) = interpreter.stack.popn_top()?; + let offset = as_usize_or_halt_with(*top, || Halt::InvalidOperandOOG)?; + interpreter.memory.resize(offset, 32)?; + *top = U256::from_big_endian(interpreter.memory.slice_len(offset, 32)); + ControlFlow::Continue(()) } /// Implements the MSTORE instruction. /// /// Stores a 32-byte word to memory. -pub fn mstore<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::VERYLOW); - popn!([offset, value], context.interpreter); - let offset = as_usize_or_fail!(context.interpreter, offset); - resize_memory!(context.interpreter, offset, 32); - context.interpreter.memory.set(offset, &value.to_be_bytes::<32>()); +pub fn mstore<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { + interpreter.ext.gas_meter_mut().charge_evm_gas(VERYLOW)?; + let [offset, value] = interpreter.stack.popn()?; + let offset = as_usize_or_halt_with(offset, || Halt::InvalidOperandOOG)?; + interpreter.memory.resize(offset, 32)?; + interpreter.memory.set(offset, &value.to_big_endian()); + ControlFlow::Continue(()) } /// Implements the MSTORE8 instruction. /// /// Stores a single byte to memory. -pub fn mstore8<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::VERYLOW); - popn!([offset, value], context.interpreter); - let offset = as_usize_or_fail!(context.interpreter, offset); - resize_memory!(context.interpreter, offset, 1); - context.interpreter.memory.set(offset, &[value.byte(0)]); +pub fn mstore8<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { + interpreter.ext.gas_meter_mut().charge_evm_gas(VERYLOW)?; + let [offset, value] = interpreter.stack.popn()?; + let offset = as_usize_or_halt_with(offset, || Halt::InvalidOperandOOG)?; + interpreter.memory.resize(offset, 1)?; + interpreter.memory.set(offset, &[value.byte(0)]); + ControlFlow::Continue(()) } /// Implements the MSIZE instruction. /// /// Gets the size of active memory in bytes. -pub fn msize<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::BASE); - push!(context.interpreter, U256::from(context.interpreter.memory.size())); +pub fn msize<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { + interpreter.ext.gas_meter_mut().charge_evm_gas(BASE)?; + interpreter.stack.push(U256::from(interpreter.memory.size())) } /// Implements the MCOPY instruction. /// /// EIP-5656: Memory copying instruction that copies memory from one location to another. -pub fn mcopy<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - popn!([dst, src, len], context.interpreter); +pub fn mcopy<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { + let [dst, src, len] = interpreter.stack.popn()?; // Into usize or fail - let len = as_usize_or_fail!(context.interpreter, len); + let len = as_usize_or_halt_with(len, || Halt::InvalidOperandOOG)?; // Deduce gas - gas_or_fail_legacy!(context.interpreter, revm_gas::copy_cost_verylow(len)); + let Some(gas_cost) = copy_cost_verylow(len) else { + return ControlFlow::Break(Halt::OutOfGas); + }; + interpreter.ext.gas_meter_mut().charge_evm_gas(gas_cost)?; if len == 0 { - return; + return ControlFlow::Continue(()); } - let dst = as_usize_or_fail!(context.interpreter, dst); - let src = as_usize_or_fail!(context.interpreter, src); + let dst = as_usize_or_halt_with(dst, || Halt::InvalidOperandOOG)?; + let src = as_usize_or_halt_with(src, || Halt::InvalidOperandOOG)?; // Resize memory - resize_memory!(context.interpreter, max(dst, src), len); + interpreter.memory.resize(max(dst, src), len)?; // Copy memory in place - context.interpreter.memory.copy(dst, src, len); + interpreter.memory.copy(dst, src, len); + ControlFlow::Continue(()) } diff --git a/substrate/frame/revive/src/vm/evm/instructions/mod.rs b/substrate/frame/revive/src/vm/evm/instructions/mod.rs index 130a0cb1f9b09..a37b3b38a4669 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/mod.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/mod.rs @@ -34,16 +34,14 @@ mod contract; mod control; /// Host environment interactions (SLOAD, SSTORE, LOG, etc.). mod host; -// /// Signed 256-bit integer operations. -// mod i256; -// /// Memory operations (MLOAD, MSTORE, MSIZE, etc.). -// mod memory; -// /// Stack operations (PUSH, POP, DUP, SWAP, etc.). -// mod stack; -// /// System information instructions (ADDRESS, CALLER, etc.). -// mod system; -// /// Transaction information instructions (ORIGIN, GASPRICE, etc.). -// mod tx_info; +/// Memory operations (MLOAD, MSTORE, MSIZE, etc.). +mod memory; +/// Stack operations (PUSH, POP, DUP, SWAP, etc.). +mod stack; +/// System information instructions (ADDRESS, CALLER, etc.). +mod system; +/// Transaction information instructions (ORIGIN, GASPRICE, etc.). +mod tx_info; /// Utility functions and helpers for instruction implementation. mod utility; // diff --git a/substrate/frame/revive/src/vm/evm/instructions/stack.rs b/substrate/frame/revive/src/vm/evm/instructions/stack.rs index 055c1401686c9..289f6ee5f2146 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/stack.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/stack.rs @@ -15,66 +15,71 @@ // See the License for the specific language governing permissions and // limitations under the License. -use super::{utility::cast_slice_to_u256, Context}; -use crate::vm::Ext; -use revm::{ - interpreter::{ - gas as revm_gas, - interpreter_types::{Immediates, Jumps, StackTr}, - InstructionResult, +use crate::{ + vm::{ + evm::{instructions::utility::cast_slice_to_u256, interpreter::Halt, Interpreter}, + Ext, }, - primitives::U256, + U256, +}; +use core::ops::ControlFlow; +use revm::interpreter::{ + gas::{BASE, VERYLOW}, + interpreter_types::{Immediates, Jumps}, }; /// Implements the POP instruction. /// /// Removes the top item from the stack. -pub fn pop<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::BASE); - // Can ignore return. as relative N jump is safe operation. - popn!([_i], context.interpreter); +pub fn pop<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { + interpreter.ext.gas_meter_mut().charge_evm_gas(BASE)?; + let [_] = interpreter.stack.popn()?; + ControlFlow::Continue(()) } /// EIP-3855: PUSH0 instruction /// /// Introduce a new instruction which pushes the constant value 0 onto the stack. -pub fn push0<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::BASE); - push!(context.interpreter, U256::ZERO); +pub fn push0<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { + interpreter.ext.gas_meter_mut().charge_evm_gas(BASE)?; + interpreter.stack.push(U256::zero()) } /// Implements the PUSH1-PUSH32 instructions. /// /// Pushes N bytes from bytecode onto the stack as a 32-byte value. -pub fn push<'ext, const N: usize, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::VERYLOW); - push!(context.interpreter, U256::ZERO); - popn_top!([], top, context.interpreter); +pub fn push<'ext, const N: usize, E: Ext>( + interpreter: &mut Interpreter<'ext, E>, +) -> ControlFlow { + interpreter.ext.gas_meter_mut().charge_evm_gas(VERYLOW)?; + interpreter.stack.push(U256::zero())?; + let ([], top) = interpreter.stack.popn_top()?; - let imm = context.interpreter.bytecode.read_slice(N); + let imm = interpreter.bytecode.read_slice(N); cast_slice_to_u256(imm, top); // Can ignore return. as relative N jump is safe operation - context.interpreter.bytecode.relative_jump(N as isize); + interpreter.bytecode.relative_jump(N as isize); + ControlFlow::Continue(()) } /// Implements the DUP1-DUP16 instructions. /// /// Duplicates the Nth stack item to the top of the stack. -pub fn dup<'ext, const N: usize, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::VERYLOW); - if !context.interpreter.stack.dup(N) { - context.interpreter.halt(InstructionResult::StackOverflow); - } +pub fn dup<'ext, const N: usize, E: Ext>( + interpreter: &mut Interpreter<'ext, E>, +) -> ControlFlow { + interpreter.ext.gas_meter_mut().charge_evm_gas(VERYLOW)?; + interpreter.stack.dup(N) } /// Implements the SWAP1-SWAP16 instructions. /// /// Swaps the top stack item with the Nth stack item. -pub fn swap<'ext, const N: usize, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::VERYLOW); +pub fn swap<'ext, const N: usize, E: Ext>( + interpreter: &mut Interpreter<'ext, E>, +) -> ControlFlow { + interpreter.ext.gas_meter_mut().charge_evm_gas(VERYLOW)?; assert!(N != 0); - if !context.interpreter.stack.exchange(0, N) { - context.interpreter.halt(InstructionResult::StackOverflow); - } + interpreter.stack.exchange(0, N) } diff --git a/substrate/frame/revive/src/vm/evm/instructions/system.rs b/substrate/frame/revive/src/vm/evm/instructions/system.rs index 72c31c7955237..23f74e740dbdb 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/system.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/system.rs @@ -15,240 +15,211 @@ // See the License for the specific language governing permissions and // limitations under the License. -use super::Context; use crate::{ address::AddressMapper, - vm::{evm::U256Converter, Ext, RuntimeCosts}, - Config, -}; -use core::ptr; -use revm::{ - interpreter::{ - gas as revm_gas, - interpreter_types::{InputsTr, LegacyBytecode, MemoryTr, ReturnData, StackTr}, - CallInput, InstructionResult, Interpreter, + vm::{ + evm::{interpreter::Halt, util::as_usize_or_halt_with, Interpreter}, + Ext, RuntimeCosts, }, - primitives::{Address, B256, KECCAK_EMPTY, U256}, + Config, U256, }; +use core::{ops::ControlFlow, ptr}; +use revm::interpreter::gas::{copy_cost_verylow, BASE, VERYLOW}; +use sp_core::{H160, H256}; use sp_io::hashing::keccak_256; - // TODO: Fix the gas handling for the memory operations +/// The Keccak-256 hash of the empty string `""`. +pub const KECCAK_EMPTY: [u8; 32] = + alloy_core::hex!("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"); + /// Implements the KECCAK256 instruction. /// /// Computes Keccak-256 hash of memory data. -pub fn keccak256<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - popn_top!([offset], top, context.interpreter); - let len = as_usize_or_fail!(context.interpreter, top); - gas!(context.interpreter, RuntimeCosts::HashKeccak256(len as u32)); +pub fn keccak256<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { + let ([offset], top) = interpreter.stack.popn_top()?; + let len = as_usize_or_halt_with(*top, || Halt::InvalidOperandOOG)?; + interpreter + .ext + .gas_meter_mut() + .charge_evm(RuntimeCosts::HashKeccak256(len as u32))?; + let hash = if len == 0 { - KECCAK_EMPTY + H256::from(KECCAK_EMPTY) } else { - let from = as_usize_or_fail!(context.interpreter, offset); - resize_memory!(context.interpreter, from, len); - keccak_256(context.interpreter.memory.slice_len(from, len).as_ref()).into() + let from = as_usize_or_halt_with(offset, || Halt::InvalidOperandOOG)?; + interpreter.memory.resize(from, len)?; + H256::from(keccak_256(interpreter.memory.slice_len(from, len))) }; - *top = hash.into(); + *top = U256::from_big_endian(hash.as_ref()); + ControlFlow::Continue(()) } /// Implements the ADDRESS instruction. /// /// Pushes the current contract's address onto the stack. -pub fn address<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas!(context.interpreter, RuntimeCosts::Address); - let address: Address = context.interpreter.extend.address().0.into(); - push!(context.interpreter, address.into_word().into()); +pub fn address<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { + interpreter.ext.gas_meter_mut().charge_evm(RuntimeCosts::Address)?; + let address = interpreter.ext.address(); + interpreter.stack.push(address) } /// Implements the CALLER instruction. /// /// Pushes the caller's address onto the stack. -pub fn caller<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas!(context.interpreter, RuntimeCosts::Caller); - match context.interpreter.extend.caller().account_id() { +pub fn caller<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { + interpreter.ext.gas_meter_mut().charge_evm(RuntimeCosts::Caller)?; + match interpreter.ext.caller().account_id() { Ok(account_id) => { - let address: Address = ::AddressMapper::to_address(account_id).0.into(); - push!(context.interpreter, address.into_word().into()); - }, - Err(_) => { - context - .interpreter - .halt(revm::interpreter::InstructionResult::FatalExternalError); + let address = ::AddressMapper::to_address(account_id); + interpreter.stack.push(address) }, + Err(_) => ControlFlow::Break(Halt::FatalExternalError), } } /// Implements the CODESIZE instruction. /// /// Pushes the size of running contract's bytecode onto the stack. -pub fn codesize<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::BASE); - push!(context.interpreter, U256::from(context.interpreter.bytecode.bytecode_len())); +pub fn codesize<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { + interpreter.ext.gas_meter_mut().charge_evm_gas(BASE)?; + interpreter.stack.push(U256::from(interpreter.bytecode.len())) } /// Implements the CODECOPY instruction. /// /// Copies running contract's bytecode to memory. -pub fn codecopy<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - popn!([memory_offset, code_offset, len], context.interpreter); - let len = as_usize_or_fail!(context.interpreter, len); - let Some(memory_offset) = memory_resize(context.interpreter, memory_offset, len) else { - return; +pub fn codecopy<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { + let [memory_offset, code_offset, len] = interpreter.stack.popn()?; + let len = as_usize_or_halt_with(len, || Halt::InvalidOperandOOG)?; + let Some(memory_offset) = memory_resize(interpreter, memory_offset, len)? else { + return ControlFlow::Continue(()) }; let code_offset = as_usize_saturated!(code_offset); - // Note: This can't panic because we resized memory to fit. - context.interpreter.memory.set_data( + interpreter.memory.set_data( memory_offset, code_offset, len, - context.interpreter.bytecode.bytecode_slice(), + interpreter.bytecode.bytecode_slice(), ); + ControlFlow::Continue(()) } /// Implements the CALLDATALOAD instruction. /// /// Loads 32 bytes of input data from the specified offset. -pub fn calldataload<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::VERYLOW); - //pop_top!(interpreter, offset_ptr); - popn_top!([], offset_ptr, context.interpreter); - let mut word = B256::ZERO; +pub fn calldataload<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { + interpreter.ext.gas_meter_mut().charge_evm_gas(VERYLOW)?; + let ([], offset_ptr) = interpreter.stack.popn_top()?; + let mut word = [0u8; 32]; let offset = as_usize_saturated!(offset_ptr); - let input = context.interpreter.input.input(); + let input = &interpreter.input; let input_len = input.len(); if offset < input_len { let count = 32.min(input_len - offset); - // SAFETY: `count` is bounded by the calldata length. - // This is `word[..count].copy_from_slice(input[offset..offset + count])`, written using - // raw pointers as apparently the compiler cannot optimize the slice version, and using - // `get_unchecked` twice is uglier. - match context.interpreter.input.input() { - CallInput::Bytes(bytes) => { - unsafe { - ptr::copy_nonoverlapping(bytes.as_ptr().add(offset), word.as_mut_ptr(), count) - }; - }, - CallInput::SharedBuffer(range) => { - let input_slice = context.interpreter.memory.global_slice(range.clone()); - unsafe { - ptr::copy_nonoverlapping( - input_slice.as_ptr().add(offset), - word.as_mut_ptr(), - count, - ) - }; - }, - } + unsafe { ptr::copy_nonoverlapping(input.as_ptr().add(offset), word.as_mut_ptr(), count) }; } - *offset_ptr = word.into(); + *offset_ptr = U256::from_big_endian(&word); + ControlFlow::Continue(()) } /// Implements the CALLDATASIZE instruction. /// /// Pushes the size of input data onto the stack. -pub fn calldatasize<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::BASE); - push!(context.interpreter, U256::from(context.interpreter.input.input().len())); +pub fn calldatasize<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { + interpreter.ext.gas_meter_mut().charge_evm_gas(BASE)?; + interpreter.stack.push(U256::from(interpreter.input.len())) } /// Implements the CALLVALUE instruction. /// /// Pushes the value sent with the current call onto the stack. -pub fn callvalue<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas!(context.interpreter, RuntimeCosts::ValueTransferred); - let call_value = context.interpreter.extend.value_transferred(); - push!(context.interpreter, call_value.into_revm_u256()); +pub fn callvalue<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { + interpreter.ext.gas_meter_mut().charge_evm(RuntimeCosts::ValueTransferred)?; + let value = interpreter.ext.value_transferred(); + interpreter.stack.push(value) } /// Implements the CALLDATACOPY instruction. /// /// Copies input data to memory. -pub fn calldatacopy<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - popn!([memory_offset, data_offset, len], context.interpreter); - let len = as_usize_or_fail!(context.interpreter, len); - let Some(memory_offset) = memory_resize(context.interpreter, memory_offset, len) else { - return; +pub fn calldatacopy<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { + let [memory_offset, data_offset, len] = interpreter.stack.popn()?; + let len = as_usize_or_halt_with(len, || Halt::InvalidOperandOOG)?; + + let Some(memory_offset) = memory_resize(interpreter, memory_offset, len)? else { + return ControlFlow::Continue(()); }; let data_offset = as_usize_saturated!(data_offset); - match context.interpreter.input.input() { - CallInput::Bytes(bytes) => { - context - .interpreter - .memory - .set_data(memory_offset, data_offset, len, bytes.as_ref()); - }, - CallInput::SharedBuffer(range) => { - context.interpreter.memory.set_data_from_global( - memory_offset, - data_offset, - len, - range.clone(), - ); - }, - } + interpreter.memory.set_data(memory_offset, data_offset, len, &interpreter.input); + ControlFlow::Continue(()) } /// EIP-211: New opcodes: RETURNDATASIZE and RETURNDATACOPY -pub fn returndatasize<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::BASE); - push!(context.interpreter, U256::from(context.interpreter.return_data.buffer().len())); +pub fn returndatasize<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { + interpreter.ext.gas_meter_mut().charge_evm_gas(BASE)?; + let return_data_len = interpreter.ext.last_frame_output().data.len(); + interpreter.stack.push(U256::from(return_data_len)) } /// EIP-211: New opcodes: RETURNDATASIZE and RETURNDATACOPY -pub fn returndatacopy<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - popn!([memory_offset, offset, len], context.interpreter); +pub fn returndatacopy<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { + let [memory_offset, offset, len] = interpreter.stack.popn()?; - let len = as_usize_or_fail!(context.interpreter, len); + let len = as_usize_or_halt_with(len, || Halt::InvalidOperandOOG)?; let data_offset = as_usize_saturated!(offset); // Old legacy behavior is to panic if data_end is out of scope of return buffer. let data_end = data_offset.saturating_add(len); - if data_end > context.interpreter.return_data.buffer().len() { - context.interpreter.halt(InstructionResult::OutOfOffset); - return; + if data_end > interpreter.ext.last_frame_output().data.len() { + return ControlFlow::Break(Halt::OutOfOffset); } - let Some(memory_offset) = memory_resize(context.interpreter, memory_offset, len) else { - return; + let Some(memory_offset) = memory_resize(interpreter, memory_offset, len)? else { + return ControlFlow::Continue(()) }; - // Note: This can't panic because we resized memory to fit. - context.interpreter.memory.set_data( + interpreter.memory.set_data( memory_offset, data_offset, len, - context.interpreter.return_data.buffer(), + &interpreter.ext.last_frame_output().data, ); + ControlFlow::Continue(()) } /// Implements the GAS instruction. /// /// Pushes the amount of remaining gas onto the stack. -pub fn gas<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas!(context.interpreter, RuntimeCosts::RefTimeLeft); +pub fn gas<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { + interpreter.ext.gas_meter_mut().charge_evm(RuntimeCosts::RefTimeLeft)?; // TODO: This accounts only for 'ref_time' now. It should be fixed to also account for other // costs. See #9577 for more context. - let gas = context.interpreter.extend.gas_meter().gas_left().ref_time(); - push!(context.interpreter, U256::from(gas)); + let gas = interpreter.ext.gas_meter().gas_left().ref_time(); + interpreter.stack.push(U256::from(gas)) } /// Common logic for copying data from a source buffer to the EVM's memory. /// /// Handles memory expansion and gas calculation for data copy operations. pub fn memory_resize<'a, E: Ext>( - interpreter: &mut Interpreter>, + interpreter: &mut Interpreter<'a, E>, memory_offset: U256, len: usize, -) -> Option { - gas!(interpreter, RuntimeCosts::CopyToContract(len as u32), None); +) -> ControlFlow> { + interpreter + .ext + .gas_meter_mut() + .charge_evm(RuntimeCosts::CopyToContract(len as u32))?; if len == 0 { - return None; + return ControlFlow::Continue(None) } - let memory_offset = as_usize_or_fail_ret!(interpreter, memory_offset, None); - resize_memory!(interpreter, memory_offset, len, None); - Some(memory_offset) + let memory_offset = as_usize_or_halt_with(memory_offset, || Halt::InvalidOperandOOG)?; + interpreter.memory.resize(memory_offset, len); + + ControlFlow::Continue(Some(memory_offset)) } diff --git a/substrate/frame/revive/src/vm/evm/instructions/tx_info.rs b/substrate/frame/revive/src/vm/evm/instructions/tx_info.rs index 3060a4e4f83a5..fe0891955357c 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/tx_info.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/tx_info.rs @@ -15,41 +15,43 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{address::AddressMapper, evm::runtime::GAS_PRICE, vm::RuntimeCosts}; -use revm::primitives::{Address, U256}; - -use super::Context; -use crate::{vm::Ext, Config}; +use crate::{ + address::AddressMapper, + evm::runtime::GAS_PRICE, + vm::{ + evm::{interpreter::Halt, Interpreter}, + Ext, RuntimeCosts, + }, + Config, U256, +}; +use core::ops::ControlFlow; +use sp_core::{H160, H256}; /// Implements the GASPRICE instruction. /// /// Gets the gas price of the originating transaction. -pub fn gasprice<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas!(context.interpreter, RuntimeCosts::GasPrice); - push!(context.interpreter, U256::from(GAS_PRICE)); +pub fn gasprice<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { + interpreter.ext.gas_meter_mut().charge_evm(RuntimeCosts::GasPrice)?; + interpreter.stack.push(U256::from(GAS_PRICE)) } /// Implements the ORIGIN instruction. /// /// Gets the execution origination address. -pub fn origin<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas!(context.interpreter, RuntimeCosts::Origin); - match context.interpreter.extend.origin().account_id() { +pub fn origin<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { + interpreter.ext.gas_meter_mut().charge_evm(RuntimeCosts::Origin)?; + match interpreter.ext.origin().account_id() { Ok(account_id) => { - let address: Address = ::AddressMapper::to_address(account_id).0.into(); - push!(context.interpreter, address.into_word().into()); - }, - Err(_) => { - context - .interpreter - .halt(revm::interpreter::InstructionResult::FatalExternalError); + let address = ::AddressMapper::to_address(account_id); + interpreter.stack.push(address) }, + Err(_) => ControlFlow::Break(Halt::FatalExternalError), } } /// Implements the BLOBHASH instruction. /// /// EIP-4844: Shard Blob Transactions - gets the hash of a transaction blob. -pub fn blob_hash<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - context.interpreter.halt(revm::interpreter::InstructionResult::NotActivated); +pub fn blob_hash<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { + ControlFlow::Break(Halt::NotActivated) } diff --git a/substrate/frame/revive/src/vm/evm/instructions/utility.rs b/substrate/frame/revive/src/vm/evm/instructions/utility.rs index dbefdb4e88102..cbfbc3f46c693 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/utility.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/utility.rs @@ -23,64 +23,60 @@ use sp_core::{H160, U256}; /// # Panics /// /// Panics if slice is longer than 32 bytes. -// pub fn cast_slice_to_u256(slice: &[u8], dest: &mut U256) { -// use revm::primitives::{Address, B256, U256}; -// if slice.is_empty() { -// return; -// } -// assert!(slice.len() <= 32, "slice too long"); -// -// let n_words = slice.len().div_ceil(32); -// -// // SAFETY: Length checked above. -// unsafe { -// //let dst = self.data.as_mut_ptr().add(self.data.len()).cast::(); -// //self.data.set_len(new_len); -// let dst = dest.as_limbs_mut().as_mut_ptr(); -// -// let mut i = 0; -// -// // Write full words -// let words = slice.chunks_exact(32); -// let partial_last_word = words.remainder(); -// for word in words { -// // Note: We unroll `U256::from_be_bytes` here to write directly into the buffer, -// // instead of creating a 32 byte array on the stack and then copying it over. -// for l in word.rchunks_exact(8) { -// dst.add(i).write(u64::from_be_bytes(l.try_into().unwrap())); -// i += 1; -// } -// } -// -// if partial_last_word.is_empty() { -// return; -// } -// -// // Write limbs of partial last word -// let limbs = partial_last_word.rchunks_exact(8); -// let partial_last_limb = limbs.remainder(); -// for l in limbs { -// dst.add(i).write(u64::from_be_bytes(l.try_into().unwrap())); -// i += 1; -// } -// -// // Write partial last limb by padding with zeros -// if !partial_last_limb.is_empty() { -// let mut tmp = [0u8; 8]; -// tmp[8 - partial_last_limb.len()..].copy_from_slice(partial_last_limb); -// dst.add(i).write(u64::from_be_bytes(tmp)); -// i += 1; -// } -// -// debug_assert_eq!(i.div_ceil(4), n_words, "wrote too much"); -// -// // Zero out upper bytes of last word -// let m = i % 4; // 32 / 8 -// if m != 0 { -// dst.add(i).write_bytes(0, 4 - m); -// } -// } -// } +pub fn cast_slice_to_u256(slice: &[u8], dest: &mut U256) { + if slice.is_empty() { + return; + } + assert!(slice.len() <= 32, "slice too long"); + + let n_words = slice.len().div_ceil(32); + + // SAFETY: Length checked above. + unsafe { + let dst = dest.0.as_mut_ptr(); + let mut i = 0; + + // Write full words + let words = slice.chunks_exact(32); + let partial_last_word = words.remainder(); + for word in words { + // Note: We unroll `U256::from_be_bytes` here to write directly into the buffer, + // instead of creating a 32 byte array on the stack and then copying it over. + for l in word.rchunks_exact(8) { + dst.add(i).write(u64::from_be_bytes(l.try_into().unwrap())); + i += 1; + } + } + + if partial_last_word.is_empty() { + return; + } + + // Write limbs of partial last word + let limbs = partial_last_word.rchunks_exact(8); + let partial_last_limb = limbs.remainder(); + for l in limbs { + dst.add(i).write(u64::from_be_bytes(l.try_into().unwrap())); + i += 1; + } + + // Write partial last limb by padding with zeros + if !partial_last_limb.is_empty() { + let mut tmp = [0u8; 8]; + tmp[8 - partial_last_limb.len()..].copy_from_slice(partial_last_limb); + dst.add(i).write(u64::from_be_bytes(tmp)); + i += 1; + } + + debug_assert_eq!(i.div_ceil(4), n_words, "wrote too much"); + + // Zero out upper bytes of last word + let m = i % 4; // 32 / 8 + if m != 0 { + dst.add(i).write_bytes(0, 4 - m); + } + } +} /// Trait for converting types into Address values. pub trait IntoAddress { From 4356b6ffd64140380017717c6b644c211150688b Mon Sep 17 00:00:00 2001 From: pgherveou Date: Mon, 22 Sep 2025 21:30:07 +0200 Subject: [PATCH 29/76] wip --- .../revive/src/vm/evm/instructions/macros.rs | 199 +----------------- 1 file changed, 1 insertion(+), 198 deletions(-) diff --git a/substrate/frame/revive/src/vm/evm/instructions/macros.rs b/substrate/frame/revive/src/vm/evm/instructions/macros.rs index e45ab28a2c908..35d28d8a0a64a 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/macros.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/macros.rs @@ -17,156 +17,6 @@ //! Utility macros to help implementing opcode instruction functions. -/// `const` Option `?`. -#[macro_export] -macro_rules! tri { - ($e:expr) => { - match $e { - Some(v) => v, - None => return None, - } - }; -} - -/// Macro for optional try - returns early if the expression evaluates to None. -/// Similar to the `?` operator but for use in instruction implementations. -#[macro_export] -macro_rules! otry { - ($expression: expr) => {{ - let Some(value) = $expression else { - return; - }; - value - }}; -} - -/// Error if the current call is executing EOF. -#[macro_export] -macro_rules! require_eof { - ($interpreter:expr) => { - if !$interpreter.runtime_flag.is_eof() { - $interpreter.halt(revm::interpreter::InstructionResult::EOFOpcodeDisabledInLegacy); - return; - } - }; -} - -/// Check if the `SPEC` is enabled, and fail the instruction if it is not. -#[macro_export] -macro_rules! check { - ($interpreter:expr, $min:ident) => { - if !$interpreter - .runtime_flag - .spec_id() - .is_enabled_in(revm::primitives::hardfork::SpecId::$min) - { - $interpreter.halt(revm::interpreter::InstructionResult::NotActivated); - return; - } - }; -} - -/// Records a `gas` cost and fails the instruction if it would exceed the available gas. -#[macro_export] -macro_rules! gas_legacy { - ($interpreter:expr, $gas:expr) => { - gas_legacy!($interpreter, $gas, ()) - }; - ($interpreter:expr, $gas:expr, $ret:expr) => { - if $interpreter.extend.gas_meter_mut().charge_evm_gas($gas).is_err() { - $interpreter.halt(revm::interpreter::InstructionResult::OutOfGas); - return $ret; - } - }; -} - -#[macro_export] -macro_rules! gas { - ($interpreter:expr, $gas:expr) => { - gas!($interpreter, $gas, ()) - }; - ($interpreter:expr, $gas:expr, $ret:expr) => { - let meter = $interpreter.extend.gas_meter_mut(); - if meter.charge_evm_gas(1).is_err() || meter.charge($gas).is_err() { - $interpreter.halt(revm::interpreter::InstructionResult::OutOfGas); - return $ret; - } - }; -} - -/// Same as [`gas_legacy!`], but with `gas` as an option. -#[macro_export] -macro_rules! gas_or_fail_legacy { - ($interpreter:expr, $gas:expr) => { - gas_or_fail_legacy!($interpreter, $gas, ()) - }; - ($interpreter:expr, $gas:expr, $ret:expr) => { - match $gas { - Some(gas_used) => gas_legacy!($interpreter, gas_used, $ret), - None => { - $interpreter.halt(revm::interpreter::InstructionResult::OutOfGas); - return $ret; - }, - } - }; -} - -/// Resizes the interpreterreter memory if necessary. Fails the instruction if the memory or gas -/// limit is exceeded. -#[macro_export] -macro_rules! resize_memory { - ($interpreter:expr, $offset:expr, $len:expr) => { - resize_memory!($interpreter, $offset, $len, ()) - }; - ($interpreter:expr, $offset:expr, $len:expr, $ret:expr) => { - let current_len = $interpreter.memory.len(); - let target_len = revm::interpreter::num_words($offset.saturating_add($len)) * 32; - if target_len as u32 > $crate::limits::code::BASELINE_MEMORY_LIMIT { - log::debug!(target: $crate::LOG_TARGET, "check memory bounds failed: offset={} target_len={target_len} current_len={current_len}", $offset); - $interpreter.halt(revm::interpreter::InstructionResult::MemoryOOG); - return $ret; - } - - if target_len > current_len { - $interpreter.memory.resize(target_len); - }; - }; -} - -/// Pops n values from the stack. Fails the instruction if n values can't be popped. -#[macro_export] -macro_rules! popn { - ([ $($x:ident),* ],$interpreterreter:expr $(,$ret:expr)? ) => { - let Some([$( $x ),*]) = <_ as StackTr>::popn(&mut $interpreterreter.stack) else { - $interpreterreter.halt(revm::interpreter::InstructionResult::StackUnderflow); - return $($ret)?; - }; - }; -} - -/// Pops n values from the stack and returns the top value. Fails the instruction if n values can't -/// be popped. -#[macro_export] -macro_rules! popn_top { - ([ $($x:ident),* ], $top:ident, $interpreter:expr $(,$ret:expr)? ) => { - let Some(([$($x),*], $top)) = <_ as StackTr>::popn_top(&mut $interpreter.stack) else { - $interpreter.halt(revm::interpreter::InstructionResult::StackUnderflow); - return $($ret)?; - }; - }; -} - -/// Pushes a `B256` value onto the stack. Fails the instruction if the stack is full. -#[macro_export] -macro_rules! push { - ($interpreter:expr, $x:expr $(,$ret:item)?) => ( - if !($interpreter.stack.push($x)) { - $interpreter.halt(revm::interpreter::InstructionResult::StackOverflow); - return $($ret)?; - } - ) -} - /// Converts a `U256` value to a `u64`, saturating to `MAX` if the value is too large. #[macro_export] macro_rules! as_u64_saturated { @@ -188,51 +38,4 @@ macro_rules! as_usize_saturated { ($v:expr) => { usize::try_from(as_u64_saturated!($v)).unwrap_or(usize::MAX) }; -} - -/// Converts a `U256` value to a `isize`, saturating to `isize::MAX` if the value is too large. -#[macro_export] -macro_rules! as_isize_saturated { - ($v:expr) => { - // `isize_try_from(u64::MAX)`` will fail and return isize::MAX - // This is expected behavior as we are saturating the value. - isize::try_from(as_u64_saturated!($v)).unwrap_or(isize::MAX) - }; -} - -/// Converts a `U256` value to a `usize`, failing the instruction if the value is too large. -#[macro_export] -macro_rules! as_usize_or_fail { - ($interpreter:expr, $v:expr) => { - as_usize_or_fail_ret!($interpreter, $v, ()) - }; - ($interpreter:expr, $v:expr, $reason:expr) => { - as_usize_or_fail_ret!($interpreter, $v, $reason, ()) - }; -} - -/// Converts a `U256` value to a `usize` and returns `ret`, -/// failing the instruction if the value is too large. -#[macro_export] -macro_rules! as_usize_or_fail_ret { - ($interpreter:expr, $v:expr, $ret:expr) => { - as_usize_or_fail_ret!( - $interpreter, - $v, - revm::interpreter::InstructionResult::InvalidOperandOOG, - $ret - ) - }; - - ($interpreter:expr, $v:expr, $reason:expr, $ret:expr) => { - match $v.as_limbs() { - x => { - if (x[0] > usize::MAX as u64) | (x[1] != 0) | (x[2] != 0) | (x[3] != 0) { - $interpreter.halt($reason); - return $ret; - } - x[0] as usize - }, - } - }; -} +} \ No newline at end of file From 77ae1d25e28e7bff7a3aadaec074e3760efbfb6c Mon Sep 17 00:00:00 2001 From: pgherveou Date: Tue, 23 Sep 2025 12:28:01 +0200 Subject: [PATCH 30/76] wip --- substrate/frame/revive/src/benchmarking.rs | 44 +-- substrate/frame/revive/src/vm/evm.rs | 256 +++---------- .../frame/revive/src/vm/evm/ext_bytecode.rs | 6 +- .../vm/evm/instructions/arithmetic/modular.rs | 2 +- .../revive/src/vm/evm/instructions/bitwise.rs | 99 ++--- .../src/vm/evm/instructions/bitwise/bits.rs | 19 +- .../src/vm/evm/instructions/block_info.rs | 6 +- .../src/vm/evm/instructions/contract.rs | 11 +- .../evm/instructions/contract/call_helpers.rs | 9 +- .../revive/src/vm/evm/instructions/control.rs | 15 +- .../revive/src/vm/evm/instructions/host.rs | 2 +- .../revive/src/vm/evm/instructions/mod.rs | 343 +++++++++--------- .../revive/src/vm/evm/instructions/system.rs | 6 +- .../revive/src/vm/evm/instructions/tx_info.rs | 3 +- .../frame/revive/src/vm/evm/interpreter.rs | 148 +++++--- substrate/frame/revive/src/vm/evm/memory.rs | 8 +- substrate/frame/revive/src/vm/evm/stack.rs | 31 +- substrate/frame/revive/src/vm/evm/util.rs | 14 +- substrate/frame/revive/src/vm/mod.rs | 9 +- 19 files changed, 436 insertions(+), 595 deletions(-) diff --git a/substrate/frame/revive/src/benchmarking.rs b/substrate/frame/revive/src/benchmarking.rs index 2505aecd8abcb..1691d71f1c1ae 100644 --- a/substrate/frame/revive/src/benchmarking.rs +++ b/substrate/frame/revive/src/benchmarking.rs @@ -29,7 +29,7 @@ use crate::{ storage::WriteOutcome, vm::{ evm, - evm::{instructions::instruction_table, EVMInterpreter}, + evm::{instructions::instruction_table, Interpreter}, pvm, }, Pallet as Contracts, *, @@ -47,13 +47,7 @@ use frame_support::{ }; use frame_system::RawOrigin; use pallet_revive_uapi::{pack_hi_lo, CallFlags, ReturnErrorCode, StorageFlags}; -use revm::{ - bytecode::{opcode::EXTCODECOPY, Bytecode}, - interpreter::{ - host::DummyHost, interpreter_types::MemoryTr, InstructionContext, Interpreter, SharedMemory, - }, - primitives, -}; +use revm::bytecode::{opcode::EXTCODECOPY, Bytecode}; use sp_consensus_aura::AURA_ENGINE_ID; use sp_consensus_babe::{ digests::{PreDigest, PrimaryPreDigest}, @@ -2307,7 +2301,7 @@ mod benchmarks { #[benchmark(pov_mode = Measured)] fn evm_opcode(r: Linear<0, 10_000>) -> Result<(), BenchmarkError> { let module = VmBinaryModule::evm_noop(r); - let inputs = evm::EVMInputs::new(vec![]); + let inputs = vec![]; let code = Bytecode::new_raw(revm::primitives::Bytes::from(module.code.clone())); let mut setup = CallSetup::::new(module); @@ -2412,38 +2406,26 @@ mod benchmarks { let mut setup = CallSetup::::new(module); let contract = setup.contract(); - let mut address: [u8; 32] = [0; 32]; - address[12..].copy_from_slice(&contract.address.0); - + let address = H160([0u8; 20]); let (mut ext, _) = setup.ext(); - let mut interpreter: Interpreter> = Interpreter { - extend: &mut ext, - input: Default::default(), - bytecode: Default::default(), - gas: Default::default(), - stack: Default::default(), - return_data: Default::default(), - memory: SharedMemory::new(), - runtime_flag: Default::default(), - }; + let mut interpreter = Interpreter::new(Default::default(), Default::default(), &mut ext); - let table = instruction_table::<'_, _>(); + let table = instruction_table::<_>(); let extcodecopy_fn = table[EXTCODECOPY as usize]; // Setup stack for extcodecopy instruction: [address, dest_offset, offset, size] - let _ = interpreter.stack.push(primitives::U256::from(n)); - let _ = interpreter.stack.push(primitives::U256::from(0u32)); - let _ = interpreter.stack.push(primitives::U256::from(0u32)); - let _ = interpreter.stack.push(primitives::U256::from_be_bytes(address)); - - let mut host = DummyHost {}; - let context = InstructionContext { interpreter: &mut interpreter, host: &mut host }; + let _ = interpreter.stack.push(U256::from(n)); + let _ = interpreter.stack.push(U256::from(0u32)); + let _ = interpreter.stack.push(U256::from(0u32)); + let _ = interpreter.stack.push(address); + let result; #[block] { - extcodecopy_fn(context); + result = extcodecopy_fn(&mut interpreter); } + assert!(result.is_continue()); assert_eq!( *interpreter.memory.slice(0..n as usize), PristineCode::::get(contract.info()?.code_hash).unwrap()[0..n as usize], diff --git a/substrate/frame/revive/src/vm/evm.rs b/substrate/frame/revive/src/vm/evm.rs index 8dba501f108dc..317e9f362e227 100644 --- a/substrate/frame/revive/src/vm/evm.rs +++ b/substrate/frame/revive/src/vm/evm.rs @@ -14,20 +14,16 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -#![allow(unused)] // TODO remove use crate::{ - exec::ExecError, - gas, vec, - vm::{BytecodeType, ExecResult, Ext}, - AccountIdOf, Code, CodeInfo, Config, ContractBlob, DispatchError, Error, ExecReturnValue, - RuntimeCosts, H256, LOG_TARGET, U256, + vm::{ + evm::instructions::{instruction_table, InstructionTable}, + BytecodeType, ExecResult, Ext, + }, + AccountIdOf, CodeInfo, Config, ContractBlob, DispatchError, Error, H256, LOG_TARGET, U256, }; -use alloc::{boxed::Box, vec::Vec}; -use core::cmp::min; -use pallet_revive_uapi::ReturnFlags; +use alloc::vec::Vec; +use core::{convert::Infallible, ops::ControlFlow}; use revm::{bytecode::Bytecode, primitives::Bytes}; -use sp_core::H160; -use sp_runtime::Weight; #[cfg(feature = "runtime-benchmarks")] pub mod instructions; @@ -36,14 +32,11 @@ mod instructions; mod interpreter; mod util; -pub use interpreter::Halt; -use interpreter::{InstructionTable, Interpreter}; +pub use interpreter::{Halt, Interpreter}; mod memory; -use memory::Memory; mod stack; -use stack::Stack; mod ext_bytecode; use ext_bytecode::ExtBytecode; @@ -125,88 +118,31 @@ impl ContractBlob { } /// Calls the EVM interpreter with the provided bytecode and inputs. -pub fn call<'a, E: Ext>(bytecode: Bytecode, ext: &'a mut E, inputs: &'a [u8]) -> ExecResult { - todo!() - // let mut interpreter: Interpreter> = Interpreter { - // gas: Gas::default(), - // bytecode: ExtBytecode::new(bytecode), - // stack: Stack::new(), - // return_data: Default::default(), - // memory: SharedMemory::new(), - // input: inputs, - // runtime_flag: RuntimeFlags { is_static: ext.is_read_only(), spec_id: SpecId::default() }, - // extend: ext, - // }; - // - // let table = instruction_table::<'a, E>(); - // let result = run(&mut interpreter, &table); - // - // instruction_result_into_exec_error::(result.result) - // .map(Err) - // .unwrap_or_else(|| { - // Ok(ExecReturnValue { - // flags: if result.is_revert() { ReturnFlags::REVERT } else { ReturnFlags::empty() }, - // data: result.output.to_vec(), - // }) - // }) -} +pub fn call<'a, E: Ext>(bytecode: Bytecode, ext: &'a mut E, input: Vec) -> ExecResult { + let mut interpreter = Interpreter::new(ExtBytecode::new(bytecode), input, ext); + let table = instruction_table::(); -/// Runs the EVM interpreter -fn run<'a, E: Ext>( - interpreter: &mut Interpreter<'a, E>, - table: &InstructionTable, -) -> ExecResult { - loop { - #[cfg(not(feature = "std"))] - let action = run_plain(interpreter, table); - #[cfg(feature = "std")] - let action = run_plain_with_tracing(interpreter, table); - todo!() - // match action { - // InterpreterAction::Return(result) => { - // log::trace!(target: LOG_TARGET, "Evm return {:?}", result); - // return result; - // }, - // InterpreterAction::NewFrame(frame_input) => match frame_input { - // FrameInput::Call(call_input) => run_call(interpreter, call_input), - // FrameInput::Create(create_input) => run_create(interpreter, create_input), - // FrameInput::Empty => unreachable!(), - // }, - // } - } + #[cfg(not(feature = "std"))] + let ControlFlow::Break(halt) = run_plain(&mut interpreter, &table); + #[cfg(feature = "std")] + let ControlFlow::Break(halt) = run_plain_with_tracing(&mut interpreter, &table); + + interpreter.into_exec_result(halt) } /// Re-implementation of REVM run_plain function to add trace logging to our EVM interpreter loop. /// NB: copied directly from revm tag v82 -#[cfg(feature = "std")] +#[cfg(not(feature = "std"))] fn run_plain<'a, E: Ext>( interpreter: &mut Interpreter, table: &InstructionTable, -) -> ExecResult { - todo!() - // use crate::{alloc::string::ToString, format}; - // use revm::{ - // bytecode::OpCode, - // interpreter::{ - // instruction_context::InstructionContext, - // interpreter_types::{Jumps, LoopControl, MemoryTr, StackTr}, - // }, - // }; - // while interpreter.bytecode.is_not_end() { - // // Get current opcode. - // let opcode = interpreter.bytecode.opcode(); - // - // // SAFETY: In analysis we are doing padding of bytecode so that we are sure that last - // // byte instruction is STOP so we are safe to just increment program_counter bcs on last - // // instruction it will do noop and just stop execution of this contract - // interpreter.bytecode.relative_jump(1); - // let context = InstructionContext { interpreter, host }; - // // Execute instruction. - // instruction_table[opcode as usize](context); - // } - // interpreter.bytecode.revert_to_previous_pointer(); - // - // interpreter.take_next_action() +) -> ControlFlow { + use revm::interpreter::interpreter_types::Jumps; + loop { + let opcode = interpreter.bytecode.opcode(); + table[opcode as usize](interpreter)?; + interpreter.bytecode.relative_jump(1); + } } /// Re-implementation of REVM run_plain function to add trace logging to our EVM interpreter loop. @@ -215,125 +151,25 @@ fn run_plain<'a, E: Ext>( fn run_plain_with_tracing<'a, E: Ext>( interpreter: &mut Interpreter<'a, E>, table: &InstructionTable, -) -> ExecResult { - todo!() - // use crate::{alloc::string::ToString, format}; - // use revm::{ - // bytecode::OpCode, - // interpreter::{ - // instruction_context::InstructionContext, - // interpreter_types::{Jumps, LoopControl, MemoryTr, StackTr}, - // }, - // }; - // while interpreter.bytecode.is_not_end() { - // log::trace!(target: LOG_TARGET, - // "[{pc}]: {opcode}, stacktop: {stacktop}, memory size: {memsize} {memory:?}", - // pc = interpreter.bytecode.pc(), - // opcode = OpCode::new(interpreter.bytecode.opcode()) - // .map_or("INVALID".to_string(), |x| format!("{:?}", x.info())), - // stacktop = interpreter.stack.top().map_or("None".to_string(), |x| format!("{:#x}", x)), - // memsize = interpreter.memory.size(), - // // printing at most the first 32 bytes of memory - // memory = interpreter - // .memory - // .slice_len(0, core::cmp::min(32, interpreter.memory.size())) - // .to_vec(), - // ); - // // Get current opcode. - // let opcode = interpreter.bytecode.opcode(); - // - // // SAFETY: In analysis we are doing padding of bytecode so that we are sure that last - // // byte instruction is STOP so we are safe to just increment program_counter bcs on last - // // instruction it will do noop and just stop execution of this contract - // interpreter.bytecode.relative_jump(1); - // let context = InstructionContext { interpreter, host }; - // // Execute instruction. - // instruction_table[opcode as usize](context); - // } - // interpreter.bytecode.revert_to_previous_pointer(); - // - // interpreter.take_next_action() +) -> ControlFlow { + use crate::{alloc::string::ToString, format}; + use revm::{bytecode::OpCode, interpreter::interpreter_types::Jumps}; + loop { + let opcode = interpreter.bytecode.opcode(); + log::trace!(target: LOG_TARGET, + "[{pc}]: {opcode}, stacktop: {stacktop}, memory size: {memsize} {memory:?}", + pc = interpreter.bytecode.pc(), + opcode = OpCode::new(interpreter.bytecode.opcode()) + .map_or("INVALID".to_string(), |x| format!("{:?}", x.info())), + stacktop = interpreter.stack.top().map_or("None".to_string(), |x| format!("{:#x}", x)), + memsize = interpreter.memory.size(), + // printing at most the first 32 bytes of memory + memory = interpreter + .memory + .slice_len(0, core::cmp::min(32, interpreter.memory.size())) + .to_vec(), + ); + interpreter.bytecode.relative_jump(1); + table[opcode as usize](interpreter)?; + } } - -// /// Conversion of a `ExecError` to `ReturnErrorCode`. -// /// -// /// Used when converting the error returned from a subcall in order to map it to the -// /// equivalent EVM interpreter [InstructionResult]. -// /// -// /// - Returns `None` when the caller can recover the error. -// /// - Otherwise, some [InstructionResult] error code (the halt reason) is returned. Most -// [ExecError] /// variants don't map to a [InstructionResult]. The conversion is lossy and -// defaults to /// [InstructionResult::Revert] for most cases. -// /// -// /// Uses the overarching [super::exec_error_into_return_code] method to determine if -// /// the error is recoverable or not. This guarantees consistent behavior accross both -// /// VM backends. -// fn exec_error_into_halt_reason(from: ExecError) -> Option { -// log::trace!("call frame execution error in EVM caller: {:?}", &from); -// -// if super::exec_error_into_return_code::(from).is_ok() { -// return None; -// } -// -// let static_memory_too_large = Error::::StaticMemoryTooLarge.into(); -// let code_rejected = Error::::CodeRejected.into(); -// let transfer_failed = Error::::TransferFailed.into(); -// let duplicate_contract = Error::::DuplicateContract.into(); -// let balance_conversion_failed = Error::::BalanceConversionFailed.into(); -// let value_too_large = Error::::ValueTooLarge.into(); -// let out_of_gas = Error::::OutOfGas.into(); -// let out_of_deposit = Error::::StorageDepositLimitExhausted.into(); -// -// Some(match from.error { -// err if err == static_memory_too_large => InstructionResult::MemoryLimitOOG, -// err if err == code_rejected => InstructionResult::OpcodeNotFound, -// err if err == transfer_failed => InstructionResult::OutOfFunds, -// err if err == duplicate_contract => InstructionResult::CreateCollision, -// err if err == balance_conversion_failed => InstructionResult::OverflowPayment, -// err if err == value_too_large => InstructionResult::OverflowPayment, -// err if err == out_of_deposit => InstructionResult::OutOfFunds, -// err if err == out_of_gas => InstructionResult::OutOfGas, -// _ => InstructionResult::Revert, -// }) -// } -// -// /// Map [InstructionResult] into an [ExecError] for passing it up the stack. -// /// -// /// Returns `None` if the instruction result is not an error case. -// fn instruction_result_into_exec_error(from: InstructionResult) -> Option { -// match from { -// InstructionResult::OutOfGas | -// InstructionResult::InvalidOperandOOG | -// InstructionResult::ReentrancySentryOOG | -// InstructionResult::PrecompileOOG | -// InstructionResult::MemoryOOG => Some(Error::::OutOfGas), -// InstructionResult::MemoryLimitOOG => Some(Error::::StaticMemoryTooLarge), -// InstructionResult::OpcodeNotFound | -// InstructionResult::InvalidJump | -// InstructionResult::NotActivated | -// InstructionResult::InvalidFEOpcode | -// InstructionResult::CreateContractStartingWithEF => Some(Error::::InvalidInstruction), -// InstructionResult::CallNotAllowedInsideStatic | -// InstructionResult::StateChangeDuringStaticCall => Some(Error::::StateChangeDenied), -// InstructionResult::StackUnderflow | -// InstructionResult::StackOverflow | -// InstructionResult::NonceOverflow | -// InstructionResult::PrecompileError | -// InstructionResult::FatalExternalError => Some(Error::::ContractTrapped), -// InstructionResult::OutOfOffset => Some(Error::::OutOfBounds), -// InstructionResult::CreateCollision => Some(Error::::DuplicateContract), -// InstructionResult::OverflowPayment => Some(Error::::BalanceConversionFailed), -// InstructionResult::CreateContractSizeLimit | InstructionResult::CreateInitCodeSizeLimit => -// Some(Error::::StaticMemoryTooLarge), -// InstructionResult::CallTooDeep => Some(Error::::MaxCallDepthReached), -// InstructionResult::OutOfFunds => Some(Error::::TransferFailed), -// InstructionResult::CreateInitCodeStartingEF00 | -// InstructionResult::InvalidEOFInitCode | -// InstructionResult::InvalidExtDelegateCallTarget => Some(Error::::ContractTrapped), -// InstructionResult::Stop | -// InstructionResult::Return | -// InstructionResult::Revert | -// InstructionResult::SelfDestruct => None, -// } -// .map(Into::into) -// } diff --git a/substrate/frame/revive/src/vm/evm/ext_bytecode.rs b/substrate/frame/revive/src/vm/evm/ext_bytecode.rs index 2943f48f4d51f..1155c3870b56d 100644 --- a/substrate/frame/revive/src/vm/evm/ext_bytecode.rs +++ b/substrate/frame/revive/src/vm/evm/ext_bytecode.rs @@ -1,4 +1,4 @@ -use core::{ops::Deref, ptr}; +use core::ops::Deref; use revm::{ bytecode::{utils::read_u16, Bytecode}, interpreter::interpreter_types::{Immediates, Jumps}, @@ -34,10 +34,6 @@ impl ExtBytecode { Self { base, instruction_pointer } } - fn is_end(&self) -> bool { - self.instruction_pointer.is_null() - } - pub fn bytecode_slice(&self) -> &[u8] { self.base.original_byte_slice() } diff --git a/substrate/frame/revive/src/vm/evm/instructions/arithmetic/modular.rs b/substrate/frame/revive/src/vm/evm/instructions/arithmetic/modular.rs index 82472324a5f23..0d75925c7aeb1 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/arithmetic/modular.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/arithmetic/modular.rs @@ -94,7 +94,7 @@ impl Modular for U256 { #[cfg(test)] mod tests { use super::*; - use proptest::{prop_assume, proptest, test_runner::Config}; + use proptest::proptest; #[test] fn test_commutative() { diff --git a/substrate/frame/revive/src/vm/evm/instructions/bitwise.rs b/substrate/frame/revive/src/vm/evm/instructions/bitwise.rs index 6ac46b3752fee..2b144d5d54b8e 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/bitwise.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/bitwise.rs @@ -196,13 +196,10 @@ pub fn sar<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow< #[cfg(test)] mod tests { use super::{byte, clz, sar, shl, shr}; - use crate::{ - tests::Test, - vm::evm::{Bytecode, Interpreter}, - }; + use crate::{tests::Test, vm::evm::Interpreter}; use alloy_core::hex; + use core::ops::ControlFlow; use sp_core::U256; - use sp_runtime::DispatchResult; macro_rules! test_interpreter { ($interpreter: ident) => { @@ -212,7 +209,7 @@ mod tests { } #[test] - fn test_shift_left() -> ControlFlow { + fn test_shift_left() { struct TestCase { value: U256, shift: U256, @@ -323,18 +320,20 @@ mod tests { test_interpreter!(interpreter); for test in test_cases { - interpreter.stack.push(test.value)?; - interpreter.stack.push(test.shift)?; - shl(&mut interpreter)?; - let res = interpreter.stack.pop().unwrap(); - assert_eq!(res, test.expected); + assert!((|| { + interpreter.stack.push(test.value)?; + interpreter.stack.push(test.shift)?; + shl(&mut interpreter)?; + let [res] = interpreter.stack.popn::<1>()?; + assert_eq!(res, test.expected); + ControlFlow::Continue(()) + })() + .is_continue()); } - - ControlFlow::Continue(()) } #[test] - fn test_logical_shift_right() -> ControlFlow { + fn test_logical_shift_right() { struct TestCase { value: U256, shift: U256, @@ -445,17 +444,20 @@ mod tests { test_interpreter!(interpreter); for test in test_cases { - interpreter.stack.push(test.value)?; - interpreter.stack.push(test.shift)?; - shr(&mut interpreter)?; - let res = interpreter.stack.pop().unwrap(); - assert_eq!(res, test.expected); + assert!((|| { + interpreter.stack.push(test.value)?; + interpreter.stack.push(test.shift)?; + shr(&mut interpreter)?; + let [res] = interpreter.stack.popn::<1>()?; + assert_eq!(res, test.expected); + ControlFlow::Continue(()) + })() + .is_continue()); } - ControlFlow::Continue(()) } #[test] - fn test_arithmetic_shift_right() -> ControlFlow { + fn test_arithmetic_shift_right() { struct TestCase { value: U256, shift: U256, @@ -611,17 +613,20 @@ mod tests { test_interpreter!(interpreter); for test in test_cases { - interpreter.stack.push(test.value)?; - interpreter.stack.push(test.shift)?; - sar(&mut interpreter)?; - let res = interpreter.stack.pop().unwrap(); - assert_eq!(res, test.expected); + assert!((|| { + interpreter.stack.push(test.value)?; + interpreter.stack.push(test.shift)?; + sar(&mut interpreter)?; + let [res] = interpreter.stack.popn::<1>()?; + assert_eq!(res, test.expected); + ControlFlow::Continue(()) + })() + .is_continue()); } - ControlFlow::Continue(()) } #[test] - fn test_byte() -> ControlFlow { + fn test_byte() { struct TestCase { input: U256, index: usize, @@ -640,17 +645,20 @@ mod tests { test_interpreter!(interpreter); for test in test_cases.iter() { - interpreter.stack.push(test.input)?; - interpreter.stack.push(U256::from(test.index))?; - byte(&mut interpreter)?; - let res = interpreter.stack.pop().unwrap(); - assert_eq!(res, test.expected, "Failed at index: {}", test.index); + assert!((|| { + interpreter.stack.push(test.input)?; + interpreter.stack.push(U256::from(test.index))?; + byte(&mut interpreter)?; + let [res] = interpreter.stack.popn::<1>()?; + assert_eq!(res, test.expected, "Failed at index: {}", test.index); + ControlFlow::Continue(()) + })() + .is_continue()); } - ControlFlow::Continue(()) } #[test] - fn test_clz() -> ControlFlow { + fn test_clz() { struct TestCase { value: U256, expected: U256, @@ -695,15 +703,18 @@ mod tests { test_interpreter!(interpreter); for test in test_cases.iter() { - interpreter.stack.push(test.value)?; - clz(&mut interpreter)?; - let res = interpreter.stack.pop().unwrap(); - assert_eq!( - res, test.expected, - "CLZ for value {:#x} failed. Expected: {}, Got: {}", - test.value, test.expected, res - ); + assert!((|| { + interpreter.stack.push(test.value)?; + clz(&mut interpreter)?; + let [res] = interpreter.stack.popn::<1>()?; + assert_eq!( + res, test.expected, + "CLZ for value {:#x} failed. Expected: {}, Got: {}", + test.value, test.expected, res + ); + ControlFlow::Continue(()) + })() + .is_continue()); } - ControlFlow::Continue(()) } } diff --git a/substrate/frame/revive/src/vm/evm/instructions/bitwise/bits.rs b/substrate/frame/revive/src/vm/evm/instructions/bitwise/bits.rs index 3d307c17abc04..ff1e26f41bf9f 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/bitwise/bits.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/bitwise/bits.rs @@ -18,27 +18,12 @@ use sp_core::U256; pub trait Bits { - /// Returns whether a specific bit is set. - /// - /// Returns `false` if `index` exceeds the bit width of the number. - #[must_use] - fn bit(&self, index: usize) -> bool; - /// Arithmetic shift right by `rhs` bits. #[must_use] fn arithmetic_shr(self, rhs: usize) -> Self; } impl Bits for U256 { - fn bit(&self, index: usize) -> bool { - const BITS: usize = 256; - if index >= BITS { - return false; - } - let (limbs, bits) = (index / 64, index % 64); - self.0[limbs] & (1 << bits) != 0 - } - fn arithmetic_shr(self, rhs: usize) -> Self { const BITS: usize = 256; if BITS == 0 { @@ -56,11 +41,11 @@ impl Bits for U256 { #[cfg(test)] mod tests { use super::*; - use core::cmp::min; - use proptest::proptest; // #[test] // fn test_arithmetic_shr() { + // use proptest::proptest; + // use core::cmp::min; // proptest!(|(limbs: [u64; 4], shift in 0..=258)| { // let value = U256(limbs); // let shifted = value.arithmetic_shr(shift); diff --git a/substrate/frame/revive/src/vm/evm/instructions/block_info.rs b/substrate/frame/revive/src/vm/evm/instructions/block_info.rs index dd707b8e6a748..b96ebae6db15d 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/block_info.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/block_info.rs @@ -23,10 +23,8 @@ use crate::{ RuntimeCosts, }; use core::ops::ControlFlow; - -use revm::{interpreter::gas::BASE, primitives::Address}; +use revm::interpreter::gas::BASE; use sp_core::{H160, U256}; -use sp_runtime::DispatchResult; /// EIP-1344: ChainID opcode pub fn chainid<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { @@ -92,6 +90,6 @@ pub fn basefee<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlF } /// EIP-7516: BLOBBASEFEE opcode is not supported -pub fn blob_basefee<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { +pub fn blob_basefee<'ext, E: Ext>(_interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { ControlFlow::Break(Halt::NotActivated) } diff --git a/substrate/frame/revive/src/vm/evm/instructions/contract.rs b/substrate/frame/revive/src/vm/evm/instructions/contract.rs index 1837bcab9ecb3..e0ed806e62fe5 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/contract.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/contract.rs @@ -30,10 +30,7 @@ use core::{ cmp::min, ops::{ControlFlow, Range}, }; -use revm::{ - context_interface::CreateScheme, - interpreter::{interpreter_action::CallScheme, interpreter_types::LoopControl}, -}; +use revm::interpreter::interpreter_action::CallScheme; /// Implements the CREATE/CREATE2 instruction. /// @@ -101,7 +98,7 @@ pub fn create<'ext, const IS_CREATE2: bool, E: Ext>( }, Err(err) => { interpreter.stack.push(U256::zero())?; - return err.into() + return crate::vm::evm::interpreter::exec_error_into_halt::(err) }, } } @@ -142,7 +139,7 @@ pub fn call<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow /// /// Isn't supported yet: [`solc` no longer emits it since Solidity v0.3.0 in 2016] /// (https://soliditylang.org/blog/2016/03/11/solidity-0.3.0-release-announcement/). -pub fn call_code<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { +pub fn call_code<'ext, E: Ext>(_interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { ControlFlow::Break(Halt::NotActivated) } @@ -244,7 +241,7 @@ fn run_call<'a, E: Ext>( }, Err(err) => { interpreter.stack.push(U256::zero())?; - err.into() + crate::vm::evm::interpreter::exec_error_into_halt::(err) }, } } diff --git a/substrate/frame/revive/src/vm/evm/instructions/contract/call_helpers.rs b/substrate/frame/revive/src/vm/evm/instructions/contract/call_helpers.rs index 9ed8a2b6b2c71..52f7440a470c0 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/contract/call_helpers.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/contract/call_helpers.rs @@ -95,9 +95,12 @@ pub fn calc_call_gas<'a, E: Ext>( }, }; if !value.is_zero() { - interpreter.ext.gas_meter_mut().charge_evm(RuntimeCosts::CallTransferSurcharge { - dust_transfer: Pallet::::has_dust(value), - }); + interpreter + .ext + .gas_meter_mut() + .charge_evm(RuntimeCosts::CallTransferSurcharge { + dust_transfer: Pallet::::has_dust(value), + })?; } ControlFlow::Continue(u64::MAX) // TODO: Set the right gas limit diff --git a/substrate/frame/revive/src/vm/evm/instructions/control.rs b/substrate/frame/revive/src/vm/evm/instructions/control.rs index 7c6385945d0b8..00630f759b054 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/control.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/control.rs @@ -20,15 +20,12 @@ use crate::{ evm::{interpreter::Halt, util::as_usize_or_halt_with, Interpreter}, Ext, }, - RuntimeCosts, U256, + U256, }; use core::ops::ControlFlow; -use revm::{ - interpreter::{ - gas::{BASE, HIGH, JUMPDEST, MID}, - interpreter_types::{Immediates, Jumps}, - }, - primitives::Bytes, +use revm::interpreter::{ + gas::{BASE, HIGH, JUMPDEST, MID}, + interpreter_types::Jumps, }; /// Implements the JUMP instruction. @@ -65,7 +62,7 @@ fn jump_inner(interpreter: &mut Interpreter<'_, E>, target: U256) -> Con return ControlFlow::Break(Halt::InvalidJump); } // SAFETY: `is_valid_jump` ensures that `dest` is in bounds. - interpreter.bytecode.absolute_jump(target); + interpreter.bytecode.absolute_jump(target - 1); // pc will be increased by the interpreter loop ControlFlow::Continue(()) } @@ -134,6 +131,6 @@ pub fn invalid<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlF } /// Unknown opcode. This opcode halts the execution. -pub fn unknown<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { +pub fn unknown<'ext, E: Ext>(_interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { ControlFlow::Break(Halt::OpcodeNotFound) } diff --git a/substrate/frame/revive/src/vm/evm/instructions/host.rs b/substrate/frame/revive/src/vm/evm/instructions/host.rs index 48fddd0926bd4..8c0c65863df43 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/host.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/host.rs @@ -253,7 +253,7 @@ pub fn log<'ext, const N: usize, E: Ext>( /// Implements the SELFDESTRUCT instruction. /// /// Halt execution and register account for later deletion. -pub fn selfdestruct<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { +pub fn selfdestruct<'ext, E: Ext>(_interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { // TODO: for now this instruction is not supported ControlFlow::Break(Halt::NotActivated) } diff --git a/substrate/frame/revive/src/vm/evm/instructions/mod.rs b/substrate/frame/revive/src/vm/evm/instructions/mod.rs index a37b3b38a4669..e1ae25dcfaa58 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/mod.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/mod.rs @@ -18,7 +18,7 @@ //! EVM opcode implementations. use super::interpreter::Interpreter; -use crate::vm::Ext; +use crate::vm::{evm::Halt, Ext}; #[macro_use] mod macros; @@ -44,176 +44,177 @@ mod system; mod tx_info; /// Utility functions and helpers for instruction implementation. mod utility; -// -// /// Returns the instruction table for the given spec. -// pub const fn instruction_table<'a, E: Ext>() -> [Instruction, DummyHost>; -// 256] { -// use revm::bytecode::opcode::*; -// let mut table = [control::unknown as Instruction, DummyHost>; 256]; -// -// table[STOP as usize] = control::stop; -// table[ADD as usize] = arithmetic::add; -// table[MUL as usize] = arithmetic::mul; -// table[SUB as usize] = arithmetic::sub; -// table[DIV as usize] = arithmetic::div; -// table[SDIV as usize] = arithmetic::sdiv; -// table[MOD as usize] = arithmetic::rem; -// table[SMOD as usize] = arithmetic::smod; -// table[ADDMOD as usize] = arithmetic::addmod; -// table[MULMOD as usize] = arithmetic::mulmod; -// table[EXP as usize] = arithmetic::exp; -// table[SIGNEXTEND as usize] = arithmetic::signextend; -// -// table[LT as usize] = bitwise::lt; -// table[GT as usize] = bitwise::gt; -// table[SLT as usize] = bitwise::slt; -// table[SGT as usize] = bitwise::sgt; -// table[EQ as usize] = bitwise::eq; -// table[ISZERO as usize] = bitwise::iszero; -// table[AND as usize] = bitwise::bitand; -// table[OR as usize] = bitwise::bitor; -// table[XOR as usize] = bitwise::bitxor; -// table[NOT as usize] = bitwise::not; -// table[BYTE as usize] = bitwise::byte; -// table[SHL as usize] = bitwise::shl; -// table[SHR as usize] = bitwise::shr; -// table[SAR as usize] = bitwise::sar; -// table[CLZ as usize] = bitwise::clz; -// -// table[KECCAK256 as usize] = system::keccak256; -// -// table[ADDRESS as usize] = system::address; -// table[BALANCE as usize] = host::balance; -// table[ORIGIN as usize] = tx_info::origin; -// table[CALLER as usize] = system::caller; -// table[CALLVALUE as usize] = system::callvalue; -// table[CALLDATALOAD as usize] = system::calldataload; -// table[CALLDATASIZE as usize] = system::calldatasize; -// table[CALLDATACOPY as usize] = system::calldatacopy; -// table[CODESIZE as usize] = system::codesize; -// table[CODECOPY as usize] = system::codecopy; -// -// table[GASPRICE as usize] = tx_info::gasprice; -// table[EXTCODESIZE as usize] = host::extcodesize; -// table[EXTCODECOPY as usize] = host::extcodecopy; -// table[RETURNDATASIZE as usize] = system::returndatasize; -// table[RETURNDATACOPY as usize] = system::returndatacopy; -// table[EXTCODEHASH as usize] = host::extcodehash; -// table[BLOCKHASH as usize] = host::blockhash; -// table[COINBASE as usize] = block_info::coinbase; -// table[TIMESTAMP as usize] = block_info::timestamp; -// table[NUMBER as usize] = block_info::block_number; -// table[DIFFICULTY as usize] = block_info::difficulty; -// table[GASLIMIT as usize] = block_info::gaslimit; -// table[CHAINID as usize] = block_info::chainid; -// table[SELFBALANCE as usize] = host::selfbalance; -// table[BASEFEE as usize] = block_info::basefee; -// table[BLOBHASH as usize] = tx_info::blob_hash; -// table[BLOBBASEFEE as usize] = block_info::blob_basefee; -// -// table[POP as usize] = stack::pop; -// table[MLOAD as usize] = memory::mload; -// table[MSTORE as usize] = memory::mstore; -// table[MSTORE8 as usize] = memory::mstore8; -// table[SLOAD as usize] = host::sload; -// table[SSTORE as usize] = host::sstore; -// table[JUMP as usize] = control::jump; -// table[JUMPI as usize] = control::jumpi; -// table[PC as usize] = control::pc; -// table[MSIZE as usize] = memory::msize; -// table[GAS as usize] = system::gas; -// table[JUMPDEST as usize] = control::jumpdest; -// table[TLOAD as usize] = host::tload; -// table[TSTORE as usize] = host::tstore; -// table[MCOPY as usize] = memory::mcopy; -// -// table[PUSH0 as usize] = stack::push0; -// table[PUSH1 as usize] = stack::push::<1, _>; -// table[PUSH2 as usize] = stack::push::<2, _>; -// table[PUSH3 as usize] = stack::push::<3, _>; -// table[PUSH4 as usize] = stack::push::<4, _>; -// table[PUSH5 as usize] = stack::push::<5, _>; -// table[PUSH6 as usize] = stack::push::<6, _>; -// table[PUSH7 as usize] = stack::push::<7, _>; -// table[PUSH8 as usize] = stack::push::<8, _>; -// table[PUSH9 as usize] = stack::push::<9, _>; -// table[PUSH10 as usize] = stack::push::<10, _>; -// table[PUSH11 as usize] = stack::push::<11, _>; -// table[PUSH12 as usize] = stack::push::<12, _>; -// table[PUSH13 as usize] = stack::push::<13, _>; -// table[PUSH14 as usize] = stack::push::<14, _>; -// table[PUSH15 as usize] = stack::push::<15, _>; -// table[PUSH16 as usize] = stack::push::<16, _>; -// table[PUSH17 as usize] = stack::push::<17, _>; -// table[PUSH18 as usize] = stack::push::<18, _>; -// table[PUSH19 as usize] = stack::push::<19, _>; -// table[PUSH20 as usize] = stack::push::<20, _>; -// table[PUSH21 as usize] = stack::push::<21, _>; -// table[PUSH22 as usize] = stack::push::<22, _>; -// table[PUSH23 as usize] = stack::push::<23, _>; -// table[PUSH24 as usize] = stack::push::<24, _>; -// table[PUSH25 as usize] = stack::push::<25, _>; -// table[PUSH26 as usize] = stack::push::<26, _>; -// table[PUSH27 as usize] = stack::push::<27, _>; -// table[PUSH28 as usize] = stack::push::<28, _>; -// table[PUSH29 as usize] = stack::push::<29, _>; -// table[PUSH30 as usize] = stack::push::<30, _>; -// table[PUSH31 as usize] = stack::push::<31, _>; -// table[PUSH32 as usize] = stack::push::<32, _>; -// -// table[DUP1 as usize] = stack::dup::<1, _>; -// table[DUP2 as usize] = stack::dup::<2, _>; -// table[DUP3 as usize] = stack::dup::<3, _>; -// table[DUP4 as usize] = stack::dup::<4, _>; -// table[DUP5 as usize] = stack::dup::<5, _>; -// table[DUP6 as usize] = stack::dup::<6, _>; -// table[DUP7 as usize] = stack::dup::<7, _>; -// table[DUP8 as usize] = stack::dup::<8, _>; -// table[DUP9 as usize] = stack::dup::<9, _>; -// table[DUP10 as usize] = stack::dup::<10, _>; -// table[DUP11 as usize] = stack::dup::<11, _>; -// table[DUP12 as usize] = stack::dup::<12, _>; -// table[DUP13 as usize] = stack::dup::<13, _>; -// table[DUP14 as usize] = stack::dup::<14, _>; -// table[DUP15 as usize] = stack::dup::<15, _>; -// table[DUP16 as usize] = stack::dup::<16, _>; -// -// table[SWAP1 as usize] = stack::swap::<1, _>; -// table[SWAP2 as usize] = stack::swap::<2, _>; -// table[SWAP3 as usize] = stack::swap::<3, _>; -// table[SWAP4 as usize] = stack::swap::<4, _>; -// table[SWAP5 as usize] = stack::swap::<5, _>; -// table[SWAP6 as usize] = stack::swap::<6, _>; -// table[SWAP7 as usize] = stack::swap::<7, _>; -// table[SWAP8 as usize] = stack::swap::<8, _>; -// table[SWAP9 as usize] = stack::swap::<9, _>; -// table[SWAP10 as usize] = stack::swap::<10, _>; -// table[SWAP11 as usize] = stack::swap::<11, _>; -// table[SWAP12 as usize] = stack::swap::<12, _>; -// table[SWAP13 as usize] = stack::swap::<13, _>; -// table[SWAP14 as usize] = stack::swap::<14, _>; -// table[SWAP15 as usize] = stack::swap::<15, _>; -// table[SWAP16 as usize] = stack::swap::<16, _>; -// -// table[LOG0 as usize] = host::log::<0, _>; -// table[LOG1 as usize] = host::log::<1, _>; -// table[LOG2 as usize] = host::log::<2, _>; -// table[LOG3 as usize] = host::log::<3, _>; -// table[LOG4 as usize] = host::log::<4, _>; -// -// table[CREATE as usize] = contract::create::; -// table[CALL as usize] = contract::call; -// table[CALLCODE as usize] = contract::call_code; -// table[RETURN as usize] = control::ret; -// table[DELEGATECALL as usize] = contract::delegate_call; -// table[CREATE2 as usize] = contract::create::; -// -// table[STATICCALL as usize] = contract::static_call; -// table[REVERT as usize] = control::revert; -// table[INVALID as usize] = control::invalid; -// table[SELFDESTRUCT as usize] = host::selfdestruct; -// table -// } + +pub type Instruction = fn(&mut Interpreter<'_, E>) -> core::ops::ControlFlow; +pub type InstructionTable = [Instruction; 256]; + +pub const fn instruction_table() -> [Instruction; 256] { + use revm::bytecode::opcode::*; + let mut table = [control::unknown as Instruction; 256]; + + table[STOP as usize] = control::stop; + table[ADD as usize] = arithmetic::add; + table[MUL as usize] = arithmetic::mul; + table[SUB as usize] = arithmetic::sub; + table[DIV as usize] = arithmetic::div; + table[SDIV as usize] = arithmetic::sdiv; + table[MOD as usize] = arithmetic::rem; + table[SMOD as usize] = arithmetic::smod; + table[ADDMOD as usize] = arithmetic::addmod; + table[MULMOD as usize] = arithmetic::mulmod; + table[EXP as usize] = arithmetic::exp; + table[SIGNEXTEND as usize] = arithmetic::signextend; + + table[LT as usize] = bitwise::lt; + table[GT as usize] = bitwise::gt; + table[SLT as usize] = bitwise::slt; + table[SGT as usize] = bitwise::sgt; + table[EQ as usize] = bitwise::eq; + table[ISZERO as usize] = bitwise::iszero; + table[AND as usize] = bitwise::bitand; + table[OR as usize] = bitwise::bitor; + table[XOR as usize] = bitwise::bitxor; + table[NOT as usize] = bitwise::not; + table[BYTE as usize] = bitwise::byte; + table[SHL as usize] = bitwise::shl; + table[SHR as usize] = bitwise::shr; + table[SAR as usize] = bitwise::sar; + table[CLZ as usize] = bitwise::clz; + + table[KECCAK256 as usize] = system::keccak256; + + table[ADDRESS as usize] = system::address; + table[BALANCE as usize] = host::balance; + table[ORIGIN as usize] = tx_info::origin; + table[CALLER as usize] = system::caller; + table[CALLVALUE as usize] = system::callvalue; + table[CALLDATALOAD as usize] = system::calldataload; + table[CALLDATASIZE as usize] = system::calldatasize; + table[CALLDATACOPY as usize] = system::calldatacopy; + table[CODESIZE as usize] = system::codesize; + table[CODECOPY as usize] = system::codecopy; + + table[GASPRICE as usize] = tx_info::gasprice; + table[EXTCODESIZE as usize] = host::extcodesize; + table[EXTCODECOPY as usize] = host::extcodecopy; + table[RETURNDATASIZE as usize] = system::returndatasize; + table[RETURNDATACOPY as usize] = system::returndatacopy; + table[EXTCODEHASH as usize] = host::extcodehash; + table[BLOCKHASH as usize] = host::blockhash; + table[COINBASE as usize] = block_info::coinbase; + table[TIMESTAMP as usize] = block_info::timestamp; + table[NUMBER as usize] = block_info::block_number; + table[DIFFICULTY as usize] = block_info::difficulty; + table[GASLIMIT as usize] = block_info::gaslimit; + table[CHAINID as usize] = block_info::chainid; + table[SELFBALANCE as usize] = host::selfbalance; + table[BASEFEE as usize] = block_info::basefee; + table[BLOBHASH as usize] = tx_info::blob_hash; + table[BLOBBASEFEE as usize] = block_info::blob_basefee; + + table[POP as usize] = stack::pop; + table[MLOAD as usize] = memory::mload; + table[MSTORE as usize] = memory::mstore; + table[MSTORE8 as usize] = memory::mstore8; + table[SLOAD as usize] = host::sload; + table[SSTORE as usize] = host::sstore; + table[JUMP as usize] = control::jump; + table[JUMPI as usize] = control::jumpi; + table[PC as usize] = control::pc; + table[MSIZE as usize] = memory::msize; + table[GAS as usize] = system::gas; + table[JUMPDEST as usize] = control::jumpdest; + table[TLOAD as usize] = host::tload; + table[TSTORE as usize] = host::tstore; + table[MCOPY as usize] = memory::mcopy; + + table[PUSH0 as usize] = stack::push0; + table[PUSH1 as usize] = stack::push::<1, _>; + table[PUSH2 as usize] = stack::push::<2, _>; + table[PUSH3 as usize] = stack::push::<3, _>; + table[PUSH4 as usize] = stack::push::<4, _>; + table[PUSH5 as usize] = stack::push::<5, _>; + table[PUSH6 as usize] = stack::push::<6, _>; + table[PUSH7 as usize] = stack::push::<7, _>; + table[PUSH8 as usize] = stack::push::<8, _>; + table[PUSH9 as usize] = stack::push::<9, _>; + table[PUSH10 as usize] = stack::push::<10, _>; + table[PUSH11 as usize] = stack::push::<11, _>; + table[PUSH12 as usize] = stack::push::<12, _>; + table[PUSH13 as usize] = stack::push::<13, _>; + table[PUSH14 as usize] = stack::push::<14, _>; + table[PUSH15 as usize] = stack::push::<15, _>; + table[PUSH16 as usize] = stack::push::<16, _>; + table[PUSH17 as usize] = stack::push::<17, _>; + table[PUSH18 as usize] = stack::push::<18, _>; + table[PUSH19 as usize] = stack::push::<19, _>; + table[PUSH20 as usize] = stack::push::<20, _>; + table[PUSH21 as usize] = stack::push::<21, _>; + table[PUSH22 as usize] = stack::push::<22, _>; + table[PUSH23 as usize] = stack::push::<23, _>; + table[PUSH24 as usize] = stack::push::<24, _>; + table[PUSH25 as usize] = stack::push::<25, _>; + table[PUSH26 as usize] = stack::push::<26, _>; + table[PUSH27 as usize] = stack::push::<27, _>; + table[PUSH28 as usize] = stack::push::<28, _>; + table[PUSH29 as usize] = stack::push::<29, _>; + table[PUSH30 as usize] = stack::push::<30, _>; + table[PUSH31 as usize] = stack::push::<31, _>; + table[PUSH32 as usize] = stack::push::<32, _>; + + table[DUP1 as usize] = stack::dup::<1, _>; + table[DUP2 as usize] = stack::dup::<2, _>; + table[DUP3 as usize] = stack::dup::<3, _>; + table[DUP4 as usize] = stack::dup::<4, _>; + table[DUP5 as usize] = stack::dup::<5, _>; + table[DUP6 as usize] = stack::dup::<6, _>; + table[DUP7 as usize] = stack::dup::<7, _>; + table[DUP8 as usize] = stack::dup::<8, _>; + table[DUP9 as usize] = stack::dup::<9, _>; + table[DUP10 as usize] = stack::dup::<10, _>; + table[DUP11 as usize] = stack::dup::<11, _>; + table[DUP12 as usize] = stack::dup::<12, _>; + table[DUP13 as usize] = stack::dup::<13, _>; + table[DUP14 as usize] = stack::dup::<14, _>; + table[DUP15 as usize] = stack::dup::<15, _>; + table[DUP16 as usize] = stack::dup::<16, _>; + + table[SWAP1 as usize] = stack::swap::<1, _>; + table[SWAP2 as usize] = stack::swap::<2, _>; + table[SWAP3 as usize] = stack::swap::<3, _>; + table[SWAP4 as usize] = stack::swap::<4, _>; + table[SWAP5 as usize] = stack::swap::<5, _>; + table[SWAP6 as usize] = stack::swap::<6, _>; + table[SWAP7 as usize] = stack::swap::<7, _>; + table[SWAP8 as usize] = stack::swap::<8, _>; + table[SWAP9 as usize] = stack::swap::<9, _>; + table[SWAP10 as usize] = stack::swap::<10, _>; + table[SWAP11 as usize] = stack::swap::<11, _>; + table[SWAP12 as usize] = stack::swap::<12, _>; + table[SWAP13 as usize] = stack::swap::<13, _>; + table[SWAP14 as usize] = stack::swap::<14, _>; + table[SWAP15 as usize] = stack::swap::<15, _>; + table[SWAP16 as usize] = stack::swap::<16, _>; + + table[LOG0 as usize] = host::log::<0, _>; + table[LOG1 as usize] = host::log::<1, _>; + table[LOG2 as usize] = host::log::<2, _>; + table[LOG3 as usize] = host::log::<3, _>; + table[LOG4 as usize] = host::log::<4, _>; + + table[CREATE as usize] = contract::create::; + table[CALL as usize] = contract::call; + table[CALLCODE as usize] = contract::call_code; + table[RETURN as usize] = control::ret; + table[DELEGATECALL as usize] = contract::delegate_call; + table[CREATE2 as usize] = contract::create::; + + table[STATICCALL as usize] = contract::static_call; + table[REVERT as usize] = control::revert; + table[INVALID as usize] = control::invalid; + table[SELFDESTRUCT as usize] = host::selfdestruct; + table +} // // #[cfg(test)] // mod tests { diff --git a/substrate/frame/revive/src/vm/evm/instructions/system.rs b/substrate/frame/revive/src/vm/evm/instructions/system.rs index 23f74e740dbdb..69679ec83393f 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/system.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/system.rs @@ -24,8 +24,8 @@ use crate::{ Config, U256, }; use core::{ops::ControlFlow, ptr}; -use revm::interpreter::gas::{copy_cost_verylow, BASE, VERYLOW}; -use sp_core::{H160, H256}; +use revm::interpreter::gas::{BASE, VERYLOW}; +use sp_core::H256; use sp_io::hashing::keccak_256; // TODO: Fix the gas handling for the memory operations @@ -219,7 +219,7 @@ pub fn memory_resize<'a, E: Ext>( } let memory_offset = as_usize_or_halt_with(memory_offset, || Halt::InvalidOperandOOG)?; - interpreter.memory.resize(memory_offset, len); + interpreter.memory.resize(memory_offset, len)?; ControlFlow::Continue(Some(memory_offset)) } diff --git a/substrate/frame/revive/src/vm/evm/instructions/tx_info.rs b/substrate/frame/revive/src/vm/evm/instructions/tx_info.rs index fe0891955357c..1967cdde70667 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/tx_info.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/tx_info.rs @@ -25,7 +25,6 @@ use crate::{ Config, U256, }; use core::ops::ControlFlow; -use sp_core::{H160, H256}; /// Implements the GASPRICE instruction. /// @@ -52,6 +51,6 @@ pub fn origin<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFl /// Implements the BLOBHASH instruction. /// /// EIP-4844: Shard Blob Transactions - gets the hash of a transaction blob. -pub fn blob_hash<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { +pub fn blob_hash<'ext, E: Ext>(_interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { ControlFlow::Break(Halt::NotActivated) } diff --git a/substrate/frame/revive/src/vm/evm/interpreter.rs b/substrate/frame/revive/src/vm/evm/interpreter.rs index 273b61d64bc23..35578bccaeb78 100644 --- a/substrate/frame/revive/src/vm/evm/interpreter.rs +++ b/substrate/frame/revive/src/vm/evm/interpreter.rs @@ -1,58 +1,78 @@ //! Custom EVM interpreter implementation using sp_core types use super::ExtBytecode; -use crate::{ - vm::{ - evm::{memory::Memory, stack::Stack}, - Ext, - }, - DispatchError, Error, +use crate::vm::{ + evm::{memory::Memory, stack::Stack}, + ExecResult, Ext, }; use alloc::vec::Vec; use core::ops::ControlFlow; #[derive(Debug, PartialEq)] pub enum Halt { - // Successful termination with output - Return(Vec), // InstructionResult::Return - Revert(Vec), // InstructionResult::Revert - - // Successful termination without output - Stop, // InstructionResult::Stop - SelfDestruct, // InstructionResult::SelfDestruct - - // Resource limit errors - OutOfGas, // InstructionResult::OutOfGas - StackOverflow, // InstructionResult::StackOverflow - StackUnderflow, // InstructionResult::StackUnderflow - MemoryOOG, // InstructionResult::MemoryOOG + Return(Vec), + Revert(Vec), + + Stop, + SelfDestruct, + + OutOfGas, + StackOverflow, + StackUnderflow, + MemoryOOG, InvalidOperandOOG, - // Invalid operation errors - InvalidJump, // InstructionResult::InvalidJump - OpcodeNotFound, // InstructionResult::OpcodeNotFound - InvalidFEOpcode, // InstructionResult::InvalidFEOpcode - - // EVM rule violations - StateChangeDuringStaticCall, // InstructionResult::StateChangeDuringStaticCall - CallDepthExceeded, // InstructionResult::CallTooDeep - CreateInitCodeSizeLimit, // InstructionResult::CreateInitCodeSizeLimit - CallNotAllowedInsideStatic, // InstructionResult::CallNotAllowedInsideStatic - - // External/system errors - FatalExternalError, // InstructionResult::FatalExternalError - ReentrancyGuard, // InstructionResult::ReentrancySentryOOG - - // Additional REVM errors you might want - OutOfOffset, // InstructionResult::OutOfOffset - NotActivated, // InstructionResult::NotActivated (for EIP activation) - EOFOpcodeDisabledInLegacy, // InstructionResult::EOFOpcodeDisabledInLegacy + InvalidJump, + OpcodeNotFound, + InvalidFEOpcode, + + StateChangeDuringStaticCall, + CallDepthExceeded, + CreateInitCodeSizeLimit, + CallNotAllowedInsideStatic, + + FatalExternalError, + ReentrancyGuard, + + OutOfOffset, + NotActivated, + EOFOpcodeDisabledInLegacy, } -impl From for ControlFlow { - fn from(err: crate::ExecError) -> Self { - todo!() - } +/// Convert ExecError to ControlFlow using proper error type comparison +pub fn exec_error_into_halt(from: crate::ExecError) -> ControlFlow { + use crate::{DispatchError, Error}; + + let static_memory_too_large: DispatchError = Error::::StaticMemoryTooLarge.into(); + let code_rejected: DispatchError = Error::::CodeRejected.into(); + let transfer_failed: DispatchError = Error::::TransferFailed.into(); + let duplicate_contract: DispatchError = Error::::DuplicateContract.into(); + let value_too_large: DispatchError = Error::::ValueTooLarge.into(); + let out_of_gas: DispatchError = Error::::OutOfGas.into(); + let out_of_deposit: DispatchError = Error::::StorageDepositLimitExhausted.into(); + let invalid_instruction: DispatchError = Error::::InvalidInstruction.into(); + let state_change_denied: DispatchError = Error::::StateChangeDenied.into(); + let contract_trapped: DispatchError = Error::::ContractTrapped.into(); + let out_of_bounds: DispatchError = Error::::OutOfBounds.into(); + let max_call_depth_reached: DispatchError = Error::::MaxCallDepthReached.into(); + + let halt = match from.error { + err if err == static_memory_too_large => Halt::MemoryOOG, + err if err == code_rejected => Halt::OpcodeNotFound, + err if err == transfer_failed => Halt::OutOfGas, + err if err == duplicate_contract => Halt::OutOfGas, + err if err == value_too_large => Halt::OutOfGas, + err if err == out_of_deposit => Halt::OutOfGas, + err if err == out_of_gas => Halt::OutOfGas, + err if err == invalid_instruction => Halt::OpcodeNotFound, + err if err == state_change_denied => Halt::StateChangeDuringStaticCall, + err if err == contract_trapped => Halt::FatalExternalError, + err if err == out_of_bounds => Halt::OutOfOffset, + err if err == max_call_depth_reached => Halt::CallDepthExceeded, + _ => Halt::FatalExternalError, + }; + + ControlFlow::Break(halt) } /// EVM interpreter state using sp_core types @@ -63,7 +83,7 @@ pub struct Interpreter<'a, E: Ext> { /// The bytecode being executed pub bytecode: ExtBytecode, /// Input data for the current call - pub input: Vec, + pub input: Vec, // TODO maybe just &'a[u8] /// The execution stack pub stack: Stack, /// EVM memory @@ -75,6 +95,44 @@ impl<'a, E: Ext> Interpreter<'a, E> { pub fn new(bytecode: ExtBytecode, input: Vec, ext: &'a mut E) -> Self { Self { ext, bytecode, input, stack: Stack::new(), memory: Memory::new() } } -} -pub type InstructionTable = [fn(Interpreter<'_, E>); 256]; + /// Convert a Halt reason into an ExecResult + pub fn into_exec_result(&self, halt: Halt) -> ExecResult { + use crate::{primitives::ExecReturnValue, Error, ExecError}; + use pallet_revive_uapi::ReturnFlags; + + match halt { + Halt::Return(data) => Ok(ExecReturnValue { flags: ReturnFlags::empty(), data }), + Halt::Revert(data) => Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data }), + Halt::Stop => Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() }), + Halt::SelfDestruct => + Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() }), + + Halt::OutOfGas => Err(ExecError::from(Error::::OutOfGas)), + Halt::StackOverflow => Err(ExecError::from(Error::::ContractTrapped)), + Halt::StackUnderflow => Err(ExecError::from(Error::::ContractTrapped)), + Halt::MemoryOOG => Err(ExecError::from(Error::::StaticMemoryTooLarge)), + Halt::InvalidOperandOOG => Err(ExecError::from(Error::::OutOfGas)), + + Halt::InvalidJump => Err(ExecError::from(Error::::InvalidInstruction)), + Halt::OpcodeNotFound => Err(ExecError::from(Error::::InvalidInstruction)), + Halt::InvalidFEOpcode => Err(ExecError::from(Error::::InvalidInstruction)), + + Halt::StateChangeDuringStaticCall => + Err(ExecError::from(Error::::StateChangeDenied)), + Halt::CallDepthExceeded => Err(ExecError::from(Error::::MaxCallDepthReached)), + Halt::CreateInitCodeSizeLimit => + Err(ExecError::from(Error::::StaticMemoryTooLarge)), + Halt::CallNotAllowedInsideStatic => + Err(ExecError::from(Error::::StateChangeDenied)), + + Halt::FatalExternalError => Err(ExecError::from(Error::::ContractTrapped)), + Halt::ReentrancyGuard => Err(ExecError::from(Error::::ContractTrapped)), + + Halt::OutOfOffset => Err(ExecError::from(Error::::OutOfBounds)), + Halt::NotActivated => Err(ExecError::from(Error::::InvalidInstruction)), + Halt::EOFOpcodeDisabledInLegacy => + Err(ExecError::from(Error::::InvalidInstruction)), + } + } +} diff --git a/substrate/frame/revive/src/vm/evm/memory.rs b/substrate/frame/revive/src/vm/evm/memory.rs index 54bbeebdbfdf7..c2e242650f535 100644 --- a/substrate/frame/revive/src/vm/evm/memory.rs +++ b/substrate/frame/revive/src/vm/evm/memory.rs @@ -124,12 +124,12 @@ mod tests { let mut memory = Memory::new(); assert_eq!(memory.size(), 0); - memory.resize(100); - assert_eq!(memory.size(), 100); + assert!(memory.resize(0, 100).is_continue()); + assert_eq!(memory.size(), 128); // Should be word-aligned (4 words * 32 bytes) // Resizing to smaller size should not shrink - memory.resize(50); - assert_eq!(memory.size(), 100); + assert!(memory.resize(0, 50).is_continue()); + assert_eq!(memory.size(), 128); // Should stay the same } #[test] diff --git a/substrate/frame/revive/src/vm/evm/stack.rs b/substrate/frame/revive/src/vm/evm/stack.rs index 22381402c332f..5671ee3157470 100644 --- a/substrate/frame/revive/src/vm/evm/stack.rs +++ b/substrate/frame/revive/src/vm/evm/stack.rs @@ -67,15 +67,6 @@ impl Stack { } } - /// Pop a value from the stack - /// Returns Continue(value) if successful, Break(Halt::StackUnderflow) if stack is empty - pub fn pop(&mut self) -> ControlFlow { - match self.0.pop() { - Some(value) => ControlFlow::Continue(value), - None => ControlFlow::Break(Halt::StackUnderflow), - } - } - /// Get a reference to the top stack item without removing it pub fn top(&self) -> Option<&U256> { self.0.last() @@ -87,6 +78,7 @@ impl Stack { } /// Check if stack is empty + #[cfg(test)] pub fn is_empty(&self) -> bool { self.0.is_empty() } @@ -181,9 +173,9 @@ mod tests { assert_eq!(stack.len(), 1); // Test pop - assert_eq!(stack.pop(), ControlFlow::Continue(U256::from(42))); + assert_eq!(stack.popn::<1>(), ControlFlow::Continue([U256::from(42)])); assert_eq!(stack.len(), 0); - assert_eq!(stack.pop(), ControlFlow::Break(Halt::StackUnderflow)); + assert_eq!(stack.popn::<1>(), ControlFlow::Break(Halt::StackUnderflow)); } #[test] @@ -191,9 +183,9 @@ mod tests { let mut stack = Stack::new(); // Push some values - stack.push(U256::from(1)); - stack.push(U256::from(2)); - stack.push(U256::from(3)); + for i in 1..=3 { + assert!(stack.push(U256::from(i)).is_continue()); + } // Pop multiple values let result: ControlFlow<_, [U256; 2]> = stack.popn(); @@ -210,10 +202,9 @@ mod tests { let mut stack = Stack::new(); // Push some values - stack.push(U256::from(1)); - stack.push(U256::from(2)); - stack.push(U256::from(3)); - stack.push(U256::from(4)); + for i in 1..=4 { + assert!(stack.push(U256::from(i)).is_continue()); + } // Pop 2 values and get mutable reference to new top let result = stack.popn_top::<2>(); @@ -290,10 +281,10 @@ mod tests { let mut stack = Stack::new(); assert!(stack.is_empty()); - let _ = stack.push(U256::from(1)); + assert!(stack.push(U256::from(1)).is_continue()); assert!(!stack.is_empty()); - let _ = stack.pop(); + assert!(stack.popn::<1>().is_continue()); assert!(stack.is_empty()); } } diff --git a/substrate/frame/revive/src/vm/evm/util.rs b/substrate/frame/revive/src/vm/evm/util.rs index cf74b56ed1eed..d4a69a0baf56f 100644 --- a/substrate/frame/revive/src/vm/evm/util.rs +++ b/substrate/frame/revive/src/vm/evm/util.rs @@ -1,18 +1,8 @@ use crate::{ - vm::{ - evm::{interpreter::Halt, Interpreter}, - Ext, - }, - RuntimeCosts, U256, + vm::evm::interpreter::Halt, + U256, }; use core::ops::ControlFlow; -use revm::{ - interpreter::{ - gas::{BASE, HIGH, JUMPDEST, MID}, - interpreter_types::{Immediates, Jumps}, - }, - primitives::Bytes, -}; /// Helper function to convert U256 to usize, checking for overflow pub fn as_usize_or_halt_with(value: U256, halt: impl Fn() -> Halt) -> ControlFlow { diff --git a/substrate/frame/revive/src/vm/mod.rs b/substrate/frame/revive/src/vm/mod.rs index 0112929e50729..0618e0da6da3e 100644 --- a/substrate/frame/revive/src/vm/mod.rs +++ b/substrate/frame/revive/src/vm/mod.rs @@ -347,12 +347,9 @@ where self.prepare_call(pvm::Runtime::new(ext, input_data), function, 0)?; prepared_call.call() } else if T::AllowEVMBytecode::get() { - todo!() - // use crate::vm::evm::EVMInputs; - // use revm::bytecode::Bytecode; - // let inputs = EVMInputs::new(input_data); - // let bytecode = Bytecode::new_raw(self.code.into()); - // evm::call(bytecode, ext, inputs) + use revm::bytecode::Bytecode; + let bytecode = Bytecode::new_raw(self.code.into()); + evm::call(bytecode, ext, input_data) } else { Err(Error::::CodeRejected.into()) } From 9d8522511280b4bff2fd6ad8902c081c65955439 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Tue, 23 Sep 2025 13:48:02 +0200 Subject: [PATCH 31/76] fixes --- substrate/frame/revive/src/tests.rs | 2 +- substrate/frame/revive/src/tests/sol.rs | 15 ++--- .../frame/revive/src/tests/sol/block_info.rs | 21 +++---- .../frame/revive/src/tests/sol/contract.rs | 6 +- .../frame/revive/src/tests/sol/control.rs | 18 +++--- substrate/frame/revive/src/tests/sol/host.rs | 56 +++++++++---------- .../frame/revive/src/tests/sol/memory.rs | 27 ++++----- substrate/frame/revive/src/tests/sol/stack.rs | 12 ++-- .../frame/revive/src/tests/sol/system.rs | 40 ++++++------- .../frame/revive/src/tests/sol/tx_info.rs | 6 +- substrate/frame/revive/todos.md | 20 +++++++ 11 files changed, 116 insertions(+), 107 deletions(-) create mode 100644 substrate/frame/revive/todos.md diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index aa36aa102f1ff..9dda6f55cadb9 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -18,7 +18,7 @@ mod pallet_dummy; mod precompiles; mod pvm; -// mod sol; +mod sol; use crate::{ self as pallet_revive, diff --git a/substrate/frame/revive/src/tests/sol.rs b/substrate/frame/revive/src/tests/sol.rs index a1da3de122d11..64e64e6455f16 100644 --- a/substrate/frame/revive/src/tests/sol.rs +++ b/substrate/frame/revive/src/tests/sol.rs @@ -25,7 +25,8 @@ use crate::{ }, Code, Config, PristineCode, }; -use alloy_core::{primitives::U256, sol_types::SolInterface}; +use alloy_core::{primitives, sol_types::SolInterface}; +use crate::U256; use frame_support::traits::fungible::Mutate; use pallet_revive_fixtures::{compile_module_with_type, Fibonacci, FixtureType}; use pretty_assertions::assert_eq; @@ -88,13 +89,13 @@ fn basic_evm_flow_works() { let result = builder::bare_call(addr) .data( - Fibonacci::FibonacciCalls::fib(Fibonacci::fibCall { n: U256::from(10u64) }) + Fibonacci::FibonacciCalls::fib(Fibonacci::fibCall { n: primitives::U256::from(10u64) }) .abi_encode(), ) .build_and_unwrap_result(); assert_eq!( U256::from(55u32), - U256::from_be_bytes::<32>(result.data.try_into().unwrap()) + U256::from_big_endian(&result.data) ); } @@ -140,15 +141,15 @@ fn basic_evm_flow_tracing_works() { let result = trace(&mut call_tracer, || { builder::bare_call(addr) .data( - Fibonacci::FibonacciCalls::fib(Fibonacci::fibCall { n: U256::from(10u64) }) + Fibonacci::FibonacciCalls::fib(Fibonacci::fibCall { n: primitives::U256::from(10u64) }) .abi_encode(), ) .build_and_unwrap_result() }); assert_eq!( - U256::from(55u32), - U256::from_be_bytes::<32>(result.data.clone().try_into().unwrap()) + crate::U256::from(55u32), + crate::U256::from_big_endian(&result.data) ); assert_eq!( @@ -157,7 +158,7 @@ fn basic_evm_flow_tracing_works() { call_type: CallType::Call, from: ALICE_ADDR, to: addr, - input: Fibonacci::FibonacciCalls::fib(Fibonacci::fibCall { n: U256::from(10u64) }) + input: Fibonacci::FibonacciCalls::fib(Fibonacci::fibCall { n: primitives::U256::from(10u64) }) .abi_encode() .into(), output: result.data.into(), diff --git a/substrate/frame/revive/src/tests/sol/block_info.rs b/substrate/frame/revive/src/tests/sol/block_info.rs index 158100805cfe1..a5bcdfd4f2f8f 100644 --- a/substrate/frame/revive/src/tests/sol/block_info.rs +++ b/substrate/frame/revive/src/tests/sol/block_info.rs @@ -20,15 +20,15 @@ use crate::{ test_utils::{builder::Contract, ALICE}, tests::{builder, Contracts, ExtBuilder, System, Test, Timestamp}, - vm::evm::{U256Converter, BASE_FEE, DIFFICULTY}, + vm::evm::{BASE_FEE, DIFFICULTY}, Code, Config, }; -use alloy_core::{primitives::U256, sol_types::SolInterface}; +use alloy_core::sol_types::SolInterface; use frame_support::traits::fungible::Mutate; use pallet_revive_fixtures::{compile_module_with_type, BlockInfo, FixtureType}; use pretty_assertions::assert_eq; -use sp_core::H160; +use sp_core::{H160, U256}; /// Tests that the blocknumber opcode works as expected. #[test] @@ -50,7 +50,7 @@ fn block_number_works() { .build_and_unwrap_result(); assert_eq!( U256::from(42u32), - U256::from_be_bytes::<32>(result.data.try_into().unwrap()) + U256::from_big_endian(&result.data) ); }); } @@ -93,7 +93,7 @@ fn chainid_works() { .build_and_unwrap_result(); assert_eq!( U256::from(::ChainId::get()), - U256::from_be_bytes::<32>(result.data.try_into().unwrap()) + U256::from_big_endian(&result.data) ); }); } @@ -119,7 +119,7 @@ fn timestamp_works() { // Solidity expects timestamps in seconds, whereas pallet_timestamp uses // milliseconds. U256::from(Timestamp::get() / 1000), - U256::from_be_bytes::<32>(result.data.try_into().unwrap()) + U256::from_big_endian(&result.data) ); }); } @@ -142,7 +142,7 @@ fn gaslimit_works() { U256::from( ::BlockWeights::get().max_block.ref_time() ), - U256::from_be_bytes::<32>(result.data.try_into().unwrap()) + U256::from_big_endian(&result.data) ); }); } @@ -161,10 +161,7 @@ fn basefee_works() { let result = builder::bare_call(addr) .data(BlockInfo::BlockInfoCalls::basefee(BlockInfo::basefeeCall {}).abi_encode()) .build_and_unwrap_result(); - assert_eq!( - BASE_FEE.into_revm_u256(), - U256::from_be_bytes::<32>(result.data.try_into().unwrap()) - ); + assert_eq!(BASE_FEE, U256::from_big_endian(&result.data)); }); } } @@ -188,7 +185,7 @@ fn difficulty_works() { assert_eq!( // Alligned with the value set for PVM U256::from(DIFFICULTY), - U256::from_be_bytes::<32>(result.data.try_into().unwrap()) + U256::from_big_endian(&result.data) ); }); } diff --git a/substrate/frame/revive/src/tests/sol/contract.rs b/substrate/frame/revive/src/tests/sol/contract.rs index bfcfbcfb93b33..448a43d300422 100644 --- a/substrate/frame/revive/src/tests/sol/contract.rs +++ b/substrate/frame/revive/src/tests/sol/contract.rs @@ -70,7 +70,7 @@ fn staticcall_works() { assert!(result.success, "the call must succeed"); assert_eq!( magic_number, - U256::from_be_bytes::<32>(result.output.as_ref().try_into().unwrap()), + U256::from_be_slice(result.output.as_ref()), "the call must reproduce the magic number" ); @@ -130,7 +130,7 @@ fn call_works() { assert!(result.success, "the call must succeed"); assert_eq!( magic_number, - U256::from_be_bytes::<32>(result.output.as_ref().try_into().unwrap()), + U256::from_be_slice(result.output.as_ref()), "the call must reproduce the magic number" ); @@ -348,7 +348,7 @@ fn delegatecall_works() { assert!(result.success, "the call must succeed"); assert_eq!( magic_number, - U256::from_be_bytes::<32>(result.output.as_ref().try_into().unwrap()), + U256::from_be_slice(result.output.as_ref()), "the call must reproduce the magic number" ); diff --git a/substrate/frame/revive/src/tests/sol/control.rs b/substrate/frame/revive/src/tests/sol/control.rs index 0fc0955c890bb..96b577b01732b 100644 --- a/substrate/frame/revive/src/tests/sol/control.rs +++ b/substrate/frame/revive/src/tests/sol/control.rs @@ -18,13 +18,11 @@ use crate::{ test_utils::{builder::Contract, ALICE}, tests::{builder, sol::make_initcode_from_runtime_code, ExtBuilder, Test}, - Code, Config, + Code, Config, U256, }; -use alloy_core::primitives::U256; use frame_support::traits::fungible::Mutate; use pallet_revive_uapi::ReturnFlags; use pretty_assertions::assert_eq; - use revm::bytecode::opcode::*; #[test] @@ -63,7 +61,7 @@ fn jump_works() { assert!(!result.did_revert(), "test reverted"); assert_eq!( - U256::from_be_bytes::<32>(result.data.try_into().unwrap()), + U256::from_big_endian(&result.data), U256::from(expected_value), "memory test should return {expected_value}" ); @@ -146,12 +144,12 @@ fn jumpi_works() { { // JUMPI was *not* triggered, contract returns 0xfefefefe - let argument = U256::from(expected_value).to_be_bytes::<32>().to_vec(); + let argument = U256::from(expected_value).to_big_endian().to_vec(); let result = builder::bare_call(addr).data(argument).build_and_unwrap_result(); assert!(!result.did_revert(), "test reverted"); assert_eq!( - U256::from_be_bytes::<32>(result.data.try_into().unwrap()), + U256::from_big_endian(&result.data), U256::from(expected_value), "memory test should return {expected_value}" ); @@ -159,13 +157,13 @@ fn jumpi_works() { { // JUMPI was triggered, contract returns 0xdeadbeef - let argument = U256::from(unexpected_value).to_be_bytes::<32>().to_vec(); + let argument = U256::from(unexpected_value).to_big_endian().to_vec(); let result = builder::bare_call(addr).data(argument).build_and_unwrap_result(); assert!(!result.did_revert(), "test reverted"); assert_eq!( - U256::from_be_bytes::<32>(result.data.try_into().unwrap()), + U256::from_big_endian(&result.data), U256::from(0xdeadbeef_u64), "memory test should return 0xdeadbeef" ); @@ -198,7 +196,7 @@ fn ret_works() { assert!(!result.did_revert(), "test reverted"); assert_eq!( - U256::from_be_bytes::<32>(result.data.try_into().unwrap()), + U256::from_big_endian(&result.data), U256::from(expected_value), "memory test should return {expected_value}" ); @@ -230,7 +228,7 @@ fn revert_works() { assert!(result.flags == ReturnFlags::REVERT, "test did not revert"); assert_eq!( - U256::from_be_bytes::<32>(result.data.try_into().unwrap()), + U256::from_big_endian(&result.data), U256::from(expected_value), "memory test should return {expected_value}" ); diff --git a/substrate/frame/revive/src/tests/sol/host.rs b/substrate/frame/revive/src/tests/sol/host.rs index 7ee487dda90df..af6c02a1da310 100644 --- a/substrate/frame/revive/src/tests/sol/host.rs +++ b/substrate/frame/revive/src/tests/sol/host.rs @@ -21,13 +21,11 @@ use crate::{ address::AddressMapper, test_utils::{builder::Contract, ALICE, BOB, BOB_ADDR}, tests::{builder, test_utils, ExtBuilder, RuntimeEvent, Test}, - Code, Config, Key, System, H256, + Code, Config, Key, System, H256, U256, }; +use alloy_core::primitives; -use alloy_core::{ - primitives::U256, - sol_types::{SolCall, SolInterface}, -}; +use alloy_core::sol_types::{SolCall, SolInterface}; use frame_support::traits::{fungible::Mutate, Get}; use pallet_revive_fixtures::{compile_module_with_type, FixtureType, Host}; use pretty_assertions::assert_eq; @@ -61,7 +59,7 @@ fn balance_works() { ) .build_and_unwrap_result(); assert!(!result.did_revert(), "test reverted"); - let result = U256::from_be_bytes::<32>(result.data.try_into().unwrap()); + let result = U256::from_big_endian(&result.data); assert_eq!( expected_balance, result, @@ -95,7 +93,7 @@ fn selfbalance_works() { .data(Host::HostCalls::selfbalance(Host::selfbalanceCall {}).abi_encode()) .build_and_unwrap_result(); assert!(!result.did_revert(), "test reverted"); - let result_balance = U256::from_be_bytes::<32>(result.data.try_into().unwrap()); + let result_balance = U256::from_big_endian(&result.data); assert_eq!( expected_balance, result_balance, @@ -135,7 +133,7 @@ fn extcodesize_works() { .build_and_unwrap_result(); assert!(!result.did_revert(), "test reverted"); - let result_size = U256::from_be_bytes::<32>(result.data.try_into().unwrap()); + let result_size = U256::from_big_endian(&result.data); assert_eq!( expected_code_size, result_size, @@ -174,8 +172,8 @@ fn extcodehash_works() { .build_and_unwrap_result(); assert!(!result.did_revert(), "test reverted"); - let result_hash = U256::from_be_bytes::<32>(result.data.try_into().unwrap()); - let result_hash = H256::from(result_hash.to_be_bytes()); + let result_hash = U256::from_big_endian(&result.data); + let result_hash = H256::from(result_hash.to_big_endian()); assert_eq!( expected_code_hash, result_hash, @@ -257,8 +255,8 @@ fn extcodecopy_works() { .data( HostEvmOnlyCalls::extcodecopyOp(HostEvmOnly::extcodecopyOpCall { account: dummy_addr.0.into(), - offset: U256::from(test_case.offset), - size: U256::from(test_case.size), + offset: primitives::U256::from(test_case.offset), + size: primitives::U256::from(test_case.size), }) .abi_encode(), ) @@ -303,15 +301,15 @@ fn blockhash_works() { let result = builder::bare_call(addr) .data( Host::HostCalls::blockhashOp(Host::blockhashOpCall { - blockNumber: U256::from(block_number_to_test), + blockNumber: primitives::U256::from(block_number_to_test), }) .abi_encode(), ) .build_and_unwrap_result(); assert!(!result.did_revert(), "test reverted"); - let result_hash = U256::from_be_bytes::<32>(result.data.try_into().unwrap()); - let result_hash = H256::from(result_hash.to_be_bytes()); + let result_hash = U256::from_big_endian(&result.data); + let result_hash = H256::from(result_hash.to_big_endian()); let expected_block_hash = System::::block_hash(block_number_to_test); @@ -341,18 +339,18 @@ fn sload_works() { { let contract_info = test_utils::get_contract(&addr); - let key = Key::Fix(index.to_be_bytes()); + let key = Key::Fix(index.to_big_endian()); contract_info - .write(&key, Some(expected_value.to_be_bytes::<32>().to_vec()), None, false) + .write(&key, Some(expected_value.to_big_endian().to_vec()), None, false) .unwrap(); } { let result = builder::bare_call(addr) - .data(Host::HostCalls::sloadOp(Host::sloadOpCall { slot: index }).abi_encode()) + .data(Host::HostCalls::sloadOp(Host::sloadOpCall { slot: primitives::U256::from_be_bytes(index.to_big_endian()) }).abi_encode()) .build_and_unwrap_result(); assert!(!result.did_revert(), "test reverted"); - let result = U256::from_be_bytes::<32>(result.data.try_into().unwrap()); + let result = U256::from_big_endian(&result.data); assert_eq!( expected_value, result, @@ -381,9 +379,9 @@ fn sstore_works() { { let contract_info = test_utils::get_contract(&addr); - let key = Key::Fix(index.to_be_bytes()); + let key = Key::Fix(index.to_big_endian()); contract_info - .write(&key, Some(unexpected_value.to_be_bytes::<32>().to_vec()), None, false) + .write(&key, Some(unexpected_value.to_big_endian().to_vec()), None, false) .unwrap(); } @@ -391,8 +389,8 @@ fn sstore_works() { let result = builder::bare_call(addr) .data( Host::HostCalls::sstoreOp(Host::sstoreOpCall { - slot: index, - value: expected_value, + slot: primitives::U256::from_be_bytes(index.to_big_endian()), + value: primitives::U256::from_be_bytes(expected_value.to_big_endian()), }) .abi_encode(), ) @@ -401,9 +399,9 @@ fn sstore_works() { let written_value = { let contract_info = test_utils::get_contract(&addr); - let key = Key::Fix(index.to_be_bytes()); + let key = Key::Fix(index.to_big_endian()); let result = contract_info.read(&key).unwrap(); - U256::from_be_bytes::<32>(result.try_into().unwrap()) + U256::from_big_endian(&result) }; assert_eq!( expected_value, written_value, @@ -506,9 +504,9 @@ fn transient_storage_works() { let (code, _) = compile_module_with_type("HostTransientMemory", fixture_type).unwrap(); ExtBuilder::default().build().execute_with(|| { - let slot = U256::from(0); + let slot = primitives::U256::from(0); - let value = U256::from(13); + let value = primitives::U256::from(13); ::Currency::set_balance(&ALICE, 100_000_000_000); @@ -525,7 +523,7 @@ fn transient_storage_works() { .build_and_unwrap_result(); assert!(!result.did_revert(), "test reverted"); assert_eq!( - U256::from_be_bytes::<32>(result.data.try_into().unwrap()), + U256::from_big_endian(&result.data), U256::from(0), "transient storage should return zero for {:?}", fixture_type @@ -558,7 +556,7 @@ fn logs_denied_for_static_call() { Caller::CallerCalls::staticCall(Caller::staticCallCall { _callee: host_addr.0.into(), _data: Host::HostCalls::logOps(Host::logOpsCall {}).abi_encode().into(), - _gas: U256::MAX, + _gas: primitives::U256::MAX, }) .abi_encode(), ) diff --git a/substrate/frame/revive/src/tests/sol/memory.rs b/substrate/frame/revive/src/tests/sol/memory.rs index bb6255864c6a0..1633a125cc785 100644 --- a/substrate/frame/revive/src/tests/sol/memory.rs +++ b/substrate/frame/revive/src/tests/sol/memory.rs @@ -19,13 +19,10 @@ use crate::{ evm::decode_revert_reason, test_utils::{builder::Contract, ALICE}, tests::{builder, ExtBuilder, Test}, - Code, Config, Error, ExecReturnValue, LOG_TARGET, -}; - -use alloy_core::{ - primitives::U256, - sol_types::{SolCall, SolInterface}, + Code, Config, Error, ExecReturnValue, LOG_TARGET, U256, }; +use alloy_core::primitives; +use alloy_core::sol_types::{SolCall, SolInterface}; use frame_support::traits::fungible::Mutate; use pallet_revive_fixtures::{compile_module_with_type, FixtureType, Memory}; use pallet_revive_uapi::ReturnFlags; @@ -44,14 +41,14 @@ fn memory_limit_works() { ( // Writing 1 byte from 0 to the limit - 1 should work. Memory::expandMemoryCall { - memorySize: U256::from(crate::limits::code::BASELINE_MEMORY_LIMIT - 1), + memorySize: primitives::U256::from(crate::limits::code::BASELINE_MEMORY_LIMIT - 1), }, Ok(ExecReturnValue { data: vec![0u8; 32], flags: ReturnFlags::empty() }), ), ( // Writing 1 byte from the limit should revert. Memory::expandMemoryCall { - memorySize: U256::from(crate::limits::code::BASELINE_MEMORY_LIMIT), + memorySize: primitives::U256::from(crate::limits::code::BASELINE_MEMORY_LIMIT), }, Err(>::OutOfGas.into()), ), @@ -104,14 +101,14 @@ fn msize_works() { let result = builder::bare_call(addr) .data( Memory::MemoryCalls::testMsize(Memory::testMsizeCall { - offset: U256::from(512), + offset: primitives::U256::from(512), }) .abi_encode(), ) .build_and_unwrap_result(); assert!(!result.did_revert(), "test reverted"); assert_eq!( - U256::from_be_bytes::<32>(result.data.try_into().unwrap()), + U256::from_big_endian(&result.data), U256::from(offset + 32), "memory test should return {}", offset + 32 @@ -135,17 +132,17 @@ fn mcopy_works() { let result = builder::bare_call(addr) .data( Memory::MemoryCalls::testMcopy(Memory::testMcopyCall { - dstOffset: U256::from(512), - offset: U256::from(0), - size: U256::from(32), - value: expected_value, + dstOffset: primitives::U256::from(512), + offset: primitives::U256::from(0), + size: primitives::U256::from(32), + value: primitives::U256::from_be_bytes(expected_value.to_big_endian()), }) .abi_encode(), ) .build_and_unwrap_result(); assert!(!result.did_revert(), "test reverted"); assert_eq!( - U256::from_be_bytes::<32>(result.data.try_into().unwrap()), + U256::from_big_endian(&result.data), expected_value, "memory test should return {expected_value}" ); diff --git a/substrate/frame/revive/src/tests/sol/stack.rs b/substrate/frame/revive/src/tests/sol/stack.rs index cd692bc78aa64..2bcdc731f6bb5 100644 --- a/substrate/frame/revive/src/tests/sol/stack.rs +++ b/substrate/frame/revive/src/tests/sol/stack.rs @@ -18,12 +18,10 @@ use crate::{ test_utils::{builder::Contract, ALICE}, tests::{builder, sol::make_initcode_from_runtime_code, ExtBuilder, Test}, - Code, Config, + Code, Config, U256, }; -use alloy_core::primitives::U256; use frame_support::traits::fungible::Mutate; use pretty_assertions::assert_eq; - use revm::bytecode::opcode::*; #[test] @@ -51,7 +49,7 @@ fn push_works() { assert!(!result.did_revert(), "test reverted"); assert_eq!( - U256::from_be_bytes::<32>(result.data.try_into().unwrap()), + U256::from_big_endian(&result.data), U256::from(expected_value), "memory test should return {expected_value}" ); @@ -85,7 +83,7 @@ fn pop_works() { assert!(!result.did_revert(), "test reverted"); assert_eq!( - U256::from_be_bytes::<32>(result.data.try_into().unwrap()), + U256::from_big_endian(&result.data), U256::from(expected_value), "memory test should return {expected_value}" ); @@ -119,7 +117,7 @@ fn dup_works() { assert!(!result.did_revert(), "test reverted"); assert_eq!( - U256::from_be_bytes::<32>(result.data.try_into().unwrap()), + U256::from_big_endian(&result.data), U256::from(expected_value), "memory test should return {expected_value}" ); @@ -153,7 +151,7 @@ fn swap_works() { assert!(!result.did_revert(), "test reverted"); assert_eq!( - U256::from_be_bytes::<32>(result.data.try_into().unwrap()), + U256::from_big_endian(&result.data), U256::from(expected_value), "memory test should return {expected_value}" ); diff --git a/substrate/frame/revive/src/tests/sol/system.rs b/substrate/frame/revive/src/tests/sol/system.rs index 1bb28b265b6fb..2cea3a6a1c3d9 100644 --- a/substrate/frame/revive/src/tests/sol/system.rs +++ b/substrate/frame/revive/src/tests/sol/system.rs @@ -20,9 +20,10 @@ use crate::{ test_utils::{builder::Contract, ALICE, ALICE_ADDR}, tests::{builder, Contracts, ExtBuilder, Test}, - Code, Config, + Code, Config, U256, }; -use alloy_core::{primitives::U256, sol_types::SolCall}; +use alloy_core::primitives; +use alloy_core::sol_types::SolCall; use frame_support::traits::fungible::Mutate; use pallet_revive_fixtures::{ compile_module_with_type, Callee, FixtureType, System as SystemFixture, @@ -107,7 +108,7 @@ fn callvalue_works() { .data(SystemFixture::callvalueCall {}.abi_encode()) .build_and_unwrap_result(); - let returned_val = U256::from_be_bytes::<32>(result.data.try_into().unwrap()); + let returned_val = U256::from_big_endian(&result.data); assert_eq!(U256::from(value), returned_val); }); } @@ -124,13 +125,13 @@ fn calldataload_works() { let result = builder::bare_call(addr) .data( - SystemFixture::calldataloadCall { offset: U256::from(4u32) } /* skip selector */ + SystemFixture::calldataloadCall { offset: primitives::U256::from(4u32) } /* skip selector */ .abi_encode(), ) .build_and_unwrap_result(); // Call calldataload(offset=4) → returns the argument "4" - let returned = U256::from_be_bytes::<32>(result.data.as_slice().try_into().unwrap()); + let returned = U256::from_big_endian(result.data.as_slice().try_into().unwrap()); assert_eq!(U256::from(4u32), returned); }); } @@ -151,7 +152,7 @@ fn calldatasize_works() { .build_and_unwrap_result(); // ABI encodes: 4 (selector) + 0 (no args) = 4 - let returned = U256::from_be_bytes::<32>(result.data.as_slice().try_into().unwrap()); + let returned = U256::from_big_endian(result.data.as_slice().try_into().unwrap()); assert_eq!(returned, U256::from(4u32)); }); } @@ -167,9 +168,9 @@ fn calldatacopy_works() { builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); let call_data = SystemFixture::calldatacopyCall { - destOffset: U256::from(0u32), // unused - offset: U256::from(4u32), // skip selector - size: U256::from(64u32), // copy destOffset + offset + destOffset: primitives::U256::from(0u32), // unused + offset: primitives::U256::from(4u32), // skip selector + size: primitives::U256::from(64u32), // copy destOffset + offset } .abi_encode(); @@ -204,8 +205,7 @@ fn codesize_works() { // Now fetch the actual *runtime* code size from storage let code = Contracts::code(&addr); - let returned_size = - U256::from_be_bytes::<32>(result.data.as_slice().try_into().unwrap()); + let returned_size = U256::from_big_endian(result.data.as_slice().try_into().unwrap()); let expected_size = U256::from(code.len()); assert_eq!(expected_size, returned_size); @@ -234,14 +234,14 @@ fn returndatasize_works() { .data( SystemFixture::returndatasizeCall { _callee: callee_addr.0.into(), - _data: Callee::echoCall { _data: magic_number }.abi_encode().into(), - _gas: U256::MAX, + _data: Callee::echoCall { _data: primitives::U256::from_be_bytes(magic_number.to_big_endian()) }.abi_encode().into(), + _gas: primitives::U256::MAX, } .abi_encode(), ) .build_and_unwrap_result(); - let size = U256::from_be_bytes::<32>(result.data.try_into().unwrap()); + let size = U256::from_big_endian(&result.data); // Always 32 bytes for a single uint256 assert_eq!(U256::from(32), size); }); @@ -268,11 +268,11 @@ fn returndatacopy_works() { .data( SystemFixture::returndatacopyCall { _callee: callee_addr.0.into(), - _data: Callee::echoCall { _data: magic_number }.abi_encode().into(), - _gas: U256::MAX, - destOffset: U256::ZERO, - offset: U256::ZERO, - size: U256::from(32u32), + _data: Callee::echoCall { _data: primitives::U256::from_be_bytes(magic_number.to_big_endian()) }.abi_encode().into(), + _gas: primitives::U256::MAX, + destOffset: primitives::U256::ZERO, + offset: primitives::U256::ZERO, + size: primitives::U256::from(32u32), } .abi_encode(), ) @@ -280,7 +280,7 @@ fn returndatacopy_works() { let result = SystemFixture::returndatacopyCall::abi_decode_returns(&result.data).unwrap(); - assert_eq!(magic_number, U256::from_be_bytes::<32>(result.as_ref().try_into().unwrap())) + assert_eq!(magic_number, U256::from_big_endian(result.as_ref().try_into().unwrap())) }); } } diff --git a/substrate/frame/revive/src/tests/sol/tx_info.rs b/substrate/frame/revive/src/tests/sol/tx_info.rs index d1e38c26ac837..8d60329042685 100644 --- a/substrate/frame/revive/src/tests/sol/tx_info.rs +++ b/substrate/frame/revive/src/tests/sol/tx_info.rs @@ -21,10 +21,10 @@ use crate::{ evm::runtime::GAS_PRICE, test_utils::{builder::Contract, ALICE, ALICE_ADDR}, tests::{builder, ExtBuilder, Test}, - Code, Config, + Code, Config, U256, }; -use alloy_core::{primitives::U256, sol_types::SolInterface}; +use alloy_core::sol_types::SolInterface; use frame_support::traits::fungible::Mutate; use pallet_revive_fixtures::{compile_module_with_type, FixtureType, TransactionInfo}; use pretty_assertions::assert_eq; @@ -50,7 +50,7 @@ fn gasprice_works() { .build_and_unwrap_result(); assert_eq!( U256::from(GAS_PRICE), - U256::from_be_bytes::<32>(result.data.try_into().unwrap()) + U256::from_big_endian(&result.data) ); }); } diff --git a/substrate/frame/revive/todos.md b/substrate/frame/revive/todos.md new file mode 100644 index 0000000000000..a3c7500f8e07b --- /dev/null +++ b/substrate/frame/revive/todos.md @@ -0,0 +1,20 @@ +# TODOs and Commented Code Added in PR + +## Summary +Found 4 new TODO comments and 1 commented code line added in the `pg/revm-refactor` PR. + +## Newly Added TODO Comments + +1. **Gas limit setting**: `Weight::from_parts(u64::MAX, u64::MAX), // TODO: set the right limit` +2. **Gas limit control flow**: `ControlFlow::Continue(u64::MAX) // TODO: Set the right gas limit` +3. **Input data type**: `pub input: Vec, // TODO maybe just &'a[u8]` +4. **Memory slice method**: `/// TODO same as slice_mut?` + +## Newly Added Commented Code + +1. **Disabled Solidity tests**: `// mod sol;` - commented out module import + +## Notes +- Most TODOs are related to gas handling optimization +- The commented `mod sol;` appears to intentionally disable Solidity test module +- No critical issues found, mainly optimization reminders \ No newline at end of file From dc62dd2f0d55bc33f9cbbd25d97e36a521cacf58 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Tue, 23 Sep 2025 14:02:52 +0200 Subject: [PATCH 32/76] wip --- substrate/frame/revive/src/tests/sol.rs | 33 ++++---- .../frame/revive/src/tests/sol/block_info.rs | 5 +- substrate/frame/revive/src/tests/sol/host.rs | 7 +- .../frame/revive/src/tests/sol/memory.rs | 10 ++- .../frame/revive/src/tests/sol/system.rs | 15 +++- .../frame/revive/src/tests/sol/tx_info.rs | 5 +- .../vm/evm/instructions/arithmetic/i256.rs | 28 +++---- .../revive/src/vm/evm/instructions/macros.rs | 2 +- substrate/frame/revive/src/vm/evm/util.rs | 5 +- substrate/frame/revive/todos.md | 81 ++++++++++++++++++- 10 files changed, 133 insertions(+), 58 deletions(-) diff --git a/substrate/frame/revive/src/tests/sol.rs b/substrate/frame/revive/src/tests/sol.rs index 64e64e6455f16..590adc0e22a48 100644 --- a/substrate/frame/revive/src/tests/sol.rs +++ b/substrate/frame/revive/src/tests/sol.rs @@ -23,10 +23,9 @@ use crate::{ test_utils::{contract_base_deposit, ensure_stored, get_contract}, ExtBuilder, Test, }, - Code, Config, PristineCode, + Code, Config, PristineCode, U256, }; use alloy_core::{primitives, sol_types::SolInterface}; -use crate::U256; use frame_support::traits::fungible::Mutate; use pallet_revive_fixtures::{compile_module_with_type, Fibonacci, FixtureType}; use pretty_assertions::assert_eq; @@ -89,14 +88,13 @@ fn basic_evm_flow_works() { let result = builder::bare_call(addr) .data( - Fibonacci::FibonacciCalls::fib(Fibonacci::fibCall { n: primitives::U256::from(10u64) }) - .abi_encode(), + Fibonacci::FibonacciCalls::fib(Fibonacci::fibCall { + n: primitives::U256::from(10u64), + }) + .abi_encode(), ) .build_and_unwrap_result(); - assert_eq!( - U256::from(55u32), - U256::from_big_endian(&result.data) - ); + assert_eq!(U256::from(55u32), U256::from_big_endian(&result.data)); } // init code is not stored @@ -141,16 +139,15 @@ fn basic_evm_flow_tracing_works() { let result = trace(&mut call_tracer, || { builder::bare_call(addr) .data( - Fibonacci::FibonacciCalls::fib(Fibonacci::fibCall { n: primitives::U256::from(10u64) }) - .abi_encode(), + Fibonacci::FibonacciCalls::fib(Fibonacci::fibCall { + n: primitives::U256::from(10u64), + }) + .abi_encode(), ) .build_and_unwrap_result() }); - assert_eq!( - crate::U256::from(55u32), - crate::U256::from_big_endian(&result.data) - ); + assert_eq!(crate::U256::from(55u32), crate::U256::from_big_endian(&result.data)); assert_eq!( call_tracer.collect_trace().unwrap(), @@ -158,9 +155,11 @@ fn basic_evm_flow_tracing_works() { call_type: CallType::Call, from: ALICE_ADDR, to: addr, - input: Fibonacci::FibonacciCalls::fib(Fibonacci::fibCall { n: primitives::U256::from(10u64) }) - .abi_encode() - .into(), + input: Fibonacci::FibonacciCalls::fib(Fibonacci::fibCall { + n: primitives::U256::from(10u64) + }) + .abi_encode() + .into(), output: result.data.into(), value: Some(crate::U256::zero()), ..Default::default() diff --git a/substrate/frame/revive/src/tests/sol/block_info.rs b/substrate/frame/revive/src/tests/sol/block_info.rs index a5bcdfd4f2f8f..7a435b4140acd 100644 --- a/substrate/frame/revive/src/tests/sol/block_info.rs +++ b/substrate/frame/revive/src/tests/sol/block_info.rs @@ -48,10 +48,7 @@ fn block_number_works() { .abi_encode(), ) .build_and_unwrap_result(); - assert_eq!( - U256::from(42u32), - U256::from_big_endian(&result.data) - ); + assert_eq!(U256::from(42u32), U256::from_big_endian(&result.data)); }); } } diff --git a/substrate/frame/revive/src/tests/sol/host.rs b/substrate/frame/revive/src/tests/sol/host.rs index af6c02a1da310..deb508edf79c3 100644 --- a/substrate/frame/revive/src/tests/sol/host.rs +++ b/substrate/frame/revive/src/tests/sol/host.rs @@ -347,7 +347,12 @@ fn sload_works() { { let result = builder::bare_call(addr) - .data(Host::HostCalls::sloadOp(Host::sloadOpCall { slot: primitives::U256::from_be_bytes(index.to_big_endian()) }).abi_encode()) + .data( + Host::HostCalls::sloadOp(Host::sloadOpCall { + slot: primitives::U256::from_be_bytes(index.to_big_endian()), + }) + .abi_encode(), + ) .build_and_unwrap_result(); assert!(!result.did_revert(), "test reverted"); let result = U256::from_big_endian(&result.data); diff --git a/substrate/frame/revive/src/tests/sol/memory.rs b/substrate/frame/revive/src/tests/sol/memory.rs index 1633a125cc785..895404ad5ed20 100644 --- a/substrate/frame/revive/src/tests/sol/memory.rs +++ b/substrate/frame/revive/src/tests/sol/memory.rs @@ -21,8 +21,10 @@ use crate::{ tests::{builder, ExtBuilder, Test}, Code, Config, Error, ExecReturnValue, LOG_TARGET, U256, }; -use alloy_core::primitives; -use alloy_core::sol_types::{SolCall, SolInterface}; +use alloy_core::{ + primitives, + sol_types::{SolCall, SolInterface}, +}; use frame_support::traits::fungible::Mutate; use pallet_revive_fixtures::{compile_module_with_type, FixtureType, Memory}; use pallet_revive_uapi::ReturnFlags; @@ -41,7 +43,9 @@ fn memory_limit_works() { ( // Writing 1 byte from 0 to the limit - 1 should work. Memory::expandMemoryCall { - memorySize: primitives::U256::from(crate::limits::code::BASELINE_MEMORY_LIMIT - 1), + memorySize: primitives::U256::from( + crate::limits::code::BASELINE_MEMORY_LIMIT - 1, + ), }, Ok(ExecReturnValue { data: vec![0u8; 32], flags: ReturnFlags::empty() }), ), diff --git a/substrate/frame/revive/src/tests/sol/system.rs b/substrate/frame/revive/src/tests/sol/system.rs index 2cea3a6a1c3d9..03450640efb5a 100644 --- a/substrate/frame/revive/src/tests/sol/system.rs +++ b/substrate/frame/revive/src/tests/sol/system.rs @@ -22,8 +22,7 @@ use crate::{ tests::{builder, Contracts, ExtBuilder, Test}, Code, Config, U256, }; -use alloy_core::primitives; -use alloy_core::sol_types::SolCall; +use alloy_core::{primitives, sol_types::SolCall}; use frame_support::traits::fungible::Mutate; use pallet_revive_fixtures::{ compile_module_with_type, Callee, FixtureType, System as SystemFixture, @@ -234,7 +233,11 @@ fn returndatasize_works() { .data( SystemFixture::returndatasizeCall { _callee: callee_addr.0.into(), - _data: Callee::echoCall { _data: primitives::U256::from_be_bytes(magic_number.to_big_endian()) }.abi_encode().into(), + _data: Callee::echoCall { + _data: primitives::U256::from_be_bytes(magic_number.to_big_endian()), + } + .abi_encode() + .into(), _gas: primitives::U256::MAX, } .abi_encode(), @@ -268,7 +271,11 @@ fn returndatacopy_works() { .data( SystemFixture::returndatacopyCall { _callee: callee_addr.0.into(), - _data: Callee::echoCall { _data: primitives::U256::from_be_bytes(magic_number.to_big_endian()) }.abi_encode().into(), + _data: Callee::echoCall { + _data: primitives::U256::from_be_bytes(magic_number.to_big_endian()), + } + .abi_encode() + .into(), _gas: primitives::U256::MAX, destOffset: primitives::U256::ZERO, offset: primitives::U256::ZERO, diff --git a/substrate/frame/revive/src/tests/sol/tx_info.rs b/substrate/frame/revive/src/tests/sol/tx_info.rs index 8d60329042685..97c4cf865b233 100644 --- a/substrate/frame/revive/src/tests/sol/tx_info.rs +++ b/substrate/frame/revive/src/tests/sol/tx_info.rs @@ -48,10 +48,7 @@ fn gasprice_works() { .abi_encode(), ) .build_and_unwrap_result(); - assert_eq!( - U256::from(GAS_PRICE), - U256::from_big_endian(&result.data) - ); + assert_eq!(U256::from(GAS_PRICE), U256::from_big_endian(&result.data)); }); } } diff --git a/substrate/frame/revive/src/vm/evm/instructions/arithmetic/i256.rs b/substrate/frame/revive/src/vm/evm/instructions/arithmetic/i256.rs index 226fdf0deec3a..a712c576e8044 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/arithmetic/i256.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/arithmetic/i256.rs @@ -34,20 +34,12 @@ pub enum Sign { #[cfg(test)] /// The maximum positive value for a 256-bit signed integer. -pub const MAX_POSITIVE_VALUE: U256 = U256([ - 0xffffffffffffffff, - 0xffffffffffffffff, - 0xffffffffffffffff, - 0x7fffffffffffffff, -]); +pub const MAX_POSITIVE_VALUE: U256 = + U256([0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x7fffffffffffffff]); /// The minimum negative value for a 256-bit signed integer. -pub const MIN_NEGATIVE_VALUE: U256 = U256([ - 0x0000000000000000, - 0x0000000000000000, - 0x0000000000000000, - 0x8000000000000000, -]); +pub const MIN_NEGATIVE_VALUE: U256 = + U256([0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x8000000000000000]); const FLIPH_BITMASK_U64: u64 = 0x7FFF_FFFF_FFFF_FFFF; @@ -76,12 +68,7 @@ pub fn i256_sign_compl(val: &mut U256) -> Sign { fn u256_remove_sign(val: &mut U256) { // Clear the sign bit by masking the highest bit let limbs = val.0; - *val = U256([ - limbs[0], - limbs[1], - limbs[2], - limbs[3] & FLIPH_BITMASK_U64, - ]); + *val = U256([limbs[0], limbs[1], limbs[2], limbs[3] & FLIPH_BITMASK_U64]); } /// Computes the two's complement of a U256 value in place. @@ -180,7 +167,10 @@ mod tests { assert_eq!(i256_div(MIN_NEGATIVE_VALUE, neg_one), MIN_NEGATIVE_VALUE); assert_eq!(i256_div(MIN_NEGATIVE_VALUE, U256::from(1)), MIN_NEGATIVE_VALUE); assert_eq!(i256_div(MAX_POSITIVE_VALUE, U256::from(1)), MAX_POSITIVE_VALUE); - assert_eq!(i256_div(MAX_POSITIVE_VALUE, neg_one), (!MAX_POSITIVE_VALUE).overflowing_add(U256::from(1)).0); + assert_eq!( + i256_div(MAX_POSITIVE_VALUE, neg_one), + (!MAX_POSITIVE_VALUE).overflowing_add(U256::from(1)).0 + ); assert_eq!(i256_div(U256::from(100), neg_one), U256::from(100).overflowing_neg().0); assert_eq!(i256_div(U256::from(100), U256::from(2)), U256::from(50)); } diff --git a/substrate/frame/revive/src/vm/evm/instructions/macros.rs b/substrate/frame/revive/src/vm/evm/instructions/macros.rs index 35d28d8a0a64a..8950d361e353a 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/macros.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/macros.rs @@ -38,4 +38,4 @@ macro_rules! as_usize_saturated { ($v:expr) => { usize::try_from(as_u64_saturated!($v)).unwrap_or(usize::MAX) }; -} \ No newline at end of file +} diff --git a/substrate/frame/revive/src/vm/evm/util.rs b/substrate/frame/revive/src/vm/evm/util.rs index d4a69a0baf56f..987546f96245d 100644 --- a/substrate/frame/revive/src/vm/evm/util.rs +++ b/substrate/frame/revive/src/vm/evm/util.rs @@ -1,7 +1,4 @@ -use crate::{ - vm::evm::interpreter::Halt, - U256, -}; +use crate::{vm::evm::interpreter::Halt, U256}; use core::ops::ControlFlow; /// Helper function to convert U256 to usize, checking for overflow diff --git a/substrate/frame/revive/todos.md b/substrate/frame/revive/todos.md index a3c7500f8e07b..290986984bbd6 100644 --- a/substrate/frame/revive/todos.md +++ b/substrate/frame/revive/todos.md @@ -17,4 +17,83 @@ Found 4 new TODO comments and 1 commented code line added in the `pg/revm-refact ## Notes - Most TODOs are related to gas handling optimization - The commented `mod sol;` appears to intentionally disable Solidity test module -- No critical issues found, mainly optimization reminders \ No newline at end of file +- No critical issues found, mainly optimization reminders + +--- + +# PR Review Notes + +## Overall Assessment +This is a substantial refactor that modernizes the revm integration. The changes look well-structured and improve type safety. + +## Positive Changes ✅ + +### 1. **Cleaner Type System** +- Good separation between `sp_core::U256` and `alloy_core::primitives::U256` +- Consistent use of `primitives::U256` alias instead of verbose `AlloyU256` +- Proper conversion methods (`from_big_endian` vs `from_be_slice`) + +### 2. **Simplified Interpreter Interface** +- The new `Interpreter::new()` constructor is much cleaner than the old struct initialization +- Removing the complex generic `Interpreter>` type is a good simplification + +### 3. **Better Error Handling** +- Using `ControlFlow` for gas charging is more idiomatic than throwing errors +- The `charge_evm` method combining gas and token charging is elegant + +## Areas for Improvement ⚠️ + +### 1. **Memory Management** +```rust +// In benchmarking.rs - line 2411 +let mut interpreter = Interpreter::new(Default::default(), Default::default(), &mut ext); +``` +**Concern**: Using `Default::default()` for bytecode and gas could mask initialization issues. Consider explicit initialization. + +### 2. **Test Coverage** +The test file changes look comprehensive, but consider adding error cases to ensure the new conversion methods handle edge cases properly. + +### 3. **Documentation** ❌ +The PR lacks documentation for the major API changes. Consider adding: +- Doc comments for the new `Interpreter` interface +- Migration guide for the type system changes +- Examples of proper `primitives::U256` vs `crate::U256` usage + +### 4. **Performance Considerations** +```rust +// Multiple instances of type conversions like: +primitives::U256::from_be_bytes(magic_number.to_big_endian()) +``` +**Question**: Have you benchmarked the performance impact of these additional conversions? The `to_big_endian()` -> `from_be_bytes()` roundtrip might be optimizable. + +## Specific Code Issues + +### 1. **Potential Bug in benchmarking.rs** +```rust +let result; +#[block] +{ + result = extcodecopy_fn(&mut interpreter); +} +assert!(result.is_continue()); +``` +**Issue**: The `result` variable is uninitialized before the block. This might not compile. + +### 2. **Inconsistent Error Handling** +```rust +// In gas.rs +.map_or_else(|| ControlFlow::Break(Halt::OutOfGas), ControlFlow::Continue)?; +``` +**Style**: The `.map_or_else()` followed by `?` is unusual. Consider using `ok_or()` or explicit matching. + +## Missing Tests +Consider adding tests for: +1. Gas limit edge cases with the new `ControlFlow` system +2. Type conversion boundary conditions +3. Memory allocation patterns with the new interpreter + +## Dependencies +The addition of `proptest = "1"` suggests property-based testing, but it's not visible in the diff. Make sure it's actually needed. + +## Overall Recommendation +**Approve with minor changes** - This is a solid refactor that improves the codebase. Address the documentation and potential initialization issues, then it should be ready to merge. \ No newline at end of file From ff2884b9a0df7884c64480f59e34f7c45fd51937 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Tue, 23 Sep 2025 15:46:54 +0200 Subject: [PATCH 33/76] wip --- .../revive/src/evm/api/debug_rpc_types.rs | 28 +++++++++++++------ .../revive/src/evm/tracing/opcode_tracing.rs | 2 +- substrate/frame/revive/src/tests/sol.rs | 2 +- substrate/frame/revive/src/vm/evm.rs | 8 ++---- 4 files changed, 25 insertions(+), 15 deletions(-) diff --git a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs index cb90c9cffd386..63028dc67c891 100644 --- a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs +++ b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs @@ -142,24 +142,36 @@ impl Default for PrestateTracerConfig { } } +fn zero_to_none<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let opt = Option::::deserialize(deserializer)?; + Ok(match opt { + Some(0) => None, + other => other, + }) +} + /// The configuration for the opcode tracer. #[derive(Clone, Debug, Decode, Serialize, Deserialize, Encode, PartialEq, TypeInfo)] #[serde(default, rename_all = "camelCase")] pub struct OpcodeTracerConfig { - /// Whether to enable memory capture (default: false) + /// Whether to enable memory capture pub enable_memory: bool, - /// Whether to disable stack capture (default: false) + /// Whether to disable stack capture pub disable_stack: bool, - /// Whether to disable storage capture (default: false) + /// Whether to disable storage capture pub disable_storage: bool, - /// Whether to enable return data capture (default: false) + /// Whether to enable return data capture pub enable_return_data: bool, - /// Limit number of steps captured (default: 0, no limit) - pub limit: u64, + /// Limit number of steps captured + #[serde(skip_serializing_if = "Option::is_none", deserialize_with = "zero_to_none")] + pub limit: Option, /// Maximum number of memory words to capture per step (default: 16) pub memory_word_limit: u32, @@ -172,7 +184,7 @@ impl Default for OpcodeTracerConfig { disable_stack: false, disable_storage: false, enable_return_data: false, - limit: 0, + limit: None, memory_word_limit: 16, } } @@ -204,7 +216,7 @@ fn test_tracer_config_serialization() { disable_stack: false, disable_storage: false, enable_return_data: true, - limit: 0, + limit: None, memory_word_limit: 16, })), timeout: None, diff --git a/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs b/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs index b0acc318392f3..33102cec9188d 100644 --- a/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs +++ b/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs @@ -129,7 +129,7 @@ impl U256> Tracing for OpcodeTracer 0 && self.step_count >= self.config.limit { + if self.config.limit.map(|l| self.step_count >= l).unwrap_or(false) { return; } diff --git a/substrate/frame/revive/src/tests/sol.rs b/substrate/frame/revive/src/tests/sol.rs index b4f9b29fa9f4d..062e475feeced 100644 --- a/substrate/frame/revive/src/tests/sol.rs +++ b/substrate/frame/revive/src/tests/sol.rs @@ -187,7 +187,7 @@ fn opcode_tracing_works() { disable_stack: false, disable_storage: true, enable_return_data: true, - limit: 5, + limit: Some(5), memory_word_limit: 16, }; diff --git a/substrate/frame/revive/src/vm/evm.rs b/substrate/frame/revive/src/vm/evm.rs index 4b46e213c4ebc..12a77d74717fe 100644 --- a/substrate/frame/revive/src/vm/evm.rs +++ b/substrate/frame/revive/src/vm/evm.rs @@ -155,11 +155,9 @@ fn run<'a, E: Ext>( table: &revm::interpreter::InstructionTable, DummyHost>, ) -> InterpreterResult { let host = &mut DummyHost {}; - + let use_opcode_tracing = + tracing::if_tracing(|tracer| tracer.is_opcode_tracing_enabled()).unwrap_or(false); loop { - let use_opcode_tracing = tracing::if_tracing(|tracer| tracer.is_opcode_tracing_enabled()) - .unwrap_or(false); - let action = if use_opcode_tracing { run_with_opcode_tracing(interpreter, table, host) } else { @@ -214,7 +212,7 @@ fn run_with_opcode_tracing<'a, E: Ext>( let context = InstructionContext { interpreter, host }; table[opcode as usize](context); let gas_left = interpreter.extend.gas_meter().gas_left(); - + tracing::if_tracing(|tracer| { tracer.exit_opcode(gas_left); }); From c7c3819fb88ea59e585cdae9939a954cdac08403 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Tue, 23 Sep 2025 20:31:54 +0200 Subject: [PATCH 34/76] wip --- substrate/frame/revive/src/tests/sol.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/substrate/frame/revive/src/tests/sol.rs b/substrate/frame/revive/src/tests/sol.rs index f90fffa8a201b..796a82666c61b 100644 --- a/substrate/frame/revive/src/tests/sol.rs +++ b/substrate/frame/revive/src/tests/sol.rs @@ -17,6 +17,7 @@ use crate::{ assert_refcount, + evm::OpcodeTrace, test_utils::{builder::Contract, ALICE}, tests::{ builder, @@ -29,7 +30,6 @@ use alloy_core::{primitives, sol_types::SolInterface}; use frame_support::traits::fungible::Mutate; use pallet_revive_fixtures::{compile_module_with_type, Fibonacci, FixtureType}; use pretty_assertions::assert_eq; - use revm::bytecode::opcode::*; mod arithmetic; @@ -171,10 +171,9 @@ fn basic_evm_flow_tracing_works() { #[test] fn opcode_tracing_works() { use crate::{ - evm::{OpcodeTrace, OpcodeTracer, OpcodeTracerConfig}, + evm::{OpcodeTracer, OpcodeTracerConfig}, tracing::trace, }; - use revm::bytecode::opcode::*; let (code, _) = compile_module_with_type("Fibonacci", FixtureType::Solc).unwrap(); ExtBuilder::default().existential_deposit(200).build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 100_000_000); @@ -187,7 +186,7 @@ fn opcode_tracing_works() { disable_stack: false, disable_storage: true, enable_return_data: true, - limit: Some(5), + limit: None, memory_word_limit: 16, }; From 809c7f7322c66c16d9c746072626035f46c53f66 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Tue, 23 Sep 2025 20:32:29 +0200 Subject: [PATCH 35/76] wip --- substrate/frame/revive/src/vm/evm.rs | 6 ++++-- substrate/frame/revive/src/vm/evm/instructions/control.rs | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/substrate/frame/revive/src/vm/evm.rs b/substrate/frame/revive/src/vm/evm.rs index 5350ca411f922..1c0a82acb733c 100644 --- a/substrate/frame/revive/src/vm/evm.rs +++ b/substrate/frame/revive/src/vm/evm.rs @@ -146,8 +146,8 @@ fn run_plain<'a, E: Ext>( use revm::interpreter::interpreter_types::Jumps; loop { let opcode = interpreter.bytecode.opcode(); - table[opcode as usize](interpreter)?; interpreter.bytecode.relative_jump(1); + table[opcode as usize](interpreter)?; } } @@ -174,11 +174,13 @@ fn run_plain_with_tracing<'a, E: Ext>( }); interpreter.bytecode.relative_jump(1); - table[opcode as usize](interpreter)?; + let res = table[opcode as usize](interpreter); tracing::if_tracing(|tracer| { let gas_left = interpreter.ext.gas_meter().gas_left(); tracer.exit_opcode(gas_left); }); + + res?; } } diff --git a/substrate/frame/revive/src/vm/evm/instructions/control.rs b/substrate/frame/revive/src/vm/evm/instructions/control.rs index 00630f759b054..3afa1e3581d91 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/control.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/control.rs @@ -62,7 +62,7 @@ fn jump_inner(interpreter: &mut Interpreter<'_, E>, target: U256) -> Con return ControlFlow::Break(Halt::InvalidJump); } // SAFETY: `is_valid_jump` ensures that `dest` is in bounds. - interpreter.bytecode.absolute_jump(target - 1); // pc will be increased by the interpreter loop + interpreter.bytecode.absolute_jump(target); ControlFlow::Continue(()) } From a507aa6e42875a343a03911abcf7071511364852 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Tue, 23 Sep 2025 20:42:49 +0200 Subject: [PATCH 36/76] wip --- substrate/frame/revive/src/vm/evm.rs | 36 ------------------- .../revive/src/vm/evm/instructions/control.rs | 2 +- substrate/frame/revive/src/vm/evm/stack.rs | 1 + 3 files changed, 2 insertions(+), 37 deletions(-) diff --git a/substrate/frame/revive/src/vm/evm.rs b/substrate/frame/revive/src/vm/evm.rs index 317e9f362e227..cfeddb24d204b 100644 --- a/substrate/frame/revive/src/vm/evm.rs +++ b/substrate/frame/revive/src/vm/evm.rs @@ -122,17 +122,10 @@ pub fn call<'a, E: Ext>(bytecode: Bytecode, ext: &'a mut E, input: Vec) -> E let mut interpreter = Interpreter::new(ExtBytecode::new(bytecode), input, ext); let table = instruction_table::(); - #[cfg(not(feature = "std"))] let ControlFlow::Break(halt) = run_plain(&mut interpreter, &table); - #[cfg(feature = "std")] - let ControlFlow::Break(halt) = run_plain_with_tracing(&mut interpreter, &table); - interpreter.into_exec_result(halt) } -/// Re-implementation of REVM run_plain function to add trace logging to our EVM interpreter loop. -/// NB: copied directly from revm tag v82 -#[cfg(not(feature = "std"))] fn run_plain<'a, E: Ext>( interpreter: &mut Interpreter, table: &InstructionTable, @@ -140,35 +133,6 @@ fn run_plain<'a, E: Ext>( use revm::interpreter::interpreter_types::Jumps; loop { let opcode = interpreter.bytecode.opcode(); - table[opcode as usize](interpreter)?; - interpreter.bytecode.relative_jump(1); - } -} - -/// Re-implementation of REVM run_plain function to add trace logging to our EVM interpreter loop. -/// NB: copied directly from revm tag v82 -#[cfg(feature = "std")] -fn run_plain_with_tracing<'a, E: Ext>( - interpreter: &mut Interpreter<'a, E>, - table: &InstructionTable, -) -> ControlFlow { - use crate::{alloc::string::ToString, format}; - use revm::{bytecode::OpCode, interpreter::interpreter_types::Jumps}; - loop { - let opcode = interpreter.bytecode.opcode(); - log::trace!(target: LOG_TARGET, - "[{pc}]: {opcode}, stacktop: {stacktop}, memory size: {memsize} {memory:?}", - pc = interpreter.bytecode.pc(), - opcode = OpCode::new(interpreter.bytecode.opcode()) - .map_or("INVALID".to_string(), |x| format!("{:?}", x.info())), - stacktop = interpreter.stack.top().map_or("None".to_string(), |x| format!("{:#x}", x)), - memsize = interpreter.memory.size(), - // printing at most the first 32 bytes of memory - memory = interpreter - .memory - .slice_len(0, core::cmp::min(32, interpreter.memory.size())) - .to_vec(), - ); interpreter.bytecode.relative_jump(1); table[opcode as usize](interpreter)?; } diff --git a/substrate/frame/revive/src/vm/evm/instructions/control.rs b/substrate/frame/revive/src/vm/evm/instructions/control.rs index 00630f759b054..3afa1e3581d91 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/control.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/control.rs @@ -62,7 +62,7 @@ fn jump_inner(interpreter: &mut Interpreter<'_, E>, target: U256) -> Con return ControlFlow::Break(Halt::InvalidJump); } // SAFETY: `is_valid_jump` ensures that `dest` is in bounds. - interpreter.bytecode.absolute_jump(target - 1); // pc will be increased by the interpreter loop + interpreter.bytecode.absolute_jump(target); ControlFlow::Continue(()) } diff --git a/substrate/frame/revive/src/vm/evm/stack.rs b/substrate/frame/revive/src/vm/evm/stack.rs index 5671ee3157470..fffaf0921208a 100644 --- a/substrate/frame/revive/src/vm/evm/stack.rs +++ b/substrate/frame/revive/src/vm/evm/stack.rs @@ -68,6 +68,7 @@ impl Stack { } /// Get a reference to the top stack item without removing it + #[cfg(test)] pub fn top(&self) -> Option<&U256> { self.0.last() } From 5f08ae8d1275be56b278757ea345dd7fbab00579 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Wed, 24 Sep 2025 15:54:23 +0200 Subject: [PATCH 37/76] fix tests --- Cargo.lock | 293 +++++++++++++++--- Cargo.toml | 4 +- substrate/frame/revive/Cargo.toml | 2 + substrate/frame/revive/src/benchmarking.rs | 3 +- .../revive/src/evm/tracing/opcode_tracing.rs | 6 + substrate/frame/revive/src/tests/sol.rs | 48 ++- .../frame/revive/src/tests/sol/bitwise.rs | 27 +- .../frame/revive/src/tests/sol/contract.rs | 7 +- .../frame/revive/src/tests/sol/memory.rs | 8 +- .../revive/src/tests/sol/revm_tracing.rs | 62 ++++ .../src/vm/evm/instructions/arithmetic.rs | 6 +- .../revive/src/vm/evm/instructions/control.rs | 4 +- .../frame/revive/src/vm/evm/interpreter.rs | 3 +- substrate/frame/revive/src/vm/evm/memory.rs | 3 + 14 files changed, 406 insertions(+), 70 deletions(-) create mode 100644 substrate/frame/revive/src/tests/sol/revm_tracing.rs diff --git a/Cargo.lock b/Cargo.lock index 9fd4d67dd7f4f..9da62b258c450 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -105,6 +105,46 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "alloy-consensus" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bf3c28aa7a5765042739f964e335408e434819b96fdda97f12eb1beb46dead0" +dependencies = [ + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "alloy-trie", + "alloy-tx-macros", + "auto_impl", + "c-kzg", + "derive_more 2.0.1", + "either", + "k256", + "once_cell", + "rand 0.8.5", + "secp256k1 0.30.0", + "serde", + "serde_json", + "serde_with", + "thiserror 2.0.12", +] + +[[package]] +name = "alloy-consensus-any" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfda7b14f1664b6c23d7f38bca2b73c460f2497cf93dd1589753890cb0da158" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "serde", +] + [[package]] name = "alloy-core" version = "1.1.2" @@ -173,9 +213,9 @@ dependencies = [ [[package]] name = "alloy-eips" -version = "1.0.28" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d9ebd288f87aa496179b83471f5b00bcd01def4048714234d3ddbf97a6b9dd2" +checksum = "72e57928382e5c7890ef90ded9f814d85a1c3db79ceb4a3c5079f1be4cadeeb4" dependencies = [ "alloy-eip2124", "alloy-eip2930", @@ -195,9 +235,9 @@ dependencies = [ [[package]] name = "alloy-json-abi" -version = "1.1.2" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ccaa79753d7bf15f06399ea76922afbfaf8d18bebed9e8fc452984b4a90dcc9" +checksum = "125a1c373261b252e53e04d6e92c37d881833afc1315fceab53fd46045695640" dependencies = [ "alloy-primitives", "alloy-sol-type-parser", @@ -205,6 +245,19 @@ dependencies = [ "serde_json", ] +[[package]] +name = "alloy-network-primitives" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67f3b37447082a47289f26e26c0686ac6407710fdd4e818043d9b6d37f2ab55c" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-serde", + "serde", +] + [[package]] name = "alloy-primitives" version = "1.3.1" @@ -254,11 +307,46 @@ dependencies = [ "syn 2.0.98", ] +[[package]] +name = "alloy-rpc-types-eth" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd7c1bc07b6c9222c4ad822da3cea0fbbfcbe2876cf5d4780e147a0da6fe2862" +dependencies = [ + "alloy-consensus", + "alloy-consensus-any", + "alloy-eips", + "alloy-network-primitives", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "alloy-sol-types", + "itertools 0.14.0", + "serde", + "serde_json", + "serde_with", + "thiserror 2.0.12", +] + +[[package]] +name = "alloy-rpc-types-trace" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e54b3f616d9f30e11bc73e685f71da6f1682da5a3c2ca5206ec47f1d3bc96c7" +dependencies = [ + "alloy-primitives", + "alloy-rpc-types-eth", + "alloy-serde", + "serde", + "serde_json", + "thiserror 2.0.12", +] + [[package]] name = "alloy-serde" -version = "1.0.28" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743e0fe099fe90a07215ae08413b271b8fac12fca6880c85326ce61341566e6f" +checksum = "8603b89af4ba0acb94465319e506b8c0b40a5daf563046bedd58d26c98dbd62c" dependencies = [ "alloy-primitives", "serde", @@ -267,9 +355,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro" -version = "1.1.2" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8612e0658964d616344f199ab251a49d48113992d81b92dab93ed855faa66383" +checksum = "d20d867dcf42019d4779519a1ceb55eba8d7f3d0e4f0a89bcba82b8f9eb01e48" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", @@ -281,9 +369,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro-expander" -version = "1.1.2" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a384edac7283bc4c010a355fb648082860c04b826bb7a814c45263c8f304c74" +checksum = "b74e91b0b553c115d14bd0ed41898309356dc85d0e3d4b9014c4e7715e48c8ad" dependencies = [ "alloy-sol-macro-input", "const-hex", @@ -299,9 +387,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro-input" -version = "1.1.2" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd588c2d516da7deb421b8c166dc60b7ae31bca5beea29ab6621fcfa53d6ca5" +checksum = "84194d31220803f5f62d0a00f583fd3a062b36382e2bea446f1af96727754565" dependencies = [ "const-hex", "dunce", @@ -315,9 +403,9 @@ dependencies = [ [[package]] name = "alloy-sol-type-parser" -version = "1.1.2" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e86ddeb70792c7ceaad23e57d52250107ebbb86733e52f4a25d8dc1abc931837" +checksum = "fe8c27b3cf6b2bb8361904732f955bc7c05e00be5f469cec7e2280b6167f3ff0" dependencies = [ "serde", "winnow 0.7.10", @@ -325,9 +413,9 @@ dependencies = [ [[package]] name = "alloy-sol-types" -version = "1.1.2" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "584cb97bfc5746cb9dcc4def77da11694b5d6d7339be91b7480a6a68dc129387" +checksum = "f5383d34ea00079e6dd89c652bcbdb764db160cef84e6250926961a0b2295d04" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -335,6 +423,35 @@ dependencies = [ "serde", ] +[[package]] +name = "alloy-trie" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3412d52bb97c6c6cc27ccc28d4e6e8cf605469101193b50b0bd5813b1f990b5" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "arrayvec 0.7.6", + "derive_more 2.0.1", + "nybbles", + "serde", + "smallvec", + "tracing", +] + +[[package]] +name = "alloy-tx-macros" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2765badc6f621e1fc26aa70c520315866f0db6b8bd6bf3c560920d4fb33b08de" +dependencies = [ + "alloy-primitives", + "darling 0.21.3", + "proc-macro2 1.0.95", + "quote 1.0.40", + "syn 2.0.98", +] + [[package]] name = "always-assert" version = "0.1.3" @@ -1031,6 +1148,9 @@ name = "arrayvec" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +dependencies = [ + "serde", +] [[package]] name = "asn1-rs" @@ -5350,8 +5470,18 @@ version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.20.10", + "darling_macro 0.20.10", +] + +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core 0.21.3", + "darling_macro 0.21.3", ] [[package]] @@ -5368,13 +5498,39 @@ dependencies = [ "syn 2.0.98", ] +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2 1.0.95", + "quote 1.0.40", + "serde", + "strsim", + "syn 2.0.98", +] + [[package]] name = "darling_macro" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ - "darling_core", + "darling_core 0.20.10", + "quote 1.0.40", + "syn 2.0.98", +] + +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core 0.21.3", "quote 1.0.40", "syn 2.0.98", ] @@ -11002,6 +11158,20 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" +[[package]] +name = "nybbles" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa11e84403164a9f12982ab728f3c67c6fd4ab5b5f0254ffc217bdbd3b28ab0" +dependencies = [ + "alloy-rlp", + "cfg-if", + "proptest", + "ruint", + "serde", + "smallvec", +] + [[package]] name = "object" version = "0.32.2" @@ -13019,6 +13189,7 @@ name = "pallet-revive" version = "0.1.0" dependencies = [ "alloy-core", + "alloy-rpc-types-trace", "array-bytes 6.2.2", "assert_matches", "derive_more 0.99.17", @@ -13052,6 +13223,7 @@ dependencies = [ "rand 0.8.5", "rand_pcg", "revm", + "revm-inspectors", "ripemd", "rlp 0.6.1", "scale-info", @@ -14724,6 +14896,7 @@ checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ "phf_macros", "phf_shared", + "serde", ] [[package]] @@ -18022,6 +18195,7 @@ dependencies = [ "libc", "rand_chacha 0.3.1", "rand_core 0.6.4", + "serde", ] [[package]] @@ -18489,9 +18663,9 @@ dependencies = [ [[package]] name = "revm" -version = "27.1.0" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6bf82101a1ad8a2b637363a37aef27f88b4efc8a6e24c72bf5f64923dc5532" +checksum = "718d90dce5f07e115d0e66450b1b8aa29694c1cf3f89ebddaddccc2ccbd2f13e" dependencies = [ "revm-bytecode", "revm-context", @@ -18520,10 +18694,11 @@ dependencies = [ [[package]] name = "revm-context" -version = "8.0.4" +version = "9.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cd508416a35a4d8a9feaf5ccd06ac6d6661cd31ee2dc0252f9f7316455d71f9" +checksum = "5a20c98e7008591a6f012550c2a00aa36cba8c14cc88eb88dec32eb9102554b4" dependencies = [ + "bitvec", "cfg-if", "derive-where", "revm-bytecode", @@ -18536,9 +18711,9 @@ dependencies = [ [[package]] name = "revm-context-interface" -version = "9.0.0" +version = "10.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc90302642d21c8f93e0876e201f3c5f7913c4fcb66fb465b0fd7b707dfe1c79" +checksum = "b50d241ed1ce647b94caf174fcd0239b7651318b2c4c06b825b59b973dfb8495" dependencies = [ "alloy-eip2930", "alloy-eip7702", @@ -18579,9 +18754,9 @@ dependencies = [ [[package]] name = "revm-handler" -version = "8.1.0" +version = "10.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1529c8050e663be64010e80ec92bf480315d21b1f2dbf65540028653a621b27d" +checksum = "550331ea85c1d257686e672081576172fe3d5a10526248b663bbf54f1bef226a" dependencies = [ "auto_impl", "derive-where", @@ -18598,9 +18773,9 @@ dependencies = [ [[package]] name = "revm-inspector" -version = "8.1.0" +version = "10.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f78db140e332489094ef314eaeb0bd1849d6d01172c113ab0eb6ea8ab9372926" +checksum = "7c0a6e9ccc2ae006f5bed8bd80cd6f8d3832cd55c5e861b9402fdd556098512f" dependencies = [ "auto_impl", "either", @@ -18614,11 +18789,29 @@ dependencies = [ "serde_json", ] +[[package]] +name = "revm-inspectors" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9b329afcc0f9fd5adfa2c6349a7435a8558e82bcae203142103a9a95e2a63b6" +dependencies = [ + "alloy-primitives", + "alloy-rpc-types-eth", + "alloy-rpc-types-trace", + "alloy-sol-types", + "anstyle", + "colorchoice", + "revm", + "serde", + "serde_json", + "thiserror 2.0.12", +] + [[package]] name = "revm-interpreter" -version = "24.0.0" +version = "25.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff9d7d9d71e8a33740b277b602165b6e3d25fff091ba3d7b5a8d373bf55f28a7" +checksum = "06575dc51b1d8f5091daa12a435733a90b4a132dca7ccee0666c7db3851bc30c" dependencies = [ "revm-bytecode", "revm-context-interface", @@ -18628,9 +18821,9 @@ dependencies = [ [[package]] name = "revm-precompile" -version = "25.0.0" +version = "27.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cee3f336b83621294b4cfe84d817e3eef6f3d0fce00951973364cc7f860424d" +checksum = "25b57d4bd9e6b5fe469da5452a8a137bc2d030a3cd47c46908efc615bbc699da" dependencies = [ "ark-bls12-381 0.5.0", "ark-bn254", @@ -18643,7 +18836,6 @@ dependencies = [ "cfg-if", "k256", "libsecp256k1", - "once_cell", "p256", "revm-primitives", "ripemd", @@ -20958,7 +21150,7 @@ version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f4b54a1211260718b92832b661025d1f1a4b6930fbadd6908e00edd265fa5f7" dependencies = [ - "darling", + "darling 0.20.10", "proc-macro2 1.0.95", "quote 1.0.40", "syn 2.0.98", @@ -20985,7 +21177,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78a3993a13b4eafa89350604672c8757b7ea84c7c5947d4b3691e3169c96379b" dependencies = [ - "darling", + "darling 0.20.10", "proc-macro-crate 3.1.0", "proc-macro2 1.0.95", "quote 1.0.40", @@ -21222,6 +21414,7 @@ dependencies = [ "bitcoin_hashes 0.14.0", "rand 0.8.5", "secp256k1-sys 0.10.1", + "serde", ] [[package]] @@ -21364,10 +21557,11 @@ checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0" [[package]] name = "serde" -version = "1.0.219" +version = "1.0.226" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "0dca6411025b24b60bfa7ec1fe1f8e710ac09782dca409ee8237ba74b51295fd" dependencies = [ + "serde_core", "serde_derive", ] @@ -21399,11 +21593,20 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_core" +version = "1.0.226" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba2ba63999edb9dac981fb34b3e5c0d111a69b0924e253ed29d83f7c99e966a4" +dependencies = [ + "serde_derive", +] + [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.226" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "8db53ae22f34573731bafa1db20f04027b2d25e02d8205921b569171699cdb33" dependencies = [ "proc-macro2 1.0.95", "quote 1.0.40", @@ -21488,7 +21691,7 @@ version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e" dependencies = [ - "darling", + "darling 0.20.10", "proc-macro2 1.0.95", "quote 1.0.40", "syn 2.0.98", @@ -24773,7 +24976,7 @@ version = "0.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c2c8da275a620dd676381d72395dfea91f0a6cd849665b4f1d0919371850701" dependencies = [ - "darling", + "darling 0.20.10", "parity-scale-codec", "proc-macro-error2", "quote 1.0.40", @@ -24789,7 +24992,7 @@ version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69516e8ff0e9340a0f21b8398da7f997571af4734ee81deada5150a2668c8443" dependencies = [ - "darling", + "darling 0.20.10", "parity-scale-codec", "proc-macro-error2", "quote 1.0.40", @@ -25088,9 +25291,9 @@ dependencies = [ [[package]] name = "syn-solidity" -version = "1.1.2" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5d879005cc1b5ba4e18665be9e9501d9da3a9b95f625497c4cb7ee082b532e" +checksum = "a0b198d366dbec045acfcd97295eb653a7a2b40e4dc764ef1e79aafcad439d3c" dependencies = [ "paste", "proc-macro2 1.0.95", diff --git a/Cargo.toml b/Cargo.toml index 63fc621a4756b..45f8b2f4a759a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1190,7 +1190,9 @@ regex = { version = "1.10.2" } relay-substrate-client = { path = "bridges/relays/client-substrate" } relay-utils = { path = "bridges/relays/utils" } remote-externalities = { path = "substrate/utils/frame/remote-externalities", default-features = false, package = "frame-remote-externalities" } -revm = { version = "27.0.2", default-features = false } +revm = { version = "29.0.0", default-features = false } +revm-inspectors = "0.30.0" +alloy-rpc-types-trace = "1.0.35" ripemd = { version = "0.1.3", default-features = false } rlp = { version = "0.6.1", default-features = false } rococo-emulated-chain = { path = "cumulus/parachains/integration-tests/emulated/chains/relays/rococo" } diff --git a/substrate/frame/revive/Cargo.toml b/substrate/frame/revive/Cargo.toml index 88762a7efa2f3..d2a958c51ca49 100644 --- a/substrate/frame/revive/Cargo.toml +++ b/substrate/frame/revive/Cargo.toml @@ -36,6 +36,8 @@ polkavm-common = { version = "0.27.0", default-features = false, features = ["al rand = { workspace = true, optional = true } rand_pcg = { workspace = true, optional = true } revm = { workspace = true } +revm-inspectors = { workspace = true } +alloy-rpc-types-trace = { workspace = true } rlp = { workspace = true } scale-info = { features = ["derive"], workspace = true } serde = { features = ["alloc", "derive"], workspace = true, default-features = false } diff --git a/substrate/frame/revive/src/benchmarking.rs b/substrate/frame/revive/src/benchmarking.rs index 1691d71f1c1ae..4f3972271a7c9 100644 --- a/substrate/frame/revive/src/benchmarking.rs +++ b/substrate/frame/revive/src/benchmarking.rs @@ -2406,7 +2406,6 @@ mod benchmarks { let mut setup = CallSetup::::new(module); let contract = setup.contract(); - let address = H160([0u8; 20]); let (mut ext, _) = setup.ext(); let mut interpreter = Interpreter::new(Default::default(), Default::default(), &mut ext); @@ -2417,7 +2416,7 @@ mod benchmarks { let _ = interpreter.stack.push(U256::from(n)); let _ = interpreter.stack.push(U256::from(0u32)); let _ = interpreter.stack.push(U256::from(0u32)); - let _ = interpreter.stack.push(address); + let _ = interpreter.stack.push(contract.address); let result; #[block] diff --git a/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs b/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs index dcb06738d0812..edb5531875e44 100644 --- a/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs +++ b/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs @@ -182,6 +182,12 @@ impl U256> Tracing for OpcodeTracer::Currency::set_balance(&ALICE, 100_000_000); + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + let config = OpcodeTracerConfig { + enable_memory: false, + disable_stack: false, + disable_storage: true, + enable_return_data: true, + limit: None, + memory_word_limit: 16, + }; + + let mut tracer = OpcodeTracer::new(config, |_| sp_core::U256::from(0u64)); + let _result = trace(&mut tracer, || { + builder::bare_call(addr) + .data(Bitwise::BitwiseCalls::testBitwise(Bitwise::testBitwiseCall {}).abi_encode()) + .build_and_unwrap_result() + }); + + let traces = tracer.collect_trace(); + + std::fs::write("/tmp/trace_ko.json", serde_json::to_string_pretty(&traces).unwrap()) + .unwrap(); + }); +} diff --git a/substrate/frame/revive/src/tests/sol/bitwise.rs b/substrate/frame/revive/src/tests/sol/bitwise.rs index 5fd279cffd860..561d4b35feb51 100644 --- a/substrate/frame/revive/src/tests/sol/bitwise.rs +++ b/substrate/frame/revive/src/tests/sol/bitwise.rs @@ -39,15 +39,34 @@ fn bitwise_works() { let result = builder::bare_call(addr) .data(Bitwise::BitwiseCalls::testBitwise(Bitwise::testBitwiseCall {}).abi_encode()) .build_and_unwrap_result(); + if result.did_revert() { if let Some(revert_msg) = decode_revert_reason(&result.data) { - log::error!("Revert message: {}", revert_msg); + panic!("Revert message: {revert_msg}"); } else { - log::error!("Revert without message, raw data: {:?}", result.data); + panic!("Revert without message, raw data: {:?}", result.data); } } - - assert!(!result.did_revert(), "bitwise test reverted"); }); } } + +#[test] +fn test_me() { + use crate::tests::sol::revm_tracing::RevmTracer; + use alloy_core::sol_types::SolInterface; + use revm::{context::TxEnv, context_interface::TransactTo, primitives::Bytes}; + + let (code, _) = compile_module_with_type("Bitwise", FixtureType::Solc).unwrap(); + let mut tracer = RevmTracer::default(); + let addr = tracer.deploy(TxEnv { data: Bytes::from(code), ..Default::default() }); + + let traces = tracer.call(TxEnv { + kind: TransactTo::Call(addr), + data: Bitwise::BitwiseCalls::testBitwise(Bitwise::testBitwiseCall {}) + .abi_encode() + .into(), + ..Default::default() + }); + std::fs::write("/tmp/trace_ok.json", serde_json::to_string_pretty(&traces).unwrap()).unwrap(); +} diff --git a/substrate/frame/revive/src/tests/sol/contract.rs b/substrate/frame/revive/src/tests/sol/contract.rs index 448a43d300422..7036b585d20f6 100644 --- a/substrate/frame/revive/src/tests/sol/contract.rs +++ b/substrate/frame/revive/src/tests/sol/contract.rs @@ -154,7 +154,7 @@ fn call_works() { #[test] fn call_revert() { - for fixture_type in [FixtureType::Solc, FixtureType::Resolc] { + for fixture_type in [FixtureType::Solc] { let (caller_code, _) = compile_module_with_type("Caller", fixture_type).unwrap(); let (callee_code, _) = compile_module_with_type("Callee", fixture_type).unwrap(); @@ -165,14 +165,10 @@ fn call_revert() { let Contract { addr: callee_addr, .. } = builder::bare_instantiate(Code::Upload(callee_code)).build_and_unwrap_contract(); - log::info!("Callee addr: {:?}", callee_addr); - // Instantiate the caller contract. let Contract { addr: caller_addr, .. } = builder::bare_instantiate(Code::Upload(caller_code)).build_and_unwrap_contract(); - log::info!("Caller addr: {:?}", caller_addr); - // Call revert and assert failure let result = builder::bare_call(caller_addr) .data( @@ -187,7 +183,6 @@ fn call_revert() { .build_and_unwrap_result(); let result = Caller::normalCall::abi_decode_returns(&result.data).unwrap(); assert!(!result.success, "Call should propagate revert"); - log::info!("Returned data: {:?}", result.output); assert!(result.output.len() > 0, "Returned data should contain revert message"); let data = result.output.as_ref(); diff --git a/substrate/frame/revive/src/tests/sol/memory.rs b/substrate/frame/revive/src/tests/sol/memory.rs index 895404ad5ed20..d4ffd6a4f2b42 100644 --- a/substrate/frame/revive/src/tests/sol/memory.rs +++ b/substrate/frame/revive/src/tests/sol/memory.rs @@ -41,7 +41,7 @@ fn memory_limit_works() { let test_cases = [ ( - // Writing 1 byte from 0 to the limit - 1 should work. + " Writing 1 byte from 0 to the limit - 1 should work.", Memory::expandMemoryCall { memorySize: primitives::U256::from( crate::limits::code::BASELINE_MEMORY_LIMIT - 1, @@ -50,7 +50,7 @@ fn memory_limit_works() { Ok(ExecReturnValue { data: vec![0u8; 32], flags: ReturnFlags::empty() }), ), ( - // Writing 1 byte from the limit should revert. + "Writing 1 byte from the limit should revert.", Memory::expandMemoryCall { memorySize: primitives::U256::from(crate::limits::code::BASELINE_MEMORY_LIMIT), }, @@ -58,9 +58,9 @@ fn memory_limit_works() { ), ]; - for (data, expected_result) in test_cases { + for (reason, data, expected_result) in test_cases { let result = builder::bare_call(addr).data(data.abi_encode()).build().result; - assert_eq!(result, expected_result); + assert_eq!(result, expected_result, "{}", reason); } }); } diff --git a/substrate/frame/revive/src/tests/sol/revm_tracing.rs b/substrate/frame/revive/src/tests/sol/revm_tracing.rs new file mode 100644 index 0000000000000..6b599d793ecdc --- /dev/null +++ b/substrate/frame/revive/src/tests/sol/revm_tracing.rs @@ -0,0 +1,62 @@ +use alloy_rpc_types_trace::geth::{DefaultFrame, GethDefaultTracingOptions}; +use revm::{ + context::{ContextTr, TxEnv}, + context_interface::TransactTo, + database::CacheDB, + database_interface::{DatabaseRef, EmptyDB}, + primitives::Address, + Context, ExecuteCommitEvm, InspectEvm, MainBuilder, MainContext, +}; +use revm_inspectors::tracing::{TracingInspector, TracingInspectorConfig}; + +#[derive(Debug, Default, Clone)] +pub struct RevmTracer { + db: CacheDB, +} + +impl RevmTracer { + fn get_nonce(&self, address: Address) -> u64 { + match self.db.basic_ref(address) { + Ok(Some(account_info)) => account_info.nonce, + _ => 0, + } + } + + pub fn deploy(&mut self, tx: TxEnv) -> Address { + let mut evm = Context::mainnet().with_db(self.db.clone()).build_mainnet(); + let tx = TxEnv { + gas_limit: 1000000, + kind: TransactTo::Create, + nonce: self.get_nonce(tx.caller), + ..tx + }; + let out = evm.transact_commit(tx).unwrap(); + assert!(out.is_success(), "Contract deployment failed"); + self.db = evm.db().clone(); + out.created_address().unwrap() + } + + pub fn call(&mut self, tx: TxEnv) -> DefaultFrame { + let mut insp = TracingInspector::new(TracingInspectorConfig::from_geth_config( + &GethDefaultTracingOptions::default().enable_memory(), + )); + + let evm = Context::mainnet().with_db(self.db.clone()).build_mainnet(); + let mut evm = evm.clone().build_mainnet_with_inspector(&mut insp); + let tx = TxEnv { nonce: self.get_nonce(tx.caller), ..tx }; + let res = evm.inspect_tx(tx).unwrap(); + assert!(res.result.is_success()); + self.db = evm.db().clone(); + + let trace = insp + .with_transaction_gas_used(res.result.gas_used()) + .geth_builder() + .geth_traces( + res.result.gas_used(), + res.result.output().unwrap_or_default().clone(), + GethDefaultTracingOptions::default().enable_memory(), + ); + + trace + } +} diff --git a/substrate/frame/revive/src/vm/evm/instructions/arithmetic.rs b/substrate/frame/revive/src/vm/evm/instructions/arithmetic.rs index 84ae34d6c66c3..c12954dc715ac 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/arithmetic.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/arithmetic.rs @@ -35,7 +35,7 @@ use revm::interpreter::gas::{EXP, LOW, MID, VERYLOW}; pub fn add<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { interpreter.ext.gas_meter_mut().charge_evm_gas(VERYLOW)?; let ([op1], op2) = interpreter.stack.popn_top()?; - *op2 = op1.saturating_add(*op2); + *op2 = op1.overflowing_add(*op2).0; ControlFlow::Continue(()) } @@ -43,7 +43,7 @@ pub fn add<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow< pub fn mul<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { interpreter.ext.gas_meter_mut().charge_evm_gas(LOW)?; let ([op1], op2) = interpreter.stack.popn_top()?; - *op2 = op1.saturating_mul(*op2); + *op2 = op1.overflowing_mul(*op2).0; ControlFlow::Continue(()) } @@ -51,7 +51,7 @@ pub fn mul<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow< pub fn sub<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { interpreter.ext.gas_meter_mut().charge_evm_gas(VERYLOW)?; let ([op1], op2) = interpreter.stack.popn_top()?; - *op2 = op1.saturating_sub(*op2); + *op2 = op1.overflowing_sub(*op2).0; ControlFlow::Continue(()) } diff --git a/substrate/frame/revive/src/vm/evm/instructions/control.rs b/substrate/frame/revive/src/vm/evm/instructions/control.rs index 3afa1e3581d91..f24e0f635e5c6 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/control.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/control.rs @@ -120,8 +120,8 @@ pub fn revert<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFl } /// Stop opcode. This opcode halts the execution. -pub fn stop<'ext, E: Ext>(interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { - return_inner(interpreter, |_| Halt::Stop) +pub fn stop<'ext, E: Ext>(_interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { + ControlFlow::Break(Halt::Stop) } /// Invalid opcode. This opcode halts the execution. diff --git a/substrate/frame/revive/src/vm/evm/interpreter.rs b/substrate/frame/revive/src/vm/evm/interpreter.rs index 35578bccaeb78..adee42973ebb0 100644 --- a/substrate/frame/revive/src/vm/evm/interpreter.rs +++ b/substrate/frame/revive/src/vm/evm/interpreter.rs @@ -108,10 +108,9 @@ impl<'a, E: Ext> Interpreter<'a, E> { Halt::SelfDestruct => Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() }), - Halt::OutOfGas => Err(ExecError::from(Error::::OutOfGas)), + Halt::MemoryOOG | Halt::OutOfGas => Err(ExecError::from(Error::::OutOfGas)), Halt::StackOverflow => Err(ExecError::from(Error::::ContractTrapped)), Halt::StackUnderflow => Err(ExecError::from(Error::::ContractTrapped)), - Halt::MemoryOOG => Err(ExecError::from(Error::::StaticMemoryTooLarge)), Halt::InvalidOperandOOG => Err(ExecError::from(Error::::OutOfGas)), Halt::InvalidJump => Err(ExecError::from(Error::::InvalidInstruction)), diff --git a/substrate/frame/revive/src/vm/evm/memory.rs b/substrate/frame/revive/src/vm/evm/memory.rs index 532f8bd350394..010bc61bdfdb2 100644 --- a/substrate/frame/revive/src/vm/evm/memory.rs +++ b/substrate/frame/revive/src/vm/evm/memory.rs @@ -75,6 +75,9 @@ impl Memory { /// Set memory at the given offset with the provided data pub fn set(&mut self, offset: usize, data: &[u8]) { + if data.is_empty() { + return; + } let end = offset.saturating_add(data.len()); if end > self.0.len() { self.0.resize(end, 0); From 57a5c60aa7086ea43a75e0f34ee5acac4ea80888 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Fri, 12 Dec 2025 22:49:10 +0100 Subject: [PATCH 38/76] fix --- .../frame/revive/rpc/revive_chain.metadata | 0 .../revive/src/vm/evm/instructions/macros.rs | 0 substrate/frame/revive/todos.md | 99 ------------------- 3 files changed, 99 deletions(-) delete mode 100644 substrate/frame/revive/rpc/revive_chain.metadata delete mode 100644 substrate/frame/revive/src/vm/evm/instructions/macros.rs delete mode 100644 substrate/frame/revive/todos.md diff --git a/substrate/frame/revive/rpc/revive_chain.metadata b/substrate/frame/revive/rpc/revive_chain.metadata deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/substrate/frame/revive/src/vm/evm/instructions/macros.rs b/substrate/frame/revive/src/vm/evm/instructions/macros.rs deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/substrate/frame/revive/todos.md b/substrate/frame/revive/todos.md deleted file mode 100644 index 290986984bbd6..0000000000000 --- a/substrate/frame/revive/todos.md +++ /dev/null @@ -1,99 +0,0 @@ -# TODOs and Commented Code Added in PR - -## Summary -Found 4 new TODO comments and 1 commented code line added in the `pg/revm-refactor` PR. - -## Newly Added TODO Comments - -1. **Gas limit setting**: `Weight::from_parts(u64::MAX, u64::MAX), // TODO: set the right limit` -2. **Gas limit control flow**: `ControlFlow::Continue(u64::MAX) // TODO: Set the right gas limit` -3. **Input data type**: `pub input: Vec, // TODO maybe just &'a[u8]` -4. **Memory slice method**: `/// TODO same as slice_mut?` - -## Newly Added Commented Code - -1. **Disabled Solidity tests**: `// mod sol;` - commented out module import - -## Notes -- Most TODOs are related to gas handling optimization -- The commented `mod sol;` appears to intentionally disable Solidity test module -- No critical issues found, mainly optimization reminders - ---- - -# PR Review Notes - -## Overall Assessment -This is a substantial refactor that modernizes the revm integration. The changes look well-structured and improve type safety. - -## Positive Changes ✅ - -### 1. **Cleaner Type System** -- Good separation between `sp_core::U256` and `alloy_core::primitives::U256` -- Consistent use of `primitives::U256` alias instead of verbose `AlloyU256` -- Proper conversion methods (`from_big_endian` vs `from_be_slice`) - -### 2. **Simplified Interpreter Interface** -- The new `Interpreter::new()` constructor is much cleaner than the old struct initialization -- Removing the complex generic `Interpreter>` type is a good simplification - -### 3. **Better Error Handling** -- Using `ControlFlow` for gas charging is more idiomatic than throwing errors -- The `charge_evm` method combining gas and token charging is elegant - -## Areas for Improvement ⚠️ - -### 1. **Memory Management** -```rust -// In benchmarking.rs - line 2411 -let mut interpreter = Interpreter::new(Default::default(), Default::default(), &mut ext); -``` -**Concern**: Using `Default::default()` for bytecode and gas could mask initialization issues. Consider explicit initialization. - -### 2. **Test Coverage** -The test file changes look comprehensive, but consider adding error cases to ensure the new conversion methods handle edge cases properly. - -### 3. **Documentation** ❌ -The PR lacks documentation for the major API changes. Consider adding: -- Doc comments for the new `Interpreter` interface -- Migration guide for the type system changes -- Examples of proper `primitives::U256` vs `crate::U256` usage - -### 4. **Performance Considerations** -```rust -// Multiple instances of type conversions like: -primitives::U256::from_be_bytes(magic_number.to_big_endian()) -``` -**Question**: Have you benchmarked the performance impact of these additional conversions? The `to_big_endian()` -> `from_be_bytes()` roundtrip might be optimizable. - -## Specific Code Issues - -### 1. **Potential Bug in benchmarking.rs** -```rust -let result; -#[block] -{ - result = extcodecopy_fn(&mut interpreter); -} -assert!(result.is_continue()); -``` -**Issue**: The `result` variable is uninitialized before the block. This might not compile. - -### 2. **Inconsistent Error Handling** -```rust -// In gas.rs -.map_or_else(|| ControlFlow::Break(Halt::OutOfGas), ControlFlow::Continue)?; -``` -**Style**: The `.map_or_else()` followed by `?` is unusual. Consider using `ok_or()` or explicit matching. - -## Missing Tests -Consider adding tests for: -1. Gas limit edge cases with the new `ControlFlow` system -2. Type conversion boundary conditions -3. Memory allocation patterns with the new interpreter - -## Dependencies -The addition of `proptest = "1"` suggests property-based testing, but it's not visible in the diff. Make sure it's actually needed. - -## Overall Recommendation -**Approve with minor changes** - This is a solid refactor that improves the codebase. Address the documentation and potential initialization issues, then it should be ready to merge. \ No newline at end of file From cd96cd6634cac08b82ee8bd883071f0a425dbc50 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Fri, 12 Dec 2025 22:56:22 +0100 Subject: [PATCH 39/76] rm comment --- substrate/frame/revive/src/evm/tracing/opcode_tracing.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs b/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs index d92f2899b0f2a..4fc909c2b29d3 100644 --- a/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs +++ b/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs @@ -142,14 +142,6 @@ impl Tracing for OpcodeTracer { crate::evm::Bytes::default() }; - // Create the pending opcode step (without gas cost) - - // log::trace!(target: crate::LOG_TARGET, - // "\n[{pc}]: {opcode}\nstack: {stack_data:?}\nmemory: {memory:?}", - // opcode = revm::bytecode::OpCode::new(opcode) - // .map_or("INVALID".to_string(), |x| format!("{:?}", x.info())), - // ); - let step = OpcodeStep { pc, op: opcode, From 45d876ca8277daec3b936ef42dc840bbe634974c Mon Sep 17 00:00:00 2001 From: pgherveou Date: Mon, 15 Dec 2025 10:35:51 +0100 Subject: [PATCH 40/76] fixes --- Cargo.lock | 113 +++--------------- Cargo.toml | 5 +- substrate/frame/revive/Cargo.toml | 2 - .../revive/src/evm/api/debug_rpc_types.rs | 4 +- .../revive/src/evm/tracing/opcode_tracing.rs | 6 +- .../src/precompiles/builtin/p256_verify.rs | 2 +- substrate/frame/revive/src/tests/sol.rs | 24 ++-- 7 files changed, 35 insertions(+), 121 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 46c46954fa99d..c98fb4b9ce298 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -142,20 +142,6 @@ dependencies = [ "thiserror 2.0.12", ] -[[package]] -name = "alloy-consensus-any" -version = "1.0.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2d5e8668ef6215efdb7dcca6f22277b4e483a5650e05f5de22b2350971f4b8" -dependencies = [ - "alloy-consensus", - "alloy-eips", - "alloy-primitives", - "alloy-rlp", - "alloy-serde", - "serde", -] - [[package]] name = "alloy-core" version = "1.2.1" @@ -256,19 +242,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "alloy-network-primitives" -version = "1.0.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e58f4f345cef483eab7374f2b6056973c7419ffe8ad35e994b7a7f5d8e0c7ba4" -dependencies = [ - "alloy-consensus", - "alloy-eips", - "alloy-primitives", - "alloy-serde", - "serde", -] - [[package]] name = "alloy-primitives" version = "1.3.1" @@ -318,41 +291,6 @@ dependencies = [ "syn 2.0.98", ] -[[package]] -name = "alloy-rpc-types-eth" -version = "1.0.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "361cd87ead4ba7659bda8127902eda92d17fa7ceb18aba1676f7be10f7222487" -dependencies = [ - "alloy-consensus", - "alloy-consensus-any", - "alloy-eips", - "alloy-network-primitives", - "alloy-primitives", - "alloy-rlp", - "alloy-serde", - "alloy-sol-types", - "itertools 0.14.0", - "serde", - "serde_json", - "serde_with", - "thiserror 2.0.12", -] - -[[package]] -name = "alloy-rpc-types-trace" -version = "1.0.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de4e95fb0572b97b17751d0fdf5cdc42b0050f9dd9459eddd1bf2e2fbfed0a33" -dependencies = [ - "alloy-primitives", - "alloy-rpc-types-eth", - "alloy-serde", - "serde", - "serde_json", - "thiserror 2.0.12", -] - [[package]] name = "alloy-serde" version = "1.0.41" @@ -13336,7 +13274,6 @@ version = "0.1.0" dependencies = [ "alloy-consensus", "alloy-core", - "alloy-rpc-types-trace", "alloy-trie", "array-bytes 6.2.2", "assert_matches", @@ -13373,7 +13310,6 @@ dependencies = [ "rand 0.8.5", "rand_pcg", "revm", - "revm-inspectors", "ripemd", "rlp 0.6.1", "scale-info", @@ -15089,7 +15025,6 @@ checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ "phf_macros", "phf_shared", - "serde", ] [[package]] @@ -18849,9 +18784,9 @@ dependencies = [ [[package]] name = "revm" -version = "29.0.1" +version = "27.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "718d90dce5f07e115d0e66450b1b8aa29694c1cf3f89ebddaddccc2ccbd2f13e" +checksum = "5e6bf82101a1ad8a2b637363a37aef27f88b4efc8a6e24c72bf5f64923dc5532" dependencies = [ "revm-bytecode", "revm-context", @@ -18880,11 +18815,10 @@ dependencies = [ [[package]] name = "revm-context" -version = "9.1.0" +version = "8.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a20c98e7008591a6f012550c2a00aa36cba8c14cc88eb88dec32eb9102554b4" +checksum = "9cd508416a35a4d8a9feaf5ccd06ac6d6661cd31ee2dc0252f9f7316455d71f9" dependencies = [ - "bitvec", "cfg-if", "derive-where", "revm-bytecode", @@ -18897,9 +18831,9 @@ dependencies = [ [[package]] name = "revm-context-interface" -version = "10.2.0" +version = "9.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b50d241ed1ce647b94caf174fcd0239b7651318b2c4c06b825b59b973dfb8495" +checksum = "dc90302642d21c8f93e0876e201f3c5f7913c4fcb66fb465b0fd7b707dfe1c79" dependencies = [ "alloy-eip2930", "alloy-eip7702", @@ -18940,9 +18874,9 @@ dependencies = [ [[package]] name = "revm-handler" -version = "10.0.1" +version = "8.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "550331ea85c1d257686e672081576172fe3d5a10526248b663bbf54f1bef226a" +checksum = "1529c8050e663be64010e80ec92bf480315d21b1f2dbf65540028653a621b27d" dependencies = [ "auto_impl", "derive-where", @@ -18959,9 +18893,9 @@ dependencies = [ [[package]] name = "revm-inspector" -version = "10.0.1" +version = "8.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c0a6e9ccc2ae006f5bed8bd80cd6f8d3832cd55c5e861b9402fdd556098512f" +checksum = "f78db140e332489094ef314eaeb0bd1849d6d01172c113ab0eb6ea8ab9372926" dependencies = [ "auto_impl", "either", @@ -18975,29 +18909,11 @@ dependencies = [ "serde_json", ] -[[package]] -name = "revm-inspectors" -version = "0.30.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de23199c4b6181a6539e4131cf7e31cde4df05e1192bcdce491c34a511241588" -dependencies = [ - "alloy-primitives", - "alloy-rpc-types-eth", - "alloy-rpc-types-trace", - "alloy-sol-types", - "anstyle", - "colorchoice", - "revm", - "serde", - "serde_json", - "thiserror 2.0.12", -] - [[package]] name = "revm-interpreter" -version = "25.0.3" +version = "24.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06575dc51b1d8f5091daa12a435733a90b4a132dca7ccee0666c7db3851bc30c" +checksum = "ff9d7d9d71e8a33740b277b602165b6e3d25fff091ba3d7b5a8d373bf55f28a7" dependencies = [ "revm-bytecode", "revm-context-interface", @@ -19007,9 +18923,9 @@ dependencies = [ [[package]] name = "revm-precompile" -version = "27.0.0" +version = "25.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25b57d4bd9e6b5fe469da5452a8a137bc2d030a3cd47c46908efc615bbc699da" +checksum = "4cee3f336b83621294b4cfe84d817e3eef6f3d0fce00951973364cc7f860424d" dependencies = [ "ark-bls12-381 0.5.0", "ark-bn254", @@ -19022,6 +18938,7 @@ dependencies = [ "cfg-if", "k256", "libsecp256k1", + "once_cell", "p256", "revm-primitives", "ripemd", diff --git a/Cargo.toml b/Cargo.toml index ffe4ac94ded87..e3d07a25a272e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1206,9 +1206,7 @@ regex = { version = "1.10.2" } relay-substrate-client = { path = "bridges/relays/client-substrate" } relay-utils = { path = "bridges/relays/utils" } remote-externalities = { path = "substrate/utils/frame/remote-externalities", default-features = false, package = "frame-remote-externalities" } -revm = { version = "29.0.0", default-features = false } -revm-inspectors = "0.30.0" -alloy-rpc-types-trace = "1.0.35" +revm = { version = "27.0.2", default-features = false } ripemd = { version = "0.1.3", default-features = false } rlp = { version = "0.6.1", default-features = false } rococo-emulated-chain = { path = "cumulus/parachains/integration-tests/emulated/chains/relays/rococo" } @@ -1580,3 +1578,4 @@ wasmi = { opt-level = 3 } x25519-dalek = { opt-level = 3 } yamux = { opt-level = 3 } zeroize = { opt-level = 3 } + diff --git a/substrate/frame/revive/Cargo.toml b/substrate/frame/revive/Cargo.toml index f280054ebd419..91c7c0c16563f 100644 --- a/substrate/frame/revive/Cargo.toml +++ b/substrate/frame/revive/Cargo.toml @@ -39,8 +39,6 @@ polkavm-common = { version = "0.30.0", default-features = false, features = ["al rand = { workspace = true, optional = true } rand_pcg = { workspace = true, optional = true } revm = { workspace = true } -revm-inspectors = { workspace = true } -alloy-rpc-types-trace = { workspace = true } rlp = { workspace = true } scale-info = { features = ["derive"], workspace = true } serde = { features = ["alloc", "derive"], workspace = true } diff --git a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs index 0898de7209485..c149f22380ed6 100644 --- a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs +++ b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs @@ -476,9 +476,9 @@ pub struct OpcodeStep { #[serde(serialize_with = "serialize_opcode", deserialize_with = "deserialize_opcode")] pub op: u8, /// Remaining gas before executing this opcode. - pub gas: U256, + pub gas: u64, /// Cost of executing this opcode. - pub gas_cost: U256, + pub gas_cost: u64, /// Current call depth. pub depth: u32, /// EVM stack contents. diff --git a/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs b/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs index 4fc909c2b29d3..ef70b62f87179 100644 --- a/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs +++ b/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs @@ -145,8 +145,8 @@ impl Tracing for OpcodeTracer { let step = OpcodeStep { pc, op: opcode, - gas: gas_before, - gas_cost: sp_core::U256::zero(), // Will be set in exit_opcode + gas: gas_before.try_into().unwrap_or(u64::MAX), + gas_cost: 0u64, // Will be set in exit_opcode depth: self.depth, stack: stack_data, memory: memory_data, @@ -164,7 +164,7 @@ impl Tracing for OpcodeTracer { if let Some(mut step) = self.pending_step.take() { if let Some(gas_before) = self.pending_gas_before.take() { let gas_cost = gas_before.saturating_sub(gas_left); - step.gas_cost = gas_cost; + step.gas_cost = gas_cost.try_into().unwrap_or(u64::MAX); } self.steps.push(step); } diff --git a/substrate/frame/revive/src/precompiles/builtin/p256_verify.rs b/substrate/frame/revive/src/precompiles/builtin/p256_verify.rs index 6560217b9a498..ae08ab28c9b44 100644 --- a/substrate/frame/revive/src/precompiles/builtin/p256_verify.rs +++ b/substrate/frame/revive/src/precompiles/builtin/p256_verify.rs @@ -53,7 +53,7 @@ impl PrimitivePrecompile for P256Verify { ) -> Result, Error> { env.frame_meter_mut().charge_weight_token(RuntimeCosts::P256Verify)?; - if revm::precompile::secp256r1::verify_impl(&input) { + if revm::precompile::secp256r1::verify_impl(&input).is_some() { Ok(U256::one().to_big_endian().to_vec()) } else { Ok(Default::default()) diff --git a/substrate/frame/revive/src/tests/sol.rs b/substrate/frame/revive/src/tests/sol.rs index 4726a82e1769e..9e67c86b45a0b 100644 --- a/substrate/frame/revive/src/tests/sol.rs +++ b/substrate/frame/revive/src/tests/sol.rs @@ -626,8 +626,8 @@ fn opcode_tracing_works() { let mut actual_trace = tracer.collect_trace(); actual_trace.struct_logs.iter_mut().for_each(|step| { - step.gas = U256::from(0u64); - step.gas_cost = U256::from(0u64); + step.gas = 0u64; + step.gas_cost = 0u64; }); // Create expected trace structure that matches the exact execution @@ -639,8 +639,8 @@ fn opcode_tracing_works() { OpcodeStep { pc: 0, op: PUSH1, - gas: U256::from(0u64), - gas_cost: U256::from(0u64), + gas: 0u64, + gas_cost: 0u64, depth: 1, stack: vec![], memory: vec![], @@ -651,8 +651,8 @@ fn opcode_tracing_works() { OpcodeStep { pc: 2, op: PUSH1, - gas: U256::from(0u64), - gas_cost: U256::from(0u64), + gas: 0u64, + gas_cost: 0u64, depth: 1, stack: vec![crate::evm::Bytes(U256::from(0x80).to_big_endian().to_vec())], memory: vec![], @@ -663,8 +663,8 @@ fn opcode_tracing_works() { OpcodeStep { pc: 4, op: MSTORE, - gas: U256::from(0u64), - gas_cost: U256::from(0u64), + gas: 0u64, + gas_cost: 0u64, depth: 1, stack: vec![ crate::evm::Bytes(U256::from(0x80).to_big_endian().to_vec()), @@ -678,8 +678,8 @@ fn opcode_tracing_works() { OpcodeStep { pc: 5, op: CALLVALUE, - gas: U256::from(0u64), - gas_cost: U256::from(0u64), + gas: 0u64, + gas_cost: 0u64, depth: 1, stack: vec![], memory: vec![], @@ -690,8 +690,8 @@ fn opcode_tracing_works() { OpcodeStep { pc: 6, op: DUP1, - gas: U256::from(0u64), - gas_cost: U256::from(0u64), + gas: 0u64, + gas_cost: 0u64, depth: 1, stack: vec![crate::evm::Bytes(U256::from(0).to_big_endian().to_vec())], memory: vec![], From e7febda91791a6fb321919309b6d8380f812aa44 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Mon, 15 Dec 2025 12:51:04 +0100 Subject: [PATCH 41/76] fix for evm-test-suite --- .../revive/src/evm/api/debug_rpc_types.rs | 27 +++++++++++- .../revive/src/evm/tracing/opcode_tracing.rs | 41 +++++++------------ 2 files changed, 39 insertions(+), 29 deletions(-) diff --git a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs index c149f22380ed6..848acd91eaa6c 100644 --- a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs +++ b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs @@ -456,7 +456,7 @@ where #[serde(rename_all = "camelCase")] pub struct OpcodeTrace { /// Total gas used by the transaction. - pub gas: U256, + pub gas: u64, /// Whether the transaction failed. pub failed: bool, /// The return value of the transaction. @@ -488,7 +488,10 @@ pub struct OpcodeStep { #[serde(skip_serializing_if = "Vec::is_empty", serialize_with = "serialize_memory_no_prefix")] pub memory: Vec, /// Contract storage changes. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde( + skip_serializing_if = "Option::is_none", + serialize_with = "serialize_storage_no_prefix" + )] pub storage: Option>, /// Return data from last frame output. #[serde(skip_serializing_if = "Bytes::is_empty")] @@ -939,3 +942,23 @@ where let hex_values: Vec = memory.iter().map(|bytes| bytes.to_hex_no_prefix()).collect(); hex_values.serialize(serializer) } + +/// Serialize storage map without "0x" prefix (like Geth) +fn serialize_storage_no_prefix( + storage: &Option>, + serializer: S, +) -> Result +where + S: serde::Serializer, +{ + match storage { + None => serializer.serialize_none(), + Some(map) => { + let mut ser_map = serializer.serialize_map(Some(map.len()))?; + for (key, value) in map { + ser_map.serialize_entry(&key.to_hex_no_prefix(), &value.to_hex_no_prefix())?; + } + ser_map.end() + }, + } +} diff --git a/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs b/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs index ef70b62f87179..52a1bc3b8ae2f 100644 --- a/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs +++ b/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs @@ -42,7 +42,7 @@ pub struct OpcodeTracer { step_count: u64, /// Total gas used by the transaction. - total_gas_used: U256, + total_gas_used: u64, /// Whether the transaction failed. failed: bool, @@ -54,7 +54,7 @@ pub struct OpcodeTracer { pending_step: Option, /// Gas before executing the current pending step. - pending_gas_before: Option, + pending_gas_before: Option, /// List of storage per call storages_per_call: Vec>, @@ -68,7 +68,7 @@ impl OpcodeTracer { steps: Vec::new(), depth: 0, step_count: 0, - total_gas_used: U256::zero(), + total_gas_used: 0, failed: false, return_value: Bytes::default(), pending_step: None, @@ -89,21 +89,6 @@ impl OpcodeTracer { last_step.error = Some(error); } } - - /// Record return data. - pub fn record_return_data(&mut self, data: &[u8]) { - self.return_value = Bytes(data.to_vec()); - } - - /// Mark the transaction as failed. - pub fn mark_failed(&mut self) { - self.failed = true; - } - - /// Set the total gas used by the transaction. - pub fn set_total_gas_used(&mut self, gas_used: U256) { - self.total_gas_used = gas_used; - } } impl Tracing for OpcodeTracer { @@ -156,14 +141,14 @@ impl Tracing for OpcodeTracer { }; self.pending_step = Some(step); - self.pending_gas_before = Some(gas_before); + self.pending_gas_before = Some(gas_before.try_into().unwrap_or(u64::MAX)); self.step_count += 1; } fn exit_opcode(&mut self, gas_left: U256) { if let Some(mut step) = self.pending_step.take() { if let Some(gas_before) = self.pending_gas_before.take() { - let gas_cost = gas_before.saturating_sub(gas_left); + let gas_cost = gas_before.saturating_sub(gas_left.try_into().unwrap_or(u64::MAX)); step.gas_cost = gas_cost.try_into().unwrap_or(u64::MAX); } self.steps.push(step); @@ -188,15 +173,15 @@ impl Tracing for OpcodeTracer { if output.did_revert() { self.record_error("execution reverted".to_string()); if self.depth == 0 { - self.mark_failed(); + self.failed = true; } } else { - self.record_return_data(&output.data); + self.return_value = Bytes(output.data.to_vec()); } // Set total gas used if this is the top-level call (depth 1, will become 0 after decrement) if self.depth == 1 { - self.set_total_gas_used(gas_used); + self.total_gas_used = gas_used.try_into().unwrap_or(u64::MAX); } self.storages_per_call.pop(); @@ -211,8 +196,8 @@ impl Tracing for OpcodeTracer { // Mark as failed if this is the top-level call if self.depth == 1 { - self.mark_failed(); - self.set_total_gas_used(gas_used); + self.failed = true; + self.total_gas_used = gas_used.try_into().unwrap_or(u64::MAX); } if self.depth > 0 { @@ -231,7 +216,9 @@ impl Tracing for OpcodeTracer { // Get the last storage map for the current call depth if let Some(storage) = self.storages_per_call.last_mut() { let key_bytes = crate::evm::Bytes(key.unhashed().to_vec()); - let value_bytes = crate::evm::Bytes(new_value.map(|v| v.to_vec()).unwrap_or_default()); + let value_bytes = crate::evm::Bytes( + new_value.map(|v| v.to_vec()).unwrap_or_else(|| alloc::vec![0u8; 32]), + ); storage.insert(key_bytes, value_bytes); // Set storage on the pending step @@ -251,7 +238,7 @@ impl Tracing for OpcodeTracer { if let Some(storage) = self.storages_per_call.last_mut() { let key_bytes = crate::evm::Bytes(key.unhashed().to_vec()); storage.entry(key_bytes).or_insert_with(|| { - crate::evm::Bytes(value.map(|v| v.to_vec()).unwrap_or_default()) + crate::evm::Bytes(value.map(|v| v.to_vec()).unwrap_or_else(|| alloc::vec![0u8; 32])) }); // Set storage on the pending step From a950637d0e80421f9d780443e84d7b6dcaa7329f Mon Sep 17 00:00:00 2001 From: pgherveou Date: Mon, 15 Dec 2025 21:23:21 +0100 Subject: [PATCH 42/76] wip --- substrate/frame/revive/proc-macro/src/lib.rs | 20 + .../revive/src/evm/api/debug_rpc_types.rs | 719 +++++++++--------- .../frame/revive/src/evm/api/hex_serde.rs | 2 +- substrate/frame/revive/src/evm/tracing.rs | 10 +- .../revive/src/evm/tracing/call_tracing.rs | 12 +- .../revive/src/evm/tracing/opcode_tracing.rs | 21 +- .../src/evm/tracing/prestate_tracing.rs | 8 +- .../revive/src/evm/tracing/syscall_tracing.rs | 188 +++++ substrate/frame/revive/src/exec.rs | 25 +- substrate/frame/revive/src/lib.rs | 4 +- substrate/frame/revive/src/tests/pvm.rs | 43 +- substrate/frame/revive/src/tests/sol.rs | 98 ++- substrate/frame/revive/src/tracing.rs | 27 +- substrate/frame/revive/src/vm/evm.rs | 2 +- 14 files changed, 766 insertions(+), 413 deletions(-) create mode 100644 substrate/frame/revive/src/evm/tracing/syscall_tracing.rs diff --git a/substrate/frame/revive/proc-macro/src/lib.rs b/substrate/frame/revive/proc-macro/src/lib.rs index b67e15a0103df..e88298c3c7e46 100644 --- a/substrate/frame/revive/proc-macro/src/lib.rs +++ b/substrate/frame/revive/proc-macro/src/lib.rs @@ -422,9 +422,29 @@ fn expand_functions(def: &EnvDef) -> TokenStream2 { let trace_fmt_str = format!("{}({}) = {{:?}} weight_consumed: {{:?}}", name, params_fmt_str); quote! { + + crate::tracing::if_tracing(|tracer| { + let meter = self.ext.frame_meter(); + tracer.enter_ecall( + #name, + meter.eth_gas_left().unwrap_or_default().try_into().unwrap_or_default(), + meter.weight_left().unwrap_or_default(), + self.ext.last_frame_output(), + ); + }); + // wrap body in closure to make sure the tracing is always executed let result = (|| #body)(); ::log::trace!(target: "runtime::revive::strace", #trace_fmt_str, #( #trace_fmt_args, )* result, self.ext.frame_meter().weight_consumed()); + + crate::tracing::if_tracing(|tracer| { + let meter = self.ext.frame_meter(); + tracer.exit_ecall( + meter.eth_gas_left().unwrap_or_default().try_into().unwrap_or_default(), + meter.weight_left().unwrap_or_default(), + ); + }); + result } }; diff --git a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs index 848acd91eaa6c..e932310402d33 100644 --- a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs +++ b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::evm::Bytes; +use crate::{evm::Bytes, Weight}; use alloc::{collections::BTreeMap, string::String, vec::Vec}; use codec::{Decode, Encode}; use derive_more::From; @@ -39,6 +39,9 @@ pub enum TracerType { /// A tracer that traces opcodes. StructLogger(Option), + + /// A tracer that traces syscalls. + SyscallTracer(Option), } impl From for TracerType { @@ -59,6 +62,12 @@ impl From for TracerType { } } +impl From for TracerType { + fn from(config: SyscallTracerConfig) -> Self { + TracerType::SyscallTracer(Some(config)) + } +} + impl Default for TracerType { fn default() -> Self { TracerType::CallTracer(Some(CallTracerConfig::default())) @@ -84,24 +93,39 @@ impl<'de> Deserialize<'de> for TracerConfig { where D: Deserializer<'de>, { - #[derive(Default, Deserialize)] - #[serde(default, rename_all = "camelCase")] - struct TracerConfigInner { - #[serde(flatten)] - config: Option, - + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + struct TracerConfigWithType { #[serde(flatten)] - opcode_config: Option, + config: TracerType, + #[serde(with = "humantime_serde", default)] + timeout: Option, + } - #[serde(with = "humantime_serde")] + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + struct TracerConfigInline { + #[serde(flatten, default)] + opcode_config: OpcodeTracerConfig, + #[serde(with = "humantime_serde", default)] timeout: Option, } - let inner = TracerConfigInner::deserialize(deserializer)?; - Ok(TracerConfig { - config: inner.config.unwrap_or_else(|| TracerType::StructLogger(inner.opcode_config)), - timeout: inner.timeout, - }) + #[derive(Deserialize)] + #[serde(untagged)] + enum TracerConfigHelper { + WithType(TracerConfigWithType), + Inline(TracerConfigInline), + } + + match TracerConfigHelper::deserialize(deserializer)? { + TracerConfigHelper::WithType(cfg) => + Ok(TracerConfig { config: cfg.config, timeout: cfg.timeout }), + TracerConfigHelper::Inline(cfg) => Ok(TracerConfig { + config: TracerType::StructLogger(Some(cfg.opcode_config)), + timeout: cfg.timeout, + }), + } } } @@ -265,6 +289,17 @@ fn test_tracer_config_serialization() { timeout: None, }, ), + ( + r#"{"tracer": "syscallTracer" }"#, + TracerConfig { config: TracerType::SyscallTracer(None), timeout: None }, + ), + ( + r#"{"tracer": "syscallTracer", "tracerConfig": {}}"#, + TracerConfig { + config: TracerType::SyscallTracer(Some(SyscallTracerConfig::default())), + timeout: None, + }, + ), ]; for (json_data, expected) in tracers { @@ -305,6 +340,8 @@ pub enum Trace { Prestate(PrestateTrace), /// An opcode trace. Opcode(OpcodeTrace), + /// A syscall trace. + Syscall(SyscallTrace), } /// A prestate Trace @@ -456,6 +493,7 @@ where #[serde(rename_all = "camelCase")] pub struct OpcodeTrace { /// Total gas used by the transaction. + #[serde(with = "super::hex_serde")] pub gas: u64, /// Whether the transaction failed. pub failed: bool, @@ -471,13 +509,16 @@ pub struct OpcodeTrace { #[serde(rename_all = "camelCase")] pub struct OpcodeStep { /// The program counter. + #[serde(with = "super::hex_serde")] pub pc: u64, /// The opcode being executed. #[serde(serialize_with = "serialize_opcode", deserialize_with = "deserialize_opcode")] pub op: u8, /// Remaining gas before executing this opcode. + #[serde(with = "super::hex_serde")] pub gas: u64, /// Cost of executing this opcode. + #[serde(with = "super::hex_serde")] pub gas_cost: u64, /// Current call depth. pub depth: u32, @@ -501,340 +542,245 @@ pub struct OpcodeStep { pub error: Option, } -/// Get opcode name from byte value using REVM opcode names -fn get_opcode_name(opcode: u8) -> &'static str { - use revm::bytecode::opcode::*; - - macro_rules! opcode_match { - ($($op:ident),*) => { - match opcode { - $( - $op => stringify!($op), - )* - _ => "INVALID", - } - }; - } - - opcode_match!( - STOP, - ADD, - MUL, - SUB, - DIV, - SDIV, - MOD, - SMOD, - ADDMOD, - MULMOD, - EXP, - SIGNEXTEND, - LT, - GT, - SLT, - SGT, - EQ, - ISZERO, - AND, - OR, - XOR, - NOT, - BYTE, - SHL, - SHR, - SAR, - KECCAK256, - ADDRESS, - BALANCE, - ORIGIN, - CALLER, - CALLVALUE, - CALLDATALOAD, - CALLDATASIZE, - CALLDATACOPY, - CODESIZE, - CODECOPY, - GASPRICE, - EXTCODESIZE, - EXTCODECOPY, - RETURNDATASIZE, - RETURNDATACOPY, - EXTCODEHASH, - BLOCKHASH, - COINBASE, - TIMESTAMP, - NUMBER, - DIFFICULTY, - GASLIMIT, - CHAINID, - SELFBALANCE, - BASEFEE, - BLOBHASH, - BLOBBASEFEE, - POP, - MLOAD, - MSTORE, - MSTORE8, - SLOAD, - SSTORE, - JUMP, - JUMPI, - PC, - MSIZE, - GAS, - JUMPDEST, - TLOAD, - TSTORE, - MCOPY, - PUSH0, - PUSH1, - PUSH2, - PUSH3, - PUSH4, - PUSH5, - PUSH6, - PUSH7, - PUSH8, - PUSH9, - PUSH10, - PUSH11, - PUSH12, - PUSH13, - PUSH14, - PUSH15, - PUSH16, - PUSH17, - PUSH18, - PUSH19, - PUSH20, - PUSH21, - PUSH22, - PUSH23, - PUSH24, - PUSH25, - PUSH26, - PUSH27, - PUSH28, - PUSH29, - PUSH30, - PUSH31, - PUSH32, - DUP1, - DUP2, - DUP3, - DUP4, - DUP5, - DUP6, - DUP7, - DUP8, - DUP9, - DUP10, - DUP11, - DUP12, - DUP13, - DUP14, - DUP15, - DUP16, - SWAP1, - SWAP2, - SWAP3, - SWAP4, - SWAP5, - SWAP6, - SWAP7, - SWAP8, - SWAP9, - SWAP10, - SWAP11, - SWAP12, - SWAP13, - SWAP14, - SWAP15, - SWAP16, - LOG0, - LOG1, - LOG2, - LOG3, - LOG4, - CREATE, - CALL, - CALLCODE, - RETURN, - DELEGATECALL, - CREATE2, - STATICCALL, - REVERT, - INVALID, - SELFDESTRUCT - ) -} - -/// Get opcode byte from name string -fn get_opcode_byte(name: &str) -> Option { - use revm::bytecode::opcode::*; - macro_rules! opcode_byte_match { - ($($op:ident),*) => { - match name { - $( - stringify!($op) => Some($op), - )* - _ => None, - } - }; - } - opcode_byte_match!( - STOP, - ADD, - MUL, - SUB, - DIV, - SDIV, - MOD, - SMOD, - ADDMOD, - MULMOD, - EXP, - SIGNEXTEND, - LT, - GT, - SLT, - SGT, - EQ, - ISZERO, - AND, - OR, - XOR, - NOT, - BYTE, - SHL, - SHR, - SAR, - KECCAK256, - ADDRESS, - BALANCE, - ORIGIN, - CALLER, - CALLVALUE, - CALLDATALOAD, - CALLDATASIZE, - CALLDATACOPY, - CODESIZE, - CODECOPY, - GASPRICE, - EXTCODESIZE, - EXTCODECOPY, - RETURNDATASIZE, - RETURNDATACOPY, - EXTCODEHASH, - BLOCKHASH, - COINBASE, - TIMESTAMP, - NUMBER, - DIFFICULTY, - GASLIMIT, - CHAINID, - SELFBALANCE, - BASEFEE, - BLOBHASH, - BLOBBASEFEE, - POP, - MLOAD, - MSTORE, - MSTORE8, - SLOAD, - SSTORE, - JUMP, - JUMPI, - PC, - MSIZE, - GAS, - JUMPDEST, - TLOAD, - TSTORE, - MCOPY, - PUSH0, - PUSH1, - PUSH2, - PUSH3, - PUSH4, - PUSH5, - PUSH6, - PUSH7, - PUSH8, - PUSH9, - PUSH10, - PUSH11, - PUSH12, - PUSH13, - PUSH14, - PUSH15, - PUSH16, - PUSH17, - PUSH18, - PUSH19, - PUSH20, - PUSH21, - PUSH22, - PUSH23, - PUSH24, - PUSH25, - PUSH26, - PUSH27, - PUSH28, - PUSH29, - PUSH30, - PUSH31, - PUSH32, - DUP1, - DUP2, - DUP3, - DUP4, - DUP5, - DUP6, - DUP7, - DUP8, - DUP9, - DUP10, - DUP11, - DUP12, - DUP13, - DUP14, - DUP15, - DUP16, - SWAP1, - SWAP2, - SWAP3, - SWAP4, - SWAP5, - SWAP6, - SWAP7, - SWAP8, - SWAP9, - SWAP10, - SWAP11, - SWAP12, - SWAP13, - SWAP14, - SWAP15, - SWAP16, - LOG0, - LOG1, - LOG2, - LOG3, - LOG4, - CREATE, - CALL, - CALLCODE, - RETURN, - DELEGATECALL, - CREATE2, - STATICCALL, - REVERT, - INVALID, - SELFDESTRUCT - ) +/// Configuration for the syscall tracer. +#[derive(TypeInfo, Encode, Decode, Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +#[serde(rename_all = "camelCase", default)] +pub struct SyscallTracerConfig { + /// Whether to enable return data capture + pub enable_return_data: bool, + + /// Limit number of steps captured + #[serde(skip_serializing_if = "Option::is_none", deserialize_with = "zero_to_none")] + pub limit: Option, +} + +impl Default for SyscallTracerConfig { + fn default() -> Self { + Self { enable_return_data: false, limit: None } + } } +/// Full syscall execution trace. +#[derive( + Default, TypeInfo, Encode, Decode, Serialize, Deserialize, Clone, Debug, Eq, PartialEq, +)] +#[serde(rename_all = "camelCase")] +pub struct SyscallTrace { + /// Total gas used by the transaction. + #[serde(with = "super::hex_serde")] + pub gas: u64, + /// Whether the transaction failed. + pub failed: bool, + /// The return value of the transaction. + pub return_value: Bytes, + /// The list of syscall execution steps. + pub struct_logs: Vec, +} + +/// A single syscall execution step. +#[derive(TypeInfo, Encode, Decode, Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct SyscallStep { + /// The syscall name. + pub ecall: String, + /// Remaining gas before executing this syscall. + #[serde(with = "super::hex_serde")] + pub gas: u64, + /// Weight before executing this syscall. + pub weight: Weight, + /// Cost of executing this syscall. + #[serde(with = "super::hex_serde")] + pub gas_cost: u64, + /// Weight consumed by executing this syscall (ref_time in nanoseconds). + pub weight_cost: Weight, + /// Current call depth. + pub depth: u32, + /// Return data from last frame output. + #[serde(skip_serializing_if = "Bytes::is_empty")] + pub return_data: Bytes, + /// Any error that occurred during syscall execution. + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, +} + +macro_rules! define_opcode_functions { + ($($op:ident),* $(,)?) => { + /// Get opcode name from byte value using REVM opcode names + fn get_opcode_name(opcode: u8) -> &'static str { + use revm::bytecode::opcode::*; + match opcode { + $( + $op => stringify!($op), + )* + _ => "INVALID", + } + } + + /// Get opcode byte from name string + fn get_opcode_byte(name: &str) -> Option { + use revm::bytecode::opcode::*; + match name { + $( + stringify!($op) => Some($op), + )* + _ => None, + } + } + }; +} + +define_opcode_functions!( + STOP, + ADD, + MUL, + SUB, + DIV, + SDIV, + MOD, + SMOD, + ADDMOD, + MULMOD, + EXP, + SIGNEXTEND, + LT, + GT, + SLT, + SGT, + EQ, + ISZERO, + AND, + OR, + XOR, + NOT, + BYTE, + SHL, + SHR, + SAR, + KECCAK256, + ADDRESS, + BALANCE, + ORIGIN, + CALLER, + CALLVALUE, + CALLDATALOAD, + CALLDATASIZE, + CALLDATACOPY, + CODESIZE, + CODECOPY, + GASPRICE, + EXTCODESIZE, + EXTCODECOPY, + RETURNDATASIZE, + RETURNDATACOPY, + EXTCODEHASH, + BLOCKHASH, + COINBASE, + TIMESTAMP, + NUMBER, + DIFFICULTY, + GASLIMIT, + CHAINID, + SELFBALANCE, + BASEFEE, + BLOBHASH, + BLOBBASEFEE, + POP, + MLOAD, + MSTORE, + MSTORE8, + SLOAD, + SSTORE, + JUMP, + JUMPI, + PC, + MSIZE, + GAS, + JUMPDEST, + TLOAD, + TSTORE, + MCOPY, + PUSH0, + PUSH1, + PUSH2, + PUSH3, + PUSH4, + PUSH5, + PUSH6, + PUSH7, + PUSH8, + PUSH9, + PUSH10, + PUSH11, + PUSH12, + PUSH13, + PUSH14, + PUSH15, + PUSH16, + PUSH17, + PUSH18, + PUSH19, + PUSH20, + PUSH21, + PUSH22, + PUSH23, + PUSH24, + PUSH25, + PUSH26, + PUSH27, + PUSH28, + PUSH29, + PUSH30, + PUSH31, + PUSH32, + DUP1, + DUP2, + DUP3, + DUP4, + DUP5, + DUP6, + DUP7, + DUP8, + DUP9, + DUP10, + DUP11, + DUP12, + DUP13, + DUP14, + DUP15, + DUP16, + SWAP1, + SWAP2, + SWAP3, + SWAP4, + SWAP5, + SWAP6, + SWAP7, + SWAP8, + SWAP9, + SWAP10, + SWAP11, + SWAP12, + SWAP13, + SWAP14, + SWAP15, + SWAP16, + LOG0, + LOG1, + LOG2, + LOG3, + LOG4, + CREATE, + CALL, + CALLCODE, + RETURN, + DELEGATECALL, + CREATE2, + STATICCALL, + REVERT, + INVALID, + SELFDESTRUCT, +); + /// Serialize opcode as string using REVM opcode names fn serialize_opcode(opcode: &u8, serializer: S) -> Result where @@ -859,13 +805,15 @@ where TypeInfo, Default, Encode, Decode, Serialize, Deserialize, Clone, Debug, Eq, PartialEq, )] #[serde(rename_all = "camelCase")] -pub struct CallTrace { +pub struct CallTrace { /// Address of the sender. pub from: H160, /// Amount of gas provided for the call. - pub gas: Gas, + #[serde(with = "super::hex_serde")] + pub gas: u64, /// Amount of gas used. - pub gas_used: Gas, + #[serde(with = "super::hex_serde")] + pub gas_used: u64, /// Address of the receiver. pub to: H160, /// Call input data. @@ -881,7 +829,7 @@ pub struct CallTrace { pub revert_reason: Option, /// List of sub-calls. #[serde(skip_serializing_if = "Vec::is_empty")] - pub calls: Vec>, + pub calls: Vec, /// List of logs emitted during the call. #[serde(skip_serializing_if = "Vec::is_empty")] pub logs: Vec, @@ -962,3 +910,74 @@ where }, } } + +#[test] +fn test_gas_fields_serialize_as_hex() { + // Test CallTrace gas serialization + let call_trace = CallTrace { + from: H160::zero(), + gas: 21000, + gas_used: 20000, + to: H160::zero(), + input: Bytes::default(), + output: Bytes::default(), + error: None, + revert_reason: None, + calls: Vec::new(), + logs: Vec::new(), + value: None, + call_type: CallType::Call, + child_call_count: 0, + }; + let json = serde_json::to_string(&call_trace).expect("Serialization should succeed"); + assert!(json.contains(r#""gas":"0x5208""#), "gas should be hex: {}", json); + assert!(json.contains(r#""gasUsed":"0x4e20""#), "gas_used should be hex: {}", json); + + // Test OpcodeTrace gas serialization + let opcode_trace = OpcodeTrace { gas: 100000, failed: false, return_value: Bytes::default(), struct_logs: Vec::new() }; + let json = serde_json::to_string(&opcode_trace).expect("Serialization should succeed"); + assert!(json.contains(r#""gas":"0x186a0""#), "opcode trace gas should be hex: {}", json); + + // Test OpcodeStep gas serialization + let opcode_step = OpcodeStep { + pc: 42, + op: 0x01, + gas: 50000, + gas_cost: 3, + depth: 1, + stack: Vec::new(), + memory: Vec::new(), + storage: None, + return_data: Bytes::default(), + error: None, + }; + let json = serde_json::to_string(&opcode_step).expect("Serialization should succeed"); + assert!(json.contains(r#""pc":"0x2a""#), "pc should be hex: {}", json); + assert!(json.contains(r#""gas":"0xc350""#), "opcode step gas should be hex: {}", json); + assert!(json.contains(r#""gasCost":"0x3""#), "gas_cost should be hex: {}", json); + + // Test SyscallTrace gas serialization + let syscall_trace = SyscallTrace { + gas: 75000, + failed: false, + return_value: Bytes::default(), + struct_logs: Vec::new(), + }; + let json = serde_json::to_string(&syscall_trace).expect("Serialization should succeed"); + assert!(json.contains(r#""gas":"0x124f8""#), "syscall trace gas should be hex: {}", json); + + // Test SyscallStep gas serialization + let syscall_step = SyscallStep { + ecall: "test".into(), + gas: 60000, + weight: Weight::from_parts(1000, 100), + gas_cost: 5, + weight_cost: Weight::from_parts(500, 50), + depth: 1, + return_data: Bytes::default(), + error: None, + }; + let json = serde_json::to_string(&syscall_step).expect("Serialization should succeed"); + assert!(json.contains(r#""gas":"0xea60""#), "syscall step gas should be hex: {}", json); + assert!(json.contains(r#""gasCost":"0x5""#), "syscall step gas_cost should be hex: {}", json); +} diff --git a/substrate/frame/revive/src/evm/api/hex_serde.rs b/substrate/frame/revive/src/evm/api/hex_serde.rs index 250a38496a63f..e1de2a4cb94af 100644 --- a/substrate/frame/revive/src/evm/api/hex_serde.rs +++ b/substrate/frame/revive/src/evm/api/hex_serde.rs @@ -41,7 +41,7 @@ macro_rules! impl_hex_codec { }; } -impl_hex_codec!(u8, u32); +impl_hex_codec!(u8, u32, u64); impl HexCodec for [u8; T] { type Error = hex::FromHexError; diff --git a/substrate/frame/revive/src/evm/tracing.rs b/substrate/frame/revive/src/evm/tracing.rs index 98ce4c88db275..8f0df460cc828 100644 --- a/substrate/frame/revive/src/evm/tracing.rs +++ b/substrate/frame/revive/src/evm/tracing.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. use crate::{ - evm::{CallTrace, OpcodeTrace, Trace}, + evm::{CallTrace, OpcodeTrace, SyscallTrace, Trace}, tracing::Tracing, Config, }; @@ -29,6 +29,9 @@ pub use prestate_tracing::*; mod opcode_tracing; pub use opcode_tracing::*; +mod syscall_tracing; +pub use syscall_tracing::*; + /// A composite tracer. #[derive(derive_more::From, Debug)] pub enum Tracer { @@ -38,6 +41,8 @@ pub enum Tracer { PrestateTracer(PrestateTracer), /// A tracer that traces opcodes. OpcodeTracer(OpcodeTracer), + /// A tracer that traces syscalls. + SyscallTracer(SyscallTracer), } impl Tracer @@ -50,6 +55,7 @@ where Tracer::CallTracer(_) => CallTrace::default().into(), Tracer::PrestateTracer(tracer) => tracer.empty_trace().into(), Tracer::OpcodeTracer(_) => OpcodeTrace::default().into(), + Tracer::SyscallTracer(_) => SyscallTrace::default().into(), } } @@ -59,6 +65,7 @@ where Tracer::CallTracer(inner) => inner as &mut dyn Tracing, Tracer::PrestateTracer(inner) => inner as &mut dyn Tracing, Tracer::OpcodeTracer(inner) => inner as &mut dyn Tracing, + Tracer::SyscallTracer(inner) => inner as &mut dyn Tracing, } } @@ -68,6 +75,7 @@ where Tracer::CallTracer(inner) => inner.collect_trace().map(Trace::Call), Tracer::PrestateTracer(inner) => Some(inner.collect_trace().into()), Tracer::OpcodeTracer(inner) => Some(inner.collect_trace().into()), + Tracer::SyscallTracer(inner) => Some(inner.collect_trace().into()), } } diff --git a/substrate/frame/revive/src/evm/tracing/call_tracing.rs b/substrate/frame/revive/src/evm/tracing/call_tracing.rs index 7452fe8d1d0d5..4e4dde0f41beb 100644 --- a/substrate/frame/revive/src/evm/tracing/call_tracing.rs +++ b/substrate/frame/revive/src/evm/tracing/call_tracing.rs @@ -27,7 +27,7 @@ use sp_core::{H160, H256, U256}; #[derive(Default, Debug, Clone, PartialEq)] pub struct CallTracer { /// Store all in-progress CallTrace instances. - traces: Vec>, + traces: Vec, /// Stack of indices to the current active traces. current_stack: Vec, /// The code and salt used to instantiate the next contract. @@ -57,7 +57,7 @@ impl Tracing for CallTracer { &mut self, contract_address: H160, beneficiary_address: H160, - gas_left: U256, + gas_left: u64, value: U256, ) { self.traces.last_mut().unwrap().calls.push(CallTrace { @@ -78,7 +78,7 @@ impl Tracing for CallTracer { is_read_only: bool, value: U256, input: &[u8], - gas_limit: U256, + gas_limit: u64, ) { // Increment parent's child call count. if let Some(&index) = self.current_stack.last() { @@ -151,7 +151,7 @@ impl Tracing for CallTracer { } } - fn exit_child_span(&mut self, output: &ExecReturnValue, gas_used: U256) { + fn exit_child_span(&mut self, output: &ExecReturnValue, gas_used: u64) { self.code_with_salt = None; // Set the output of the current trace @@ -159,7 +159,7 @@ impl Tracing for CallTracer { if let Some(trace) = self.traces.get_mut(current_index) { trace.output = output.data.clone().into(); - trace.gas_used = gas_used; + trace.gas_used = gas_used.into(); if output.did_revert() { trace.revert_reason = decode_revert_reason(&output.data); @@ -177,7 +177,7 @@ impl Tracing for CallTracer { } } } - fn exit_child_span_with_error(&mut self, error: DispatchError, gas_used: U256) { + fn exit_child_span_with_error(&mut self, error: DispatchError, gas_used: u64) { self.code_with_salt = None; // Set the output of the current trace diff --git a/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs b/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs index 52a1bc3b8ae2f..f1fac7fa415b2 100644 --- a/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs +++ b/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs @@ -100,7 +100,7 @@ impl Tracing for OpcodeTracer { &mut self, pc: u64, opcode: u8, - gas_before: U256, + gas_before: u64, get_stack: &dyn Fn() -> Vec, get_memory: &dyn Fn(usize) -> Vec, last_frame_output: &crate::ExecReturnValue, @@ -130,7 +130,7 @@ impl Tracing for OpcodeTracer { let step = OpcodeStep { pc, op: opcode, - gas: gas_before.try_into().unwrap_or(u64::MAX), + gas: gas_before, gas_cost: 0u64, // Will be set in exit_opcode depth: self.depth, stack: stack_data, @@ -141,15 +141,14 @@ impl Tracing for OpcodeTracer { }; self.pending_step = Some(step); - self.pending_gas_before = Some(gas_before.try_into().unwrap_or(u64::MAX)); + self.pending_gas_before = Some(gas_before); self.step_count += 1; } - fn exit_opcode(&mut self, gas_left: U256) { + fn exit_opcode(&mut self, gas_left: u64) { if let Some(mut step) = self.pending_step.take() { if let Some(gas_before) = self.pending_gas_before.take() { - let gas_cost = gas_before.saturating_sub(gas_left.try_into().unwrap_or(u64::MAX)); - step.gas_cost = gas_cost.try_into().unwrap_or(u64::MAX); + step.gas_cost = gas_before.saturating_sub(gas_left); } self.steps.push(step); } @@ -163,13 +162,13 @@ impl Tracing for OpcodeTracer { _is_read_only: bool, _value: U256, _input: &[u8], - _gas_limit: U256, + _gas_limit: u64, ) { self.storages_per_call.push(Default::default()); self.depth += 1; } - fn exit_child_span(&mut self, output: &ExecReturnValue, gas_used: U256) { + fn exit_child_span(&mut self, output: &ExecReturnValue, gas_used: u64) { if output.did_revert() { self.record_error("execution reverted".to_string()); if self.depth == 0 { @@ -181,7 +180,7 @@ impl Tracing for OpcodeTracer { // Set total gas used if this is the top-level call (depth 1, will become 0 after decrement) if self.depth == 1 { - self.total_gas_used = gas_used.try_into().unwrap_or(u64::MAX); + self.total_gas_used = gas_used; } self.storages_per_call.pop(); @@ -191,13 +190,13 @@ impl Tracing for OpcodeTracer { } } - fn exit_child_span_with_error(&mut self, error: DispatchError, gas_used: U256) { + fn exit_child_span_with_error(&mut self, error: DispatchError, gas_used: u64) { self.record_error(format!("{:?}", error)); // Mark as failed if this is the top-level call if self.depth == 1 { self.failed = true; - self.total_gas_used = gas_used.try_into().unwrap_or(u64::MAX); + self.total_gas_used = gas_used; } if self.depth > 0 { diff --git a/substrate/frame/revive/src/evm/tracing/prestate_tracing.rs b/substrate/frame/revive/src/evm/tracing/prestate_tracing.rs index b1141924d6e12..82785e58c4fa9 100644 --- a/substrate/frame/revive/src/evm/tracing/prestate_tracing.rs +++ b/substrate/frame/revive/src/evm/tracing/prestate_tracing.rs @@ -241,7 +241,7 @@ where &mut self, contract_address: H160, beneficiary_address: H160, - _gas_left: U256, + _gas_left: u64, _value: U256, ) { self.destructed_addrs.insert(contract_address); @@ -258,7 +258,7 @@ where _is_read_only: bool, _value: U256, _input: &[u8], - _gas_limit: U256, + _gas_limit: u64, ) { if let Some(delegate_call) = delegate_call { self.calls.push(self.current_addr()); @@ -275,11 +275,11 @@ where } } - fn exit_child_span_with_error(&mut self, _error: crate::DispatchError, _gas_used: U256) { + fn exit_child_span_with_error(&mut self, _error: crate::DispatchError, _gas_used: u64) { self.calls.pop(); } - fn exit_child_span(&mut self, output: &ExecReturnValue, _gas_used: U256) { + fn exit_child_span(&mut self, output: &ExecReturnValue, _gas_used: u64) { let current_addr = self.calls.pop().unwrap_or_default(); if output.did_revert() { return diff --git a/substrate/frame/revive/src/evm/tracing/syscall_tracing.rs b/substrate/frame/revive/src/evm/tracing/syscall_tracing.rs new file mode 100644 index 0000000000000..dfe626e6958f0 --- /dev/null +++ b/substrate/frame/revive/src/evm/tracing/syscall_tracing.rs @@ -0,0 +1,188 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +use crate::{ + evm::{tracing::Tracing, Bytes, SyscallStep, SyscallTrace, SyscallTracerConfig}, + DispatchError, ExecReturnValue, Weight, +}; +use alloc::{ + format, + string::{String, ToString}, + vec::Vec, +}; +use sp_core::{H160, U256}; + +/// A tracer that traces syscall execution step-by-step. +#[derive(Default, Debug, Clone, PartialEq)] +pub struct SyscallTracer { + /// The tracer configuration. + config: SyscallTracerConfig, + + /// The collected trace steps. + steps: Vec, + + /// Current call depth. + depth: u32, + + /// Number of steps captured (for limiting). + step_count: u64, + + /// Total gas used by the transaction. + total_gas_used: u64, + + /// Whether the transaction failed. + failed: bool, + + /// The return value of the transaction. + return_value: Bytes, + + /// Pending step that's waiting for gas cost to be recorded. + pending_step: Option, + + /// Gas before executing the current pending step. + pending_gas_before: Option, + + /// Weight before executing the current pending step. + pending_weight_before: Option, +} + +impl SyscallTracer { + /// Create a new [`SyscallTracer`] instance. + pub fn new(config: SyscallTracerConfig) -> Self { + Self { + config, + steps: Vec::new(), + depth: 0, + step_count: 0, + total_gas_used: 0, + failed: false, + return_value: Bytes::default(), + pending_step: None, + pending_gas_before: None, + pending_weight_before: None, + } + } + + /// Collect the traces and return them. + pub fn collect_trace(self) -> SyscallTrace { + let Self { steps: struct_logs, return_value, total_gas_used: gas, failed, .. } = self; + SyscallTrace { gas, failed, return_value, struct_logs } + } + + /// Record an error in the current step. + pub fn record_error(&mut self, error: String) { + if let Some(last_step) = self.steps.last_mut() { + last_step.error = Some(error); + } + } +} + +impl Tracing for SyscallTracer { + fn enter_ecall( + &mut self, + ecall: &'static str, + gas_before: u64, + weight_before: crate::Weight, + last_frame_output: &crate::ExecReturnValue, + ) { + // Check step limit - if exceeded, don't record anything + if self.config.limit.map(|l| self.step_count >= l).unwrap_or(false) { + return; + } + + // Extract return data if enabled + let return_data = if self.config.enable_return_data { + crate::evm::Bytes(last_frame_output.data.clone()) + } else { + crate::evm::Bytes::default() + }; + + let step = SyscallStep { + ecall: ecall.to_string(), + gas: gas_before, + weight: weight_before, + gas_cost: 0u64, // Will be set in exit_ecall + weight_cost: Default::default(), // Will be set in exit_ecall + depth: self.depth, + return_data, + error: None, + }; + + self.pending_step = Some(step); + self.pending_gas_before = Some(gas_before); + self.pending_weight_before = Some(weight_before); + self.step_count += 1; + } + + fn exit_ecall(&mut self, gas_left: u64, weight_left: crate::Weight) { + if let Some(mut step) = self.pending_step.take() { + if let Some(gas_before) = self.pending_gas_before.take() { + step.gas_cost = gas_before.saturating_sub(gas_left); + } + if let Some(weight_before) = self.pending_weight_before.take() { + step.weight_cost = weight_before.saturating_sub(weight_left); + } + self.steps.push(step); + } + } + + fn enter_child_span( + &mut self, + _from: H160, + _to: H160, + _delegate_call: Option, + _is_read_only: bool, + _value: U256, + _input: &[u8], + _gas_limit: u64, + ) { + self.depth += 1; + } + + fn exit_child_span(&mut self, output: &ExecReturnValue, gas_used: u64) { + if output.did_revert() { + self.record_error("execution reverted".to_string()); + if self.depth == 0 { + self.failed = true; + } + } else { + self.return_value = Bytes(output.data.to_vec()); + } + + // Set total gas used if this is the top-level call (depth 1, will become 0 after decrement) + if self.depth == 1 { + self.total_gas_used = gas_used.try_into().unwrap_or(u64::MAX); + } + + if self.depth > 0 { + self.depth -= 1; + } + } + + fn exit_child_span_with_error(&mut self, error: DispatchError, gas_used: u64) { + self.record_error(format!("{:?}", error)); + + // Mark as failed if this is the top-level call + if self.depth == 1 { + self.failed = true; + self.total_gas_used = gas_used.try_into().unwrap_or(u64::MAX); + } + + if self.depth > 0 { + self.depth -= 1; + } + } +} diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index e0ebe1f568ed6..4c546bb8e2243 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -1239,7 +1239,12 @@ where frame.read_only, frame.value_transferred, &input_data, - frame.frame_meter.eth_gas_left().unwrap_or_default().into(), + frame + .frame_meter + .eth_gas_left() + .unwrap_or_default() + .try_into() + .unwrap_or_default(), ); }); let mock_answer = self.exec_config.mock_handler.as_ref().and_then(|handler| { @@ -1458,11 +1463,13 @@ where // we treat the initial frame meter differently to address // https://github.com/paritytech/polkadot-sdk/issues/8362 let gas_consumed = if is_first_frame { - frame_meter.total_consumed_gas().into() + frame_meter.total_consumed_gas() } else { - frame_meter.eth_gas_consumed().into() + frame_meter.eth_gas_consumed() }; + let gas_consumed: u64 = gas_consumed.try_into().unwrap_or(u64::MAX); + match &output { Ok(output) => tracer.exit_child_span(&output, gas_consumed), Err(e) => tracer.exit_child_span_with_error(e.error.into(), gas_consumed), @@ -1480,11 +1487,12 @@ where // we treat the initial frame meter differently to address // https://github.com/paritytech/polkadot-sdk/issues/8362 let gas_consumed = if is_first_frame { - frame_meter.total_consumed_gas().into() + frame_meter.total_consumed_gas() } else { - frame_meter.eth_gas_consumed().into() + frame_meter.eth_gas_consumed() }; + let gas_consumed: u64 = gas_consumed.try_into().unwrap_or(u64::MAX); tracer.exit_child_span_with_error(error.into(), gas_consumed); }); @@ -1884,7 +1892,12 @@ where tracer.terminate( addr, *beneficiary, - self.top_frame().frame_meter.eth_gas_left().unwrap_or_default().into(), + self.top_frame() + .frame_meter + .eth_gas_left() + .unwrap_or_default() + .try_into() + .unwrap_or_default(), crate::Pallet::::evm_balance(&addr), ); }); diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 18f83533d35b4..6d32901a57d52 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -50,7 +50,7 @@ use crate::{ evm::{ block_hash::EthereumBlockBuilderIR, block_storage, fees::InfoT as FeeInfo, runtime::SetWeightLimit, CallTracer, CreateCallMode, GenericTransaction, OpcodeTracer, - PrestateTracer, Trace, Tracer, TracerType, TYPE_EIP1559, + PrestateTracer, SyscallTracer, Trace, Tracer, TracerType, TYPE_EIP1559, }, exec::{AccountIdOf, ExecError, ReentrancyProtection, Stack as ExecStack}, storage::{AccountType, DeletionQueueManager}, @@ -2150,6 +2150,8 @@ impl Pallet { PrestateTracer::new(config.unwrap_or_default()).into(), TracerType::StructLogger(config) => OpcodeTracer::new(config.unwrap_or_default()).into(), + TracerType::SyscallTracer(config) => + SyscallTracer::new(config.unwrap_or_default()).into(), } } diff --git a/substrate/frame/revive/src/tests/pvm.rs b/substrate/frame/revive/src/tests/pvm.rs index abbec02a95b09..a092ddab21baf 100644 --- a/substrate/frame/revive/src/tests/pvm.rs +++ b/substrate/frame/revive/src/tests/pvm.rs @@ -3972,7 +3972,7 @@ fn call_tracing_works() { a.gas_consumed }); let gas_trace = tracer.collect_trace().unwrap(); - assert_eq!(&gas_trace.gas_used, &gas_used.into()); + assert_eq!(&gas_trace.gas_used, &gas_used); for config in tracer_configs { let logs = if config.with_logs { @@ -4009,8 +4009,8 @@ fn call_tracing_works() { error: Some("execution reverted".to_string()), call_type: Call, value: Some(U256::from(0)), - gas: 0.into(), - gas_used: 0.into(), + gas: 0, + gas_used: 0, ..Default::default() }, CallTrace { @@ -4020,8 +4020,8 @@ fn call_tracing_works() { call_type: Call, logs: logs.clone(), value: Some(U256::from(0)), - gas: 0.into(), - gas_used: 0.into(), + gas: 0, + gas_used: 0, calls: vec![ CallTrace { from: addr, @@ -4031,8 +4031,8 @@ fn call_tracing_works() { error: Some("ContractTrapped".to_string()), call_type: Call, value: Some(U256::from(0)), - gas: 0.into(), - gas_used: 0.into(), + gas: 0, + gas_used: 0, ..Default::default() }, CallTrace { @@ -4042,8 +4042,8 @@ fn call_tracing_works() { call_type: Call, logs: logs.clone(), value: Some(U256::from(0)), - gas: 0.into(), - gas_used: 0.into(), + gas: 0, + gas_used: 0, calls: vec![ CallTrace { from: addr, @@ -4052,8 +4052,8 @@ fn call_tracing_works() { output: 0u32.to_le_bytes().to_vec().into(), call_type: Call, value: Some(U256::from(0)), - gas: 0.into(), - gas_used: 0.into(), + gas: 0, + gas_used: 0, ..Default::default() }, CallTrace { @@ -4062,8 +4062,8 @@ fn call_tracing_works() { input: (0u32, addr_callee).encode().into(), call_type: Call, value: Some(U256::from(0)), - gas: 0.into(), - gas_used: 0.into(), + gas: 0, + gas_used: 0, calls: vec![ CallTrace { from: addr, @@ -4102,8 +4102,8 @@ fn call_tracing_works() { value: Some(U256::from(0)), calls: calls, child_call_count: 2, - gas: 0.into(), - gas_used: 0.into(), + gas: 0, + gas_used: 0, ..Default::default() }; @@ -5253,12 +5253,12 @@ fn self_destruct_by_syscall_tracing_works() { to: addr, call_type: CallType::Call, value: Some(U256::zero()), - gas: 0.into(), - gas_used: 0.into(), + gas: 0, + gas_used: 0, calls: vec![CallTrace { from: addr, to: DJANGO_ADDR, - gas: 0.into(), + gas: 0, call_type: CallType::Selfdestruct, value: Some(Pallet::::convert_native_to_evm(100_000u64)), @@ -5269,9 +5269,9 @@ fn self_destruct_by_syscall_tracing_works() { }), modify_trace_fn: Some(Box::new(|mut actual_trace| { if let Trace::Call(trace) = &mut actual_trace { - trace.gas = 0.into(); - trace.gas_used = 0.into(); - trace.calls[0].gas = 0.into(); + trace.gas = 0; + trace.gas_used = 0; + trace.calls[0].gas = 0; } actual_trace })), @@ -5411,6 +5411,7 @@ fn self_destruct_by_syscall_tracing_works() { crate::evm::Trace::Call(ct) => Trace::Call(ct), crate::evm::Trace::Prestate(pt) => Trace::Prestate(pt), crate::evm::Trace::Opcode(_) => panic!("Opcode trace not expected"), + crate::evm::Trace::Syscall(_) => panic!("Syscall trace not expected"), }; assert_eq!(trace_wrapped, expected_trace, "Trace mismatch for: {}", description); diff --git a/substrate/frame/revive/src/tests/sol.rs b/substrate/frame/revive/src/tests/sol.rs index 9e67c86b45a0b..45b5538cba222 100644 --- a/substrate/frame/revive/src/tests/sol.rs +++ b/substrate/frame/revive/src/tests/sol.rs @@ -607,7 +607,6 @@ fn opcode_tracing_works() { let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); - // Test with a specific configuration and verify exact structure let config = OpcodeTracerConfig { enable_memory: false, disable_stack: false, @@ -630,11 +629,10 @@ fn opcode_tracing_works() { step.gas_cost = 0u64; }); - // Create expected trace structure that matches the exact execution let expected_trace = OpcodeTrace { - gas: actual_trace.gas, // Use actual gas since it varies + gas: actual_trace.gas, failed: false, - return_value: crate::evm::Bytes(U256::from(2).to_big_endian().to_vec()), /* fib(3) = 2 */ + return_value: crate::evm::Bytes(U256::from(2).to_big_endian().to_vec()), struct_logs: vec![ OpcodeStep { pc: 0, @@ -702,6 +700,98 @@ fn opcode_tracing_works() { ], }; + assert_eq!(actual_trace, expected_trace); + }); +} + +#[test] +fn syscall_tracing_works() { + use crate::{ + evm::{SyscallStep, SyscallTrace, SyscallTracer, SyscallTracerConfig}, + tracing::trace, + }; + use sp_core::U256; + let (code, _) = compile_module_with_type("Fibonacci", FixtureType::Resolc).unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000); + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + let config = SyscallTracerConfig { enable_return_data: true, limit: Some(5) }; + + let mut tracer = SyscallTracer::new(config); + let _result = trace(&mut tracer, || { + builder::bare_call(addr) + .data(Fibonacci::FibonacciCalls::fib(Fibonacci::fibCall { n: 3u64 }).abi_encode()) + .build_and_unwrap_result() + }); + + let mut actual_trace = tracer.collect_trace(); + actual_trace.struct_logs.iter_mut().for_each(|step| { + step.gas = 0u64; + step.gas_cost = 0u64; + step.weight = Weight::default(); + step.weight_cost = Weight::default(); + }); + + let expected_trace = SyscallTrace { + gas: actual_trace.gas, + failed: false, + return_value: crate::evm::Bytes(U256::from(2).to_big_endian().to_vec()), + struct_logs: vec![ + SyscallStep { + ecall: "call_data_size".to_string(), + gas: 0u64, + gas_cost: 0u64, + weight: Weight::default(), + weight_cost: Weight::default(), + depth: 1, + return_data: crate::evm::Bytes::default(), + error: None, + }, + SyscallStep { + ecall: "call_data_load".to_string(), + gas: 0u64, + gas_cost: 0u64, + weight: Weight::default(), + weight_cost: Weight::default(), + depth: 1, + return_data: crate::evm::Bytes::default(), + error: None, + }, + SyscallStep { + ecall: "value_transferred".to_string(), + gas: 0u64, + gas_cost: 0u64, + weight: Weight::default(), + weight_cost: Weight::default(), + depth: 1, + return_data: crate::evm::Bytes::default(), + error: None, + }, + SyscallStep { + ecall: "call_data_load".to_string(), + gas: 0u64, + gas_cost: 0u64, + weight: Weight::default(), + weight_cost: Weight::default(), + depth: 1, + return_data: crate::evm::Bytes::default(), + error: None, + }, + SyscallStep { + ecall: "seal_return".to_string(), + gas: 0u64, + gas_cost: 0u64, + weight: Weight::default(), + weight_cost: Weight::default(), + depth: 1, + return_data: crate::evm::Bytes::default(), + error: None, + }, + ], + }; + // Single assertion that verifies the complete trace structure matches exactly assert_eq!(actual_trace, expected_trace); }); diff --git a/substrate/frame/revive/src/tracing.rs b/substrate/frame/revive/src/tracing.rs index a820e6a4351dc..4b56a1a8a89df 100644 --- a/substrate/frame/revive/src/tracing.rs +++ b/substrate/frame/revive/src/tracing.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{primitives::ExecReturnValue, Code, DispatchError, Key}; +use crate::{primitives::ExecReturnValue, Code, DispatchError, Key, Weight}; use alloc::vec::Vec; use environmental::environmental; use sp_core::{H160, H256, U256}; @@ -54,7 +54,7 @@ pub trait Tracing { _is_read_only: bool, _value: U256, _input: &[u8], - _gas_limit: U256, + _gas_limit: u64, ) { } @@ -63,7 +63,7 @@ pub trait Tracing { &mut self, _contract_address: H160, _beneficiary_address: H160, - _gas_left: U256, + _gas_left: u64, _value: U256, ) { } @@ -90,10 +90,10 @@ pub trait Tracing { fn log_event(&mut self, _event: H160, _topics: &[H256], _data: &[u8]) {} /// Called after a contract call is executed - fn exit_child_span(&mut self, _output: &ExecReturnValue, _gas_used: U256) {} + fn exit_child_span(&mut self, _output: &ExecReturnValue, _gas_used: u64) {} /// Called when a contract call terminates with an error - fn exit_child_span_with_error(&mut self, _error: DispatchError, _gas_used: U256) {} + fn exit_child_span_with_error(&mut self, _error: DispatchError, _gas_used: u64) {} /// Check if opcode tracing is enabled. fn is_opcode_tracing_enabled(&self) -> bool { @@ -105,7 +105,7 @@ pub trait Tracing { &mut self, _pc: u64, _opcode: u8, - _gas_before: U256, + _gas_before: u64, _get_stack: &dyn Fn() -> Vec, _get_memory: &dyn Fn(usize) -> Vec, _last_frame_output: &crate::ExecReturnValue, @@ -113,5 +113,18 @@ pub trait Tracing { } /// Called after an opcode is executed to record the gas cost. - fn exit_opcode(&mut self, _gas_left: U256) {} + fn exit_opcode(&mut self, _gas_left: u64) {} + + /// Called before an ecall is executed. + fn enter_ecall( + &mut self, + _ecall: &'static str, + _gas_before: u64, + _weight_before: Weight, + _last_frame_output: &crate::ExecReturnValue, + ) { + } + + /// Called after an ecall is executed to record the gas cost and weight consumed. + fn exit_ecall(&mut self, _gas_left: u64, _weight_consumed: Weight) {} } diff --git a/substrate/frame/revive/src/vm/evm.rs b/substrate/frame/revive/src/vm/evm.rs index 56cd011a92caf..7ce0b97e9359d 100644 --- a/substrate/frame/revive/src/vm/evm.rs +++ b/substrate/frame/revive/src/vm/evm.rs @@ -160,7 +160,7 @@ fn run_plain_with_tracing( tracer.enter_opcode( interpreter.bytecode.pc() as u64, opcode, - gas_before.into(), + gas_before, &interpreter.stack.bytes_getter(), &interpreter.memory.bytes_getter(), interpreter.ext.last_frame_output(), From 3cdb2c4b7716dda476ad8a612afde72cc05016b4 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Mon, 15 Dec 2025 21:23:39 +0100 Subject: [PATCH 43/76] wip --- substrate/frame/revive/src/evm/api/debug_rpc_types.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs index e932310402d33..bfaedb8c4ec5d 100644 --- a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs +++ b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs @@ -934,7 +934,12 @@ fn test_gas_fields_serialize_as_hex() { assert!(json.contains(r#""gasUsed":"0x4e20""#), "gas_used should be hex: {}", json); // Test OpcodeTrace gas serialization - let opcode_trace = OpcodeTrace { gas: 100000, failed: false, return_value: Bytes::default(), struct_logs: Vec::new() }; + let opcode_trace = OpcodeTrace { + gas: 100000, + failed: false, + return_value: Bytes::default(), + struct_logs: Vec::new(), + }; let json = serde_json::to_string(&opcode_trace).expect("Serialization should succeed"); assert!(json.contains(r#""gas":"0x186a0""#), "opcode trace gas should be hex: {}", json); From ed709255748612767c0476ef17514063783a6830 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Mon, 15 Dec 2025 21:23:46 +0100 Subject: [PATCH 44/76] wip --- .../revive/src/evm/tracing/syscall_tracing.rs | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/substrate/frame/revive/src/evm/tracing/syscall_tracing.rs b/substrate/frame/revive/src/evm/tracing/syscall_tracing.rs index dfe626e6958f0..75457d2e5bad6 100644 --- a/substrate/frame/revive/src/evm/tracing/syscall_tracing.rs +++ b/substrate/frame/revive/src/evm/tracing/syscall_tracing.rs @@ -139,6 +139,54 @@ impl Tracing for SyscallTracer { } } + fn enter_opcode( + &mut self, + _pc: u64, + _opcode: u8, + gas_before: u64, + _get_stack: &dyn Fn() -> Vec, + _get_memory: &dyn Fn(usize) -> Vec, + last_frame_output: &crate::ExecReturnValue, + ) { + // Check step limit - if exceeded, don't record anything + if self.config.limit.map(|l| self.step_count >= l).unwrap_or(false) { + return; + } + + // Extract return data if enabled + let return_data = if self.config.enable_return_data { + crate::evm::Bytes(last_frame_output.data.clone()) + } else { + crate::evm::Bytes::default() + }; + + // TODO fix + // let step = SyscallStep { + // ecall: ecall.to_string(), + // gas: gas_before, + // weight: weight_before, + // gas_cost: 0u64, // Will be set in exit_ecall + // weight_cost: Default::default(), // Will be set in exit_ecall + // depth: self.depth, + // return_data, + // error: None, + // }; + + // self.pending_step = Some(step); + // self.pending_gas_before = Some(gas_before); + // self.pending_weight_before = Some(weight_before); + // self.step_count += 1; + } + + fn exit_opcode(&mut self, gas_left: u64) { + if let Some(mut step) = self.pending_step.take() { + if let Some(gas_before) = self.pending_gas_before.take() { + step.gas_cost = gas_before.saturating_sub(gas_left); + } + self.steps.push(step); + } + } + fn enter_child_span( &mut self, _from: H160, From ab335f9626061126d72ff06d5be26c79eb5432c0 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Tue, 16 Dec 2025 00:39:04 +0100 Subject: [PATCH 45/76] wip --- substrate/frame/revive/proc-macro/src/lib.rs | 25 +++++++ .../revive/src/evm/api/debug_rpc_types.rs | 45 ++++++++++-- .../revive/src/evm/tracing/opcode_tracing.rs | 10 +-- .../revive/src/evm/tracing/syscall_tracing.rs | 70 +++++++------------ substrate/frame/revive/src/tracing.rs | 1 + substrate/frame/revive/src/vm/evm.rs | 5 +- 6 files changed, 96 insertions(+), 60 deletions(-) diff --git a/substrate/frame/revive/proc-macro/src/lib.rs b/substrate/frame/revive/proc-macro/src/lib.rs index e88298c3c7e46..950ec2a844965 100644 --- a/substrate/frame/revive/proc-macro/src/lib.rs +++ b/substrate/frame/revive/proc-macro/src/lib.rs @@ -337,8 +337,13 @@ fn expand_env(def: &EnvDef) -> TokenStream2 { let docs = expand_func_doc(def); let stable_syscalls = expand_func_list(def, false); let all_syscalls = expand_func_list(def, true); + let lookup_syscall = expand_func_lookup(def); quote! { + pub fn all_syscalls() -> &'static [&'static [u8]] { + #all_syscalls + } + pub fn list_syscalls(include_unstable: bool) -> &'static [&'static [u8]] { if include_unstable { #all_syscalls @@ -347,6 +352,10 @@ fn expand_env(def: &EnvDef) -> TokenStream2 { } } + pub fn lookup_syscall_index(name: &'static str) -> Option { + #lookup_syscall + } + impl<'a, E: Ext, M: PolkaVmInstance> Runtime<'a, E, M> { fn handle_ecall( &mut self, @@ -572,3 +581,19 @@ fn expand_func_list(def: &EnvDef, include_unstable: bool) -> TokenStream2 { } } } + +fn expand_func_lookup(def: &EnvDef) -> TokenStream2 { + let arms = def.host_funcs.iter().enumerate().map(|(idx, f)| { + let name_str = &f.name; + quote! { + #name_str => Some(#idx as u32) + } + }); + + quote! { + match name { + #( #arms, )* + _ => None, + } + } +} diff --git a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs index bfaedb8c4ec5d..5b3ce22a0f95d 100644 --- a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs +++ b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs @@ -515,10 +515,8 @@ pub struct OpcodeStep { #[serde(serialize_with = "serialize_opcode", deserialize_with = "deserialize_opcode")] pub op: u8, /// Remaining gas before executing this opcode. - #[serde(with = "super::hex_serde")] pub gas: u64, /// Cost of executing this opcode. - #[serde(with = "super::hex_serde")] pub gas_cost: u64, /// Current call depth. pub depth: u32, @@ -577,19 +575,28 @@ pub struct SyscallTrace { pub struct_logs: Vec, } +/// An EVM opcode or PVM syscall. +#[derive(TypeInfo, Encode, Decode, Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +#[serde(untagged)] +pub enum Op { + /// EVM opcode referenced by its byte value. + EVMOpcode(u8), + /// A PVM syscall referenced by its index. + PvmSyscall(u32), +} + /// A single syscall execution step. #[derive(TypeInfo, Encode, Decode, Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] #[serde(rename_all = "camelCase")] pub struct SyscallStep { - /// The syscall name. - pub ecall: String, + /// The executed operation (EVM opcode or PVM syscall). + #[serde(serialize_with = "serialize_op")] + pub op: Op, /// Remaining gas before executing this syscall. - #[serde(with = "super::hex_serde")] pub gas: u64, /// Weight before executing this syscall. pub weight: Weight, /// Cost of executing this syscall. - #[serde(with = "super::hex_serde")] pub gas_cost: u64, /// Weight consumed by executing this syscall (ref_time in nanoseconds). pub weight_cost: Weight, @@ -781,6 +788,17 @@ define_opcode_functions!( SELFDESTRUCT, ); +/// Serialize an operation (opcode or syscall) +fn serialize_op(op: &Op, serializer: S) -> Result +where + S: serde::Serializer, +{ + match op { + Op::EVMOpcode(byte) => serialize_opcode(byte, serializer), + Op::PvmSyscall(index) => serialize_syscall(index, serializer), + } +} + /// Serialize opcode as string using REVM opcode names fn serialize_opcode(opcode: &u8, serializer: S) -> Result where @@ -790,6 +808,19 @@ where serializer.serialize_str(name) } +/// Serialize a syscall +fn serialize_syscall(idx: &u32, serializer: S) -> Result +where + S: serde::Serializer, +{ + use crate::vm::pvm::env::all_syscalls; + let Some(syscall_name_bytes) = all_syscalls().get(*idx as usize) else { + return Err(serde::ser::Error::custom(alloc::format!("Unknown syscall: {idx}"))) + }; + let name = core::str::from_utf8(syscall_name_bytes).unwrap_or_default(); + serializer.serialize_str(name) +} + /// Deserialize opcode from string using reverse lookup table fn deserialize_opcode<'de, D>(deserializer: D) -> Result where @@ -973,7 +1004,7 @@ fn test_gas_fields_serialize_as_hex() { // Test SyscallStep gas serialization let syscall_step = SyscallStep { - ecall: "test".into(), + op: Op::PvmSyscall(0), gas: 60000, weight: Weight::from_parts(1000, 100), gas_cost: 5, diff --git a/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs b/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs index f1fac7fa415b2..eb560a6eb5f59 100644 --- a/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs +++ b/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs @@ -53,9 +53,6 @@ pub struct OpcodeTracer { /// Pending step that's waiting for gas cost to be recorded. pending_step: Option, - /// Gas before executing the current pending step. - pending_gas_before: Option, - /// List of storage per call storages_per_call: Vec>, } @@ -72,7 +69,6 @@ impl OpcodeTracer { failed: false, return_value: Bytes::default(), pending_step: None, - pending_gas_before: None, storages_per_call: alloc::vec![Default::default()], } } @@ -101,6 +97,7 @@ impl Tracing for OpcodeTracer { pc: u64, opcode: u8, gas_before: u64, + _weight_before: crate::Weight, get_stack: &dyn Fn() -> Vec, get_memory: &dyn Fn(usize) -> Vec, last_frame_output: &crate::ExecReturnValue, @@ -141,15 +138,12 @@ impl Tracing for OpcodeTracer { }; self.pending_step = Some(step); - self.pending_gas_before = Some(gas_before); self.step_count += 1; } fn exit_opcode(&mut self, gas_left: u64) { if let Some(mut step) = self.pending_step.take() { - if let Some(gas_before) = self.pending_gas_before.take() { - step.gas_cost = gas_before.saturating_sub(gas_left); - } + step.gas_cost = step.gas.saturating_sub(gas_left); self.steps.push(step); } } diff --git a/substrate/frame/revive/src/evm/tracing/syscall_tracing.rs b/substrate/frame/revive/src/evm/tracing/syscall_tracing.rs index 75457d2e5bad6..15960ab1a2caf 100644 --- a/substrate/frame/revive/src/evm/tracing/syscall_tracing.rs +++ b/substrate/frame/revive/src/evm/tracing/syscall_tracing.rs @@ -15,7 +15,8 @@ // See the License for the specific language governing permissions and // limitations under the License. use crate::{ - evm::{tracing::Tracing, Bytes, SyscallStep, SyscallTrace, SyscallTracerConfig}, + evm::{tracing::Tracing, Bytes, Op, SyscallStep, SyscallTrace, SyscallTracerConfig}, + vm::pvm::env::lookup_syscall_index, DispatchError, ExecReturnValue, Weight, }; use alloc::{ @@ -51,12 +52,6 @@ pub struct SyscallTracer { /// Pending step that's waiting for gas cost to be recorded. pending_step: Option, - - /// Gas before executing the current pending step. - pending_gas_before: Option, - - /// Weight before executing the current pending step. - pending_weight_before: Option, } impl SyscallTracer { @@ -71,8 +66,6 @@ impl SyscallTracer { failed: false, return_value: Bytes::default(), pending_step: None, - pending_gas_before: None, - pending_weight_before: None, } } @@ -91,6 +84,10 @@ impl SyscallTracer { } impl Tracing for SyscallTracer { + fn is_opcode_tracing_enabled(&self) -> bool { + true + } + fn enter_ecall( &mut self, ecall: &'static str, @@ -111,7 +108,7 @@ impl Tracing for SyscallTracer { }; let step = SyscallStep { - ecall: ecall.to_string(), + op: Op::PvmSyscall(lookup_syscall_index(ecall).unwrap_or_default()), gas: gas_before, weight: weight_before, gas_cost: 0u64, // Will be set in exit_ecall @@ -122,19 +119,13 @@ impl Tracing for SyscallTracer { }; self.pending_step = Some(step); - self.pending_gas_before = Some(gas_before); - self.pending_weight_before = Some(weight_before); self.step_count += 1; } fn exit_ecall(&mut self, gas_left: u64, weight_left: crate::Weight) { if let Some(mut step) = self.pending_step.take() { - if let Some(gas_before) = self.pending_gas_before.take() { - step.gas_cost = gas_before.saturating_sub(gas_left); - } - if let Some(weight_before) = self.pending_weight_before.take() { - step.weight_cost = weight_before.saturating_sub(weight_left); - } + step.gas_cost = step.gas.saturating_sub(gas_left); + step.weight_cost = step.weight.saturating_sub(weight_left); self.steps.push(step); } } @@ -142,11 +133,12 @@ impl Tracing for SyscallTracer { fn enter_opcode( &mut self, _pc: u64, - _opcode: u8, + opcode: u8, gas_before: u64, + weight_before: Weight, _get_stack: &dyn Fn() -> Vec, _get_memory: &dyn Fn(usize) -> Vec, - last_frame_output: &crate::ExecReturnValue, + _last_frame_output: &crate::ExecReturnValue, ) { // Check step limit - if exceeded, don't record anything if self.config.limit.map(|l| self.step_count >= l).unwrap_or(false) { @@ -155,36 +147,28 @@ impl Tracing for SyscallTracer { // Extract return data if enabled let return_data = if self.config.enable_return_data { - crate::evm::Bytes(last_frame_output.data.clone()) + crate::evm::Bytes(_last_frame_output.data.clone()) } else { crate::evm::Bytes::default() }; - // TODO fix - // let step = SyscallStep { - // ecall: ecall.to_string(), - // gas: gas_before, - // weight: weight_before, - // gas_cost: 0u64, // Will be set in exit_ecall - // weight_cost: Default::default(), // Will be set in exit_ecall - // depth: self.depth, - // return_data, - // error: None, - // }; - - // self.pending_step = Some(step); - // self.pending_gas_before = Some(gas_before); - // self.pending_weight_before = Some(weight_before); - // self.step_count += 1; + let step = SyscallStep { + op: Op::EVMOpcode(opcode), + gas: gas_before, + weight: weight_before, + gas_cost: 0u64, // Will be set in exit_ecall + weight_cost: Default::default(), // Will be set in exit_ecall + depth: self.depth, + return_data, + error: None, + }; + + self.pending_step = Some(step); + self.step_count += 1; } fn exit_opcode(&mut self, gas_left: u64) { - if let Some(mut step) = self.pending_step.take() { - if let Some(gas_before) = self.pending_gas_before.take() { - step.gas_cost = gas_before.saturating_sub(gas_left); - } - self.steps.push(step); - } + self.exit_ecall(gas_left, Default::default()); } fn enter_child_span( diff --git a/substrate/frame/revive/src/tracing.rs b/substrate/frame/revive/src/tracing.rs index 4b56a1a8a89df..8e414cfc3e4c7 100644 --- a/substrate/frame/revive/src/tracing.rs +++ b/substrate/frame/revive/src/tracing.rs @@ -106,6 +106,7 @@ pub trait Tracing { _pc: u64, _opcode: u8, _gas_before: u64, + _weight_before: Weight, _get_stack: &dyn Fn() -> Vec, _get_memory: &dyn Fn(usize) -> Vec, _last_frame_output: &crate::ExecReturnValue, diff --git a/substrate/frame/revive/src/vm/evm.rs b/substrate/frame/revive/src/vm/evm.rs index 7ce0b97e9359d..b44c49500774d 100644 --- a/substrate/frame/revive/src/vm/evm.rs +++ b/substrate/frame/revive/src/vm/evm.rs @@ -156,11 +156,12 @@ fn run_plain_with_tracing( loop { let opcode = interpreter.bytecode.opcode(); tracing::if_tracing(|tracer| { - let gas_before = interpreter.ext.gas_left(); + let meter = interpreter.ext.frame_meter(); tracer.enter_opcode( interpreter.bytecode.pc() as u64, opcode, - gas_before, + meter.eth_gas_left().unwrap_or_default().try_into().unwrap_or_default(), + meter.weight_left().unwrap_or_default(), &interpreter.stack.bytes_getter(), &interpreter.memory.bytes_getter(), interpreter.ext.last_frame_output(), From 1bc241c4c17d6b17b5435b370fb164561a00e56f Mon Sep 17 00:00:00 2001 From: pgherveou Date: Tue, 16 Dec 2025 00:39:52 +0100 Subject: [PATCH 46/76] wip --- substrate/frame/revive/src/evm/api/debug_rpc_types.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs index 5b3ce22a0f95d..6007dca10a475 100644 --- a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs +++ b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs @@ -598,7 +598,7 @@ pub struct SyscallStep { pub weight: Weight, /// Cost of executing this syscall. pub gas_cost: u64, - /// Weight consumed by executing this syscall (ref_time in nanoseconds). + /// Weight consumed by executing this syscall pub weight_cost: Weight, /// Current call depth. pub depth: u32, From 836421c372f2ccad0d9f3d3cc7b1738bec625ec2 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Tue, 16 Dec 2025 11:39:35 +0100 Subject: [PATCH 47/76] wip --- substrate/frame/revive/proc-macro/src/lib.rs | 19 +- .../revive/src/evm/api/debug_rpc_types.rs | 233 ++++++------------ substrate/frame/revive/src/evm/tracing.rs | 26 +- .../revive/src/evm/tracing/opcode_tracing.rs | 107 +++++--- .../revive/src/evm/tracing/syscall_tracing.rs | 220 ----------------- substrate/frame/revive/src/lib.rs | 8 +- substrate/frame/revive/src/tests/pvm.rs | 3 +- substrate/frame/revive/src/tests/sol.rs | 140 ++++++----- substrate/frame/revive/src/tracing.rs | 143 ++++++++--- substrate/frame/revive/src/vm/evm.rs | 19 +- .../frame/revive/src/vm/evm/interpreter.rs | 29 ++- substrate/frame/revive/src/vm/evm/memory.rs | 22 +- substrate/frame/revive/src/vm/evm/stack.rs | 18 +- substrate/frame/revive/src/vm/pvm.rs | 22 +- 14 files changed, 407 insertions(+), 602 deletions(-) delete mode 100644 substrate/frame/revive/src/evm/tracing/syscall_tracing.rs diff --git a/substrate/frame/revive/proc-macro/src/lib.rs b/substrate/frame/revive/proc-macro/src/lib.rs index 950ec2a844965..95647f1f6c095 100644 --- a/substrate/frame/revive/proc-macro/src/lib.rs +++ b/substrate/frame/revive/proc-macro/src/lib.rs @@ -431,28 +431,13 @@ fn expand_functions(def: &EnvDef) -> TokenStream2 { let trace_fmt_str = format!("{}({}) = {{:?}} weight_consumed: {{:?}}", name, params_fmt_str); quote! { - - crate::tracing::if_tracing(|tracer| { - let meter = self.ext.frame_meter(); - tracer.enter_ecall( - #name, - meter.eth_gas_left().unwrap_or_default().try_into().unwrap_or_default(), - meter.weight_left().unwrap_or_default(), - self.ext.last_frame_output(), - ); - }); + crate::tracing::if_tracing(|tracer| tracer.enter_ecall(#name, self)); // wrap body in closure to make sure the tracing is always executed let result = (|| #body)(); ::log::trace!(target: "runtime::revive::strace", #trace_fmt_str, #( #trace_fmt_args, )* result, self.ext.frame_meter().weight_consumed()); - crate::tracing::if_tracing(|tracer| { - let meter = self.ext.frame_meter(); - tracer.exit_ecall( - meter.eth_gas_left().unwrap_or_default().try_into().unwrap_or_default(), - meter.weight_left().unwrap_or_default(), - ); - }); + crate::tracing::if_tracing(|tracer| tracer.exit_step(self)); result } diff --git a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs index 6007dca10a475..5947e91cb029a 100644 --- a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs +++ b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{evm::Bytes, Weight}; +use crate::evm::Bytes; use alloc::{collections::BTreeMap, string::String, vec::Vec}; use codec::{Decode, Encode}; use derive_more::From; @@ -37,11 +37,8 @@ pub enum TracerType { /// A tracer that traces the prestate. PrestateTracer(Option), - /// A tracer that traces opcodes. + /// A tracer that traces opcodes and syscalls. StructLogger(Option), - - /// A tracer that traces syscalls. - SyscallTracer(Option), } impl From for TracerType { @@ -62,12 +59,6 @@ impl From for TracerType { } } -impl From for TracerType { - fn from(config: SyscallTracerConfig) -> Self { - TracerType::SyscallTracer(Some(config)) - } -} - impl Default for TracerType { fn default() -> Self { TracerType::CallTracer(Some(CallTracerConfig::default())) @@ -289,17 +280,6 @@ fn test_tracer_config_serialization() { timeout: None, }, ), - ( - r#"{"tracer": "syscallTracer" }"#, - TracerConfig { config: TracerType::SyscallTracer(None), timeout: None }, - ), - ( - r#"{"tracer": "syscallTracer", "tracerConfig": {}}"#, - TracerConfig { - config: TracerType::SyscallTracer(Some(SyscallTracerConfig::default())), - timeout: None, - }, - ), ]; for (json_data, expected) in tracers { @@ -338,10 +318,8 @@ pub enum Trace { Call(CallTrace), /// A prestate trace. Prestate(PrestateTrace), - /// An opcode trace. - Opcode(OpcodeTrace), - /// A syscall trace. - Syscall(SyscallTrace), + /// An execution trace (opcodes and syscalls). + Execution(ExecutionTrace), } /// A prestate Trace @@ -485,13 +463,13 @@ where ser_map.end() } -/// An opcode trace containing the step-by-step execution of EVM instructions. +/// An execution trace containing the step-by-step execution of EVM opcodes and PVM syscalls. /// This matches Geth's structLogger output format. #[derive( Default, TypeInfo, Encode, Decode, Serialize, Deserialize, Clone, Debug, Eq, PartialEq, )] #[serde(rename_all = "camelCase")] -pub struct OpcodeTrace { +pub struct ExecutionTrace { /// Total gas used by the transaction. #[serde(with = "super::hex_serde")] pub gas: u64, @@ -499,115 +477,65 @@ pub struct OpcodeTrace { pub failed: bool, /// The return value of the transaction. pub return_value: Bytes, - /// The list of opcode execution steps (structLogs in Geth). - pub struct_logs: Vec, + /// The list of execution steps (structLogs in Geth). + pub struct_logs: Vec, } -/// A single opcode execution step. -/// This matches Geth's structLog format exactly. +/// An execution step which can be either an EVM opcode or a PVM syscall. #[derive(TypeInfo, Encode, Decode, Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] #[serde(rename_all = "camelCase")] -pub struct OpcodeStep { - /// The program counter. - #[serde(with = "super::hex_serde")] - pub pc: u64, - /// The opcode being executed. - #[serde(serialize_with = "serialize_opcode", deserialize_with = "deserialize_opcode")] - pub op: u8, - /// Remaining gas before executing this opcode. +pub struct ExecutionStep { + /// Remaining gas before executing this step. pub gas: u64, - /// Cost of executing this opcode. + /// Cost of executing this step. pub gas_cost: u64, /// Current call depth. pub depth: u32, - /// EVM stack contents. - #[serde(serialize_with = "serialize_stack_minimal")] - pub stack: Vec, - /// EVM memory contents. - #[serde(skip_serializing_if = "Vec::is_empty", serialize_with = "serialize_memory_no_prefix")] - pub memory: Vec, - /// Contract storage changes. - #[serde( - skip_serializing_if = "Option::is_none", - serialize_with = "serialize_storage_no_prefix" - )] - pub storage: Option>, /// Return data from last frame output. #[serde(skip_serializing_if = "Bytes::is_empty")] pub return_data: Bytes, - /// Any error that occurred during opcode execution. + /// Any error that occurred during execution. #[serde(skip_serializing_if = "Option::is_none")] pub error: Option, + /// The kind of execution step (EVM opcode or PVM syscall). + #[serde(flatten)] + pub kind: ExecutionStepKind, } -/// Configuration for the syscall tracer. -#[derive(TypeInfo, Encode, Decode, Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] -#[serde(rename_all = "camelCase", default)] -pub struct SyscallTracerConfig { - /// Whether to enable return data capture - pub enable_return_data: bool, - - /// Limit number of steps captured - #[serde(skip_serializing_if = "Option::is_none", deserialize_with = "zero_to_none")] - pub limit: Option, -} - -impl Default for SyscallTracerConfig { - fn default() -> Self { - Self { enable_return_data: false, limit: None } - } -} - -/// Full syscall execution trace. -#[derive( - Default, TypeInfo, Encode, Decode, Serialize, Deserialize, Clone, Debug, Eq, PartialEq, -)] -#[serde(rename_all = "camelCase")] -pub struct SyscallTrace { - /// Total gas used by the transaction. - #[serde(with = "super::hex_serde")] - pub gas: u64, - /// Whether the transaction failed. - pub failed: bool, - /// The return value of the transaction. - pub return_value: Bytes, - /// The list of syscall execution steps. - pub struct_logs: Vec, -} - -/// An EVM opcode or PVM syscall. +/// The kind of execution step. #[derive(TypeInfo, Encode, Decode, Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] #[serde(untagged)] -pub enum Op { - /// EVM opcode referenced by its byte value. - EVMOpcode(u8), - /// A PVM syscall referenced by its index. - PvmSyscall(u32), -} - -/// A single syscall execution step. -#[derive(TypeInfo, Encode, Decode, Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] -#[serde(rename_all = "camelCase")] -pub struct SyscallStep { - /// The executed operation (EVM opcode or PVM syscall). - #[serde(serialize_with = "serialize_op")] - pub op: Op, - /// Remaining gas before executing this syscall. - pub gas: u64, - /// Weight before executing this syscall. - pub weight: Weight, - /// Cost of executing this syscall. - pub gas_cost: u64, - /// Weight consumed by executing this syscall - pub weight_cost: Weight, - /// Current call depth. - pub depth: u32, - /// Return data from last frame output. - #[serde(skip_serializing_if = "Bytes::is_empty")] - pub return_data: Bytes, - /// Any error that occurred during syscall execution. - #[serde(skip_serializing_if = "Option::is_none")] - pub error: Option, +pub enum ExecutionStepKind { + /// An EVM opcode execution. + EVMOpcode { + /// The program counter. + #[serde(with = "super::hex_serde")] + pc: u64, + /// The opcode being executed. + #[serde(serialize_with = "serialize_opcode", deserialize_with = "deserialize_opcode")] + op: u8, + /// EVM stack contents. + #[serde(serialize_with = "serialize_stack_minimal")] + stack: Vec, + /// EVM memory contents. + #[serde( + skip_serializing_if = "Vec::is_empty", + serialize_with = "serialize_memory_no_prefix" + )] + memory: Vec, + /// Contract storage changes. + #[serde( + skip_serializing_if = "Option::is_none", + serialize_with = "serialize_storage_no_prefix" + )] + storage: Option>, + }, + /// A PVM syscall execution. + PVMSyscall { + /// The executed syscall. + #[serde(serialize_with = "serialize_syscall_op")] + op: u32, + }, } macro_rules! define_opcode_functions { @@ -788,17 +716,6 @@ define_opcode_functions!( SELFDESTRUCT, ); -/// Serialize an operation (opcode or syscall) -fn serialize_op(op: &Op, serializer: S) -> Result -where - S: serde::Serializer, -{ - match op { - Op::EVMOpcode(byte) => serialize_opcode(byte, serializer), - Op::PvmSyscall(index) => serialize_syscall(index, serializer), - } -} - /// Serialize opcode as string using REVM opcode names fn serialize_opcode(opcode: &u8, serializer: S) -> Result where @@ -808,8 +725,8 @@ where serializer.serialize_str(name) } -/// Serialize a syscall -fn serialize_syscall(idx: &u32, serializer: S) -> Result +/// Serialize a syscall index to its name +fn serialize_syscall_op(idx: &u32, serializer: S) -> Result where S: serde::Serializer, { @@ -964,56 +881,46 @@ fn test_gas_fields_serialize_as_hex() { assert!(json.contains(r#""gas":"0x5208""#), "gas should be hex: {}", json); assert!(json.contains(r#""gasUsed":"0x4e20""#), "gas_used should be hex: {}", json); - // Test OpcodeTrace gas serialization - let opcode_trace = OpcodeTrace { + // Test ExecutionTrace gas serialization + let execution_trace = ExecutionTrace { gas: 100000, failed: false, return_value: Bytes::default(), struct_logs: Vec::new(), }; - let json = serde_json::to_string(&opcode_trace).expect("Serialization should succeed"); - assert!(json.contains(r#""gas":"0x186a0""#), "opcode trace gas should be hex: {}", json); + let json = serde_json::to_string(&execution_trace).expect("Serialization should succeed"); + assert!(json.contains(r#""gas":"0x186a0""#), "execution trace gas should be hex: {}", json); - // Test OpcodeStep gas serialization - let opcode_step = OpcodeStep { - pc: 42, - op: 0x01, + // Test ExecutionStep with EVM opcode gas serialization + let evm_execution_step = ExecutionStep { gas: 50000, gas_cost: 3, depth: 1, - stack: Vec::new(), - memory: Vec::new(), - storage: None, return_data: Bytes::default(), error: None, + kind: ExecutionStepKind::EVMOpcode { + pc: 42, + op: 0x01, + stack: Vec::new(), + memory: Vec::new(), + storage: None, + }, }; - let json = serde_json::to_string(&opcode_step).expect("Serialization should succeed"); + let json = serde_json::to_string(&evm_execution_step).expect("Serialization should succeed"); assert!(json.contains(r#""pc":"0x2a""#), "pc should be hex: {}", json); - assert!(json.contains(r#""gas":"0xc350""#), "opcode step gas should be hex: {}", json); + assert!(json.contains(r#""gas":"0xc350""#), "evm execution step gas should be hex: {}", json); assert!(json.contains(r#""gasCost":"0x3""#), "gas_cost should be hex: {}", json); - // Test SyscallTrace gas serialization - let syscall_trace = SyscallTrace { - gas: 75000, - failed: false, - return_value: Bytes::default(), - struct_logs: Vec::new(), - }; - let json = serde_json::to_string(&syscall_trace).expect("Serialization should succeed"); - assert!(json.contains(r#""gas":"0x124f8""#), "syscall trace gas should be hex: {}", json); - - // Test SyscallStep gas serialization - let syscall_step = SyscallStep { - op: Op::PvmSyscall(0), + // Test ExecutionStep with PVM syscall gas serialization + let pvm_execution_step = ExecutionStep { gas: 60000, - weight: Weight::from_parts(1000, 100), gas_cost: 5, - weight_cost: Weight::from_parts(500, 50), depth: 1, return_data: Bytes::default(), error: None, + kind: ExecutionStepKind::PVMSyscall { op: 0 }, }; - let json = serde_json::to_string(&syscall_step).expect("Serialization should succeed"); - assert!(json.contains(r#""gas":"0xea60""#), "syscall step gas should be hex: {}", json); + let json = serde_json::to_string(&pvm_execution_step).expect("Serialization should succeed"); + assert!(json.contains(r#""gas":"0xea60""#), "pvm execution step gas should be hex: {}", json); assert!(json.contains(r#""gasCost":"0x5""#), "syscall step gas_cost should be hex: {}", json); } diff --git a/substrate/frame/revive/src/evm/tracing.rs b/substrate/frame/revive/src/evm/tracing.rs index 8f0df460cc828..92a7459370867 100644 --- a/substrate/frame/revive/src/evm/tracing.rs +++ b/substrate/frame/revive/src/evm/tracing.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. use crate::{ - evm::{CallTrace, OpcodeTrace, SyscallTrace, Trace}, + evm::{CallTrace, ExecutionTrace, Trace}, tracing::Tracing, Config, }; @@ -29,9 +29,6 @@ pub use prestate_tracing::*; mod opcode_tracing; pub use opcode_tracing::*; -mod syscall_tracing; -pub use syscall_tracing::*; - /// A composite tracer. #[derive(derive_more::From, Debug)] pub enum Tracer { @@ -39,10 +36,8 @@ pub enum Tracer { CallTracer(CallTracer), /// A tracer that traces the prestate. PrestateTracer(PrestateTracer), - /// A tracer that traces opcodes. - OpcodeTracer(OpcodeTracer), - /// A tracer that traces syscalls. - SyscallTracer(SyscallTracer), + /// A tracer that traces opcodes and syscalls. + ExecutionTracer(ExecutionTracer), } impl Tracer @@ -54,8 +49,7 @@ where match self { Tracer::CallTracer(_) => CallTrace::default().into(), Tracer::PrestateTracer(tracer) => tracer.empty_trace().into(), - Tracer::OpcodeTracer(_) => OpcodeTrace::default().into(), - Tracer::SyscallTracer(_) => SyscallTrace::default().into(), + Tracer::ExecutionTracer(_) => ExecutionTrace::default().into(), } } @@ -64,8 +58,7 @@ where match self { Tracer::CallTracer(inner) => inner as &mut dyn Tracing, Tracer::PrestateTracer(inner) => inner as &mut dyn Tracing, - Tracer::OpcodeTracer(inner) => inner as &mut dyn Tracing, - Tracer::SyscallTracer(inner) => inner as &mut dyn Tracing, + Tracer::ExecutionTracer(inner) => inner as &mut dyn Tracing, } } @@ -74,13 +67,12 @@ where match self { Tracer::CallTracer(inner) => inner.collect_trace().map(Trace::Call), Tracer::PrestateTracer(inner) => Some(inner.collect_trace().into()), - Tracer::OpcodeTracer(inner) => Some(inner.collect_trace().into()), - Tracer::SyscallTracer(inner) => Some(inner.collect_trace().into()), + Tracer::ExecutionTracer(inner) => Some(inner.collect_trace().into()), } } - /// Check if this is an opcode tracer. - pub fn is_opcode_tracer(&self) -> bool { - matches!(self, Tracer::OpcodeTracer(_)) + /// Check if this is an execution tracer. + pub fn is_execution_tracer(&self) -> bool { + matches!(self, Tracer::ExecutionTracer(_)) } } diff --git a/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs b/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs index eb560a6eb5f59..2beb4b621baa6 100644 --- a/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs +++ b/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs @@ -15,7 +15,12 @@ // See the License for the specific language governing permissions and // limitations under the License. use crate::{ - evm::{tracing::Tracing, Bytes, OpcodeStep, OpcodeTrace, OpcodeTracerConfig}, + evm::{ + tracing::Tracing, Bytes, ExecutionStep, ExecutionStepKind, ExecutionTrace, + OpcodeTracerConfig, + }, + tracing::{EVMFrameTraceInfo, FrameTraceInfo}, + vm::pvm::env::lookup_syscall_index, DispatchError, ExecReturnValue, Key, }; use alloc::{ @@ -26,14 +31,14 @@ use alloc::{ }; use sp_core::{H160, U256}; -/// A tracer that traces opcode execution step-by-step. +/// A tracer that traces opcode and syscall execution step-by-step. #[derive(Default, Debug, Clone, PartialEq)] -pub struct OpcodeTracer { +pub struct ExecutionTracer { /// The tracer configuration. config: OpcodeTracerConfig, /// The collected trace steps. - steps: Vec, + steps: Vec, /// Current call depth. depth: u32, @@ -51,14 +56,14 @@ pub struct OpcodeTracer { return_value: Bytes, /// Pending step that's waiting for gas cost to be recorded. - pending_step: Option, + pending_step: Option, /// List of storage per call storages_per_call: Vec>, } -impl OpcodeTracer { - /// Create a new [`OpcodeTracer`] instance. +impl ExecutionTracer { + /// Create a new [`ExecutionTracer`] instance. pub fn new(config: OpcodeTracerConfig) -> Self { Self { config, @@ -74,9 +79,9 @@ impl OpcodeTracer { } /// Collect the traces and return them. - pub fn collect_trace(self) -> OpcodeTrace { + pub fn collect_trace(self) -> ExecutionTrace { let Self { steps: struct_logs, return_value, total_gas_used: gas, failed, .. } = self; - OpcodeTrace { gas, failed, return_value, struct_logs } + ExecutionTrace { gas, failed, return_value, struct_logs } } /// Record an error in the current step. @@ -87,63 +92,57 @@ impl OpcodeTracer { } } -impl Tracing for OpcodeTracer { +impl Tracing for ExecutionTracer { fn is_opcode_tracing_enabled(&self) -> bool { true } - fn enter_opcode( - &mut self, - pc: u64, - opcode: u8, - gas_before: u64, - _weight_before: crate::Weight, - get_stack: &dyn Fn() -> Vec, - get_memory: &dyn Fn(usize) -> Vec, - last_frame_output: &crate::ExecReturnValue, - ) { + fn enter_opcode(&mut self, pc: u64, opcode: u8, trace_info: &dyn EVMFrameTraceInfo) { // Check step limit - if exceeded, don't record anything if self.config.limit.map(|l| self.step_count >= l).unwrap_or(false) { return; } // Extract stack data if enabled - let stack_data = if !self.config.disable_stack { get_stack() } else { Vec::new() }; + let stack_data = + if !self.config.disable_stack { trace_info.stack_snapshot() } else { Vec::new() }; // Extract memory data if enabled let memory_data = if self.config.enable_memory { - get_memory(self.config.memory_word_limit as usize) + trace_info.memory_snapshot(self.config.memory_word_limit as usize) } else { Vec::new() }; // Extract return data if enabled let return_data = if self.config.enable_return_data { - crate::evm::Bytes(last_frame_output.data.clone()) + trace_info.last_frame_output() } else { crate::evm::Bytes::default() }; - let step = OpcodeStep { - pc, - op: opcode, - gas: gas_before, + let step = ExecutionStep { + gas: trace_info.gas_left(), gas_cost: 0u64, // Will be set in exit_opcode depth: self.depth, - stack: stack_data, - memory: memory_data, - storage: None, return_data, error: None, + kind: ExecutionStepKind::EVMOpcode { + pc, + op: opcode, + stack: stack_data, + memory: memory_data, + storage: None, + }, }; self.pending_step = Some(step); self.step_count += 1; } - fn exit_opcode(&mut self, gas_left: u64) { + fn exit_step(&mut self, trace_info: &dyn FrameTraceInfo) { if let Some(mut step) = self.pending_step.take() { - step.gas_cost = step.gas.saturating_sub(gas_left); + step.gas_cost = step.gas.saturating_sub(trace_info.gas_left()); self.steps.push(step); } } @@ -214,9 +213,13 @@ impl Tracing for OpcodeTracer { ); storage.insert(key_bytes, value_bytes); - // Set storage on the pending step + // Set storage on the pending step if it's an EVM opcode if let Some(ref mut step) = self.pending_step { - step.storage = Some(storage.clone()); + if let ExecutionStepKind::EVMOpcode { storage: ref mut step_storage, .. } = + step.kind + { + *step_storage = Some(storage.clone()); + } } } } @@ -234,10 +237,42 @@ impl Tracing for OpcodeTracer { crate::evm::Bytes(value.map(|v| v.to_vec()).unwrap_or_else(|| alloc::vec![0u8; 32])) }); - // Set storage on the pending step + // Set storage on the pending step if it's an EVM opcode if let Some(ref mut step) = self.pending_step { - step.storage = Some(storage.clone()); + if let ExecutionStepKind::EVMOpcode { storage: ref mut step_storage, .. } = + step.kind + { + *step_storage = Some(storage.clone()); + } } } } + + fn enter_ecall(&mut self, ecall: &'static str, trace_info: &dyn FrameTraceInfo) { + // Check step limit - if exceeded, don't record anything + if self.config.limit.map(|l| self.step_count >= l).unwrap_or(false) { + return; + } + + // Extract return data if enabled + let return_data = if self.config.enable_return_data { + trace_info.last_frame_output() + } else { + crate::evm::Bytes::default() + }; + + let step = ExecutionStep { + gas: trace_info.gas_left(), + gas_cost: 0u64, // Will be set in exit_ecall + depth: self.depth, + return_data, + error: None, + kind: ExecutionStepKind::PVMSyscall { + op: lookup_syscall_index(ecall).unwrap_or_default(), + }, + }; + + self.pending_step = Some(step); + self.step_count += 1; + } } diff --git a/substrate/frame/revive/src/evm/tracing/syscall_tracing.rs b/substrate/frame/revive/src/evm/tracing/syscall_tracing.rs deleted file mode 100644 index 15960ab1a2caf..0000000000000 --- a/substrate/frame/revive/src/evm/tracing/syscall_tracing.rs +++ /dev/null @@ -1,220 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -use crate::{ - evm::{tracing::Tracing, Bytes, Op, SyscallStep, SyscallTrace, SyscallTracerConfig}, - vm::pvm::env::lookup_syscall_index, - DispatchError, ExecReturnValue, Weight, -}; -use alloc::{ - format, - string::{String, ToString}, - vec::Vec, -}; -use sp_core::{H160, U256}; - -/// A tracer that traces syscall execution step-by-step. -#[derive(Default, Debug, Clone, PartialEq)] -pub struct SyscallTracer { - /// The tracer configuration. - config: SyscallTracerConfig, - - /// The collected trace steps. - steps: Vec, - - /// Current call depth. - depth: u32, - - /// Number of steps captured (for limiting). - step_count: u64, - - /// Total gas used by the transaction. - total_gas_used: u64, - - /// Whether the transaction failed. - failed: bool, - - /// The return value of the transaction. - return_value: Bytes, - - /// Pending step that's waiting for gas cost to be recorded. - pending_step: Option, -} - -impl SyscallTracer { - /// Create a new [`SyscallTracer`] instance. - pub fn new(config: SyscallTracerConfig) -> Self { - Self { - config, - steps: Vec::new(), - depth: 0, - step_count: 0, - total_gas_used: 0, - failed: false, - return_value: Bytes::default(), - pending_step: None, - } - } - - /// Collect the traces and return them. - pub fn collect_trace(self) -> SyscallTrace { - let Self { steps: struct_logs, return_value, total_gas_used: gas, failed, .. } = self; - SyscallTrace { gas, failed, return_value, struct_logs } - } - - /// Record an error in the current step. - pub fn record_error(&mut self, error: String) { - if let Some(last_step) = self.steps.last_mut() { - last_step.error = Some(error); - } - } -} - -impl Tracing for SyscallTracer { - fn is_opcode_tracing_enabled(&self) -> bool { - true - } - - fn enter_ecall( - &mut self, - ecall: &'static str, - gas_before: u64, - weight_before: crate::Weight, - last_frame_output: &crate::ExecReturnValue, - ) { - // Check step limit - if exceeded, don't record anything - if self.config.limit.map(|l| self.step_count >= l).unwrap_or(false) { - return; - } - - // Extract return data if enabled - let return_data = if self.config.enable_return_data { - crate::evm::Bytes(last_frame_output.data.clone()) - } else { - crate::evm::Bytes::default() - }; - - let step = SyscallStep { - op: Op::PvmSyscall(lookup_syscall_index(ecall).unwrap_or_default()), - gas: gas_before, - weight: weight_before, - gas_cost: 0u64, // Will be set in exit_ecall - weight_cost: Default::default(), // Will be set in exit_ecall - depth: self.depth, - return_data, - error: None, - }; - - self.pending_step = Some(step); - self.step_count += 1; - } - - fn exit_ecall(&mut self, gas_left: u64, weight_left: crate::Weight) { - if let Some(mut step) = self.pending_step.take() { - step.gas_cost = step.gas.saturating_sub(gas_left); - step.weight_cost = step.weight.saturating_sub(weight_left); - self.steps.push(step); - } - } - - fn enter_opcode( - &mut self, - _pc: u64, - opcode: u8, - gas_before: u64, - weight_before: Weight, - _get_stack: &dyn Fn() -> Vec, - _get_memory: &dyn Fn(usize) -> Vec, - _last_frame_output: &crate::ExecReturnValue, - ) { - // Check step limit - if exceeded, don't record anything - if self.config.limit.map(|l| self.step_count >= l).unwrap_or(false) { - return; - } - - // Extract return data if enabled - let return_data = if self.config.enable_return_data { - crate::evm::Bytes(_last_frame_output.data.clone()) - } else { - crate::evm::Bytes::default() - }; - - let step = SyscallStep { - op: Op::EVMOpcode(opcode), - gas: gas_before, - weight: weight_before, - gas_cost: 0u64, // Will be set in exit_ecall - weight_cost: Default::default(), // Will be set in exit_ecall - depth: self.depth, - return_data, - error: None, - }; - - self.pending_step = Some(step); - self.step_count += 1; - } - - fn exit_opcode(&mut self, gas_left: u64) { - self.exit_ecall(gas_left, Default::default()); - } - - fn enter_child_span( - &mut self, - _from: H160, - _to: H160, - _delegate_call: Option, - _is_read_only: bool, - _value: U256, - _input: &[u8], - _gas_limit: u64, - ) { - self.depth += 1; - } - - fn exit_child_span(&mut self, output: &ExecReturnValue, gas_used: u64) { - if output.did_revert() { - self.record_error("execution reverted".to_string()); - if self.depth == 0 { - self.failed = true; - } - } else { - self.return_value = Bytes(output.data.to_vec()); - } - - // Set total gas used if this is the top-level call (depth 1, will become 0 after decrement) - if self.depth == 1 { - self.total_gas_used = gas_used.try_into().unwrap_or(u64::MAX); - } - - if self.depth > 0 { - self.depth -= 1; - } - } - - fn exit_child_span_with_error(&mut self, error: DispatchError, gas_used: u64) { - self.record_error(format!("{:?}", error)); - - // Mark as failed if this is the top-level call - if self.depth == 1 { - self.failed = true; - self.total_gas_used = gas_used.try_into().unwrap_or(u64::MAX); - } - - if self.depth > 0 { - self.depth -= 1; - } - } -} diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 6d32901a57d52..5b3a8a536ecd5 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -49,8 +49,8 @@ pub mod weights; use crate::{ evm::{ block_hash::EthereumBlockBuilderIR, block_storage, fees::InfoT as FeeInfo, - runtime::SetWeightLimit, CallTracer, CreateCallMode, GenericTransaction, OpcodeTracer, - PrestateTracer, SyscallTracer, Trace, Tracer, TracerType, TYPE_EIP1559, + runtime::SetWeightLimit, CallTracer, CreateCallMode, GenericTransaction, ExecutionTracer, + PrestateTracer, Trace, Tracer, TracerType, TYPE_EIP1559, }, exec::{AccountIdOf, ExecError, ReentrancyProtection, Stack as ExecStack}, storage::{AccountType, DeletionQueueManager}, @@ -2149,9 +2149,7 @@ impl Pallet { TracerType::PrestateTracer(config) => PrestateTracer::new(config.unwrap_or_default()).into(), TracerType::StructLogger(config) => - OpcodeTracer::new(config.unwrap_or_default()).into(), - TracerType::SyscallTracer(config) => - SyscallTracer::new(config.unwrap_or_default()).into(), + ExecutionTracer::new(config.unwrap_or_default()).into(), } } diff --git a/substrate/frame/revive/src/tests/pvm.rs b/substrate/frame/revive/src/tests/pvm.rs index a092ddab21baf..2db40a333ed5a 100644 --- a/substrate/frame/revive/src/tests/pvm.rs +++ b/substrate/frame/revive/src/tests/pvm.rs @@ -5410,8 +5410,7 @@ fn self_destruct_by_syscall_tracing_works() { let trace_wrapped = match trace { crate::evm::Trace::Call(ct) => Trace::Call(ct), crate::evm::Trace::Prestate(pt) => Trace::Prestate(pt), - crate::evm::Trace::Opcode(_) => panic!("Opcode trace not expected"), - crate::evm::Trace::Syscall(_) => panic!("Syscall trace not expected"), + crate::evm::Trace::Execution(_) => panic!("Execution trace not expected"), }; assert_eq!(trace_wrapped, expected_trace, "Trace mismatch for: {}", description); diff --git a/substrate/frame/revive/src/tests/sol.rs b/substrate/frame/revive/src/tests/sol.rs index 45b5538cba222..7c6054e80872f 100644 --- a/substrate/frame/revive/src/tests/sol.rs +++ b/substrate/frame/revive/src/tests/sol.rs @@ -597,7 +597,7 @@ fn eth_substrate_call_tracks_weight_correctly() { #[test] fn opcode_tracing_works() { use crate::{ - evm::{OpcodeStep, OpcodeTrace, OpcodeTracer, OpcodeTracerConfig}, + evm::{ExecutionStep, ExecutionStepKind, ExecutionTrace, ExecutionTracer, OpcodeTracerConfig}, tracing::trace, }; use sp_core::U256; @@ -616,7 +616,7 @@ fn opcode_tracing_works() { memory_word_limit: 16, }; - let mut tracer = OpcodeTracer::new(config); + let mut tracer = ExecutionTracer::new(config); let _result = trace(&mut tracer, || { builder::bare_call(addr) .data(Fibonacci::FibonacciCalls::fib(Fibonacci::fibCall { n: 3u64 }).abi_encode()) @@ -629,73 +629,83 @@ fn opcode_tracing_works() { step.gas_cost = 0u64; }); - let expected_trace = OpcodeTrace { + let expected_trace = ExecutionTrace { gas: actual_trace.gas, failed: false, return_value: crate::evm::Bytes(U256::from(2).to_big_endian().to_vec()), struct_logs: vec![ - OpcodeStep { - pc: 0, - op: PUSH1, + ExecutionStep { gas: 0u64, gas_cost: 0u64, depth: 1, - stack: vec![], - memory: vec![], - storage: None, return_data: crate::evm::Bytes::default(), error: None, + kind: ExecutionStepKind::EVMOpcode { + pc: 0, + op: PUSH1, + stack: vec![], + memory: vec![], + storage: None, + }, }, - OpcodeStep { - pc: 2, - op: PUSH1, + ExecutionStep { gas: 0u64, gas_cost: 0u64, depth: 1, - stack: vec![crate::evm::Bytes(U256::from(0x80).to_big_endian().to_vec())], - memory: vec![], - storage: None, return_data: crate::evm::Bytes::default(), error: None, + kind: ExecutionStepKind::EVMOpcode { + pc: 2, + op: PUSH1, + stack: vec![crate::evm::Bytes(U256::from(0x80).to_big_endian().to_vec())], + memory: vec![], + storage: None, + }, }, - OpcodeStep { - pc: 4, - op: MSTORE, + ExecutionStep { gas: 0u64, gas_cost: 0u64, depth: 1, - stack: vec![ - crate::evm::Bytes(U256::from(0x80).to_big_endian().to_vec()), - crate::evm::Bytes(U256::from(0x40).to_big_endian().to_vec()), - ], - memory: vec![], - storage: None, return_data: crate::evm::Bytes::default(), error: None, + kind: ExecutionStepKind::EVMOpcode { + pc: 4, + op: MSTORE, + stack: vec![ + crate::evm::Bytes(U256::from(0x80).to_big_endian().to_vec()), + crate::evm::Bytes(U256::from(0x40).to_big_endian().to_vec()), + ], + memory: vec![], + storage: None, + }, }, - OpcodeStep { - pc: 5, - op: CALLVALUE, + ExecutionStep { gas: 0u64, gas_cost: 0u64, depth: 1, - stack: vec![], - memory: vec![], - storage: None, return_data: crate::evm::Bytes::default(), error: None, + kind: ExecutionStepKind::EVMOpcode { + pc: 5, + op: CALLVALUE, + stack: vec![], + memory: vec![], + storage: None, + }, }, - OpcodeStep { - pc: 6, - op: DUP1, + ExecutionStep { gas: 0u64, gas_cost: 0u64, depth: 1, - stack: vec![crate::evm::Bytes(U256::from(0).to_big_endian().to_vec())], - memory: vec![], - storage: None, return_data: crate::evm::Bytes::default(), error: None, + kind: ExecutionStepKind::EVMOpcode { + pc: 6, + op: DUP1, + stack: vec![crate::evm::Bytes(U256::from(0).to_big_endian().to_vec())], + memory: vec![], + storage: None, + }, }, ], }; @@ -707,8 +717,9 @@ fn opcode_tracing_works() { #[test] fn syscall_tracing_works() { use crate::{ - evm::{SyscallStep, SyscallTrace, SyscallTracer, SyscallTracerConfig}, + evm::{ExecutionStep, ExecutionStepKind, ExecutionTrace, ExecutionTracer, OpcodeTracerConfig}, tracing::trace, + vm::pvm::env::lookup_syscall_index, }; use sp_core::U256; let (code, _) = compile_module_with_type("Fibonacci", FixtureType::Resolc).unwrap(); @@ -717,9 +728,16 @@ fn syscall_tracing_works() { let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); - let config = SyscallTracerConfig { enable_return_data: true, limit: Some(5) }; + let config = OpcodeTracerConfig { + enable_memory: false, + disable_stack: true, + disable_storage: true, + enable_return_data: true, + limit: Some(5), + memory_word_limit: 16, + }; - let mut tracer = SyscallTracer::new(config); + let mut tracer = ExecutionTracer::new(config); let _result = trace(&mut tracer, || { builder::bare_call(addr) .data(Fibonacci::FibonacciCalls::fib(Fibonacci::fibCall { n: 3u64 }).abi_encode()) @@ -730,64 +748,62 @@ fn syscall_tracing_works() { actual_trace.struct_logs.iter_mut().for_each(|step| { step.gas = 0u64; step.gas_cost = 0u64; - step.weight = Weight::default(); - step.weight_cost = Weight::default(); }); - let expected_trace = SyscallTrace { + let expected_trace = ExecutionTrace { gas: actual_trace.gas, failed: false, return_value: crate::evm::Bytes(U256::from(2).to_big_endian().to_vec()), struct_logs: vec![ - SyscallStep { - ecall: "call_data_size".to_string(), + ExecutionStep { gas: 0u64, gas_cost: 0u64, - weight: Weight::default(), - weight_cost: Weight::default(), depth: 1, return_data: crate::evm::Bytes::default(), error: None, + kind: ExecutionStepKind::PVMSyscall { + op: lookup_syscall_index("call_data_size").unwrap_or_default(), + }, }, - SyscallStep { - ecall: "call_data_load".to_string(), + ExecutionStep { gas: 0u64, gas_cost: 0u64, - weight: Weight::default(), - weight_cost: Weight::default(), depth: 1, return_data: crate::evm::Bytes::default(), error: None, + kind: ExecutionStepKind::PVMSyscall { + op: lookup_syscall_index("call_data_load").unwrap_or_default(), + }, }, - SyscallStep { - ecall: "value_transferred".to_string(), + ExecutionStep { gas: 0u64, gas_cost: 0u64, - weight: Weight::default(), - weight_cost: Weight::default(), depth: 1, return_data: crate::evm::Bytes::default(), error: None, + kind: ExecutionStepKind::PVMSyscall { + op: lookup_syscall_index("value_transferred").unwrap_or_default(), + }, }, - SyscallStep { - ecall: "call_data_load".to_string(), + ExecutionStep { gas: 0u64, gas_cost: 0u64, - weight: Weight::default(), - weight_cost: Weight::default(), depth: 1, return_data: crate::evm::Bytes::default(), error: None, + kind: ExecutionStepKind::PVMSyscall { + op: lookup_syscall_index("call_data_load").unwrap_or_default(), + }, }, - SyscallStep { - ecall: "seal_return".to_string(), + ExecutionStep { gas: 0u64, gas_cost: 0u64, - weight: Weight::default(), - weight_cost: Weight::default(), depth: 1, return_data: crate::evm::Bytes::default(), error: None, + kind: ExecutionStepKind::PVMSyscall { + op: lookup_syscall_index("seal_return").unwrap_or_default(), + }, }, ], }; diff --git a/substrate/frame/revive/src/tracing.rs b/substrate/frame/revive/src/tracing.rs index 8e414cfc3e4c7..5d0e4a5c9b98b 100644 --- a/substrate/frame/revive/src/tracing.rs +++ b/substrate/frame/revive/src/tracing.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{primitives::ExecReturnValue, Code, DispatchError, Key, Weight}; +use crate::{evm::Bytes, primitives::ExecReturnValue, Code, DispatchError, Key, Weight}; use alloc::vec::Vec; use environmental::environmental; use sp_core::{H160, H256, U256}; @@ -40,12 +40,48 @@ pub(crate) fn if_tracing R>(f: F) tracer::with(f) } +/// Interface to provide frame trace information for the current execution frame. +pub trait FrameTraceInfo { + /// Get the amount of gas remaining in the current frame. + fn gas_left(&self) -> u64; + + /// Get the weight remaining in the current frame. + fn weight_left(&self) -> Weight; + + /// Get the output from the last frame. + fn last_frame_output(&self) -> Bytes; +} + +/// Interface to provide EVM-specific trace information for the current execution frame. +pub trait EVMFrameTraceInfo: FrameTraceInfo { + /// Get a snapshot of the memory at this point in execution. + /// + /// # Parameters + /// - `limit`: Maximum number of memory words to capture. + fn memory_snapshot(&self, limit: usize) -> Vec; + + /// Get a snapshot of the stack at this point in execution. + fn stack_snapshot(&self) -> Vec; +} + /// Defines methods to trace contract interactions. pub trait Tracing { /// Register an address that should be traced. + /// + /// # Parameters + /// - `addr`: The address to watch for tracing. fn watch_address(&mut self, _addr: &H160) {} - /// Called before a contract call is executed + /// Called before a contract call is executed. + /// + /// # Parameters + /// - `from`: The address initiating the call. + /// - `to`: The address being called. + /// - `delegate_call`: The original caller if this is a delegate call. + /// - `is_read_only`: Whether this is a static/read-only call. + /// - `value`: The amount of value being transferred. + /// - `input`: The input data for the call. + /// - `gas_limit`: The gas limit for this call. fn enter_child_span( &mut self, _from: H160, @@ -58,7 +94,13 @@ pub trait Tracing { ) { } - /// Called when a contract calls terminates (selfdestructs) + /// Called when a contract terminates (selfdestructs). + /// + /// # Parameters + /// - `contract_address`: The address of the contract being destroyed. + /// - `beneficiary_address`: The address receiving the contract's remaining balance. + /// - `gas_left`: The amount of gas remaining. + /// - `value`: The value transferred to the beneficiary. fn terminate( &mut self, _contract_address: H160, @@ -68,16 +110,33 @@ pub trait Tracing { ) { } - /// Record the next code and salt to be instantiated. + /// Record the code and salt for the next contract instantiation. + /// + /// # Parameters + /// - `code`: The code being instantiated. + /// - `salt`: Optional salt for CREATE2 operations. fn instantiate_code(&mut self, _code: &Code, _salt: Option<&[u8; 32]>) {} - /// Called when a balance is read + /// Called when a balance is read. + /// + /// # Parameters + /// - `addr`: The address whose balance was read. + /// - `value`: The balance value. fn balance_read(&mut self, _addr: &H160, _value: U256) {} - /// Called when storage read is called + /// Called when contract storage is read. + /// + /// # Parameters + /// - `key`: The storage key being read. + /// - `value`: The value read from storage. fn storage_read(&mut self, _key: &Key, _value: Option<&[u8]>) {} - /// Called when storage write is called + /// Called when contract storage is written. + /// + /// # Parameters + /// - `key`: The storage key being written. + /// - `old_value`: The previous value at this key. + /// - `new_value`: The new value being written. fn storage_write( &mut self, _key: &Key, @@ -86,46 +145,54 @@ pub trait Tracing { ) { } - /// Record a log event + /// Record a log event. + /// + /// # Parameters + /// - `event`: The address emitting the event. + /// - `topics`: The indexed topics for the event. + /// - `data`: The event data. fn log_event(&mut self, _event: H160, _topics: &[H256], _data: &[u8]) {} - /// Called after a contract call is executed + /// Called after a contract call completes successfully. + /// + /// # Parameters + /// - `output`: The return value from the call. + /// - `gas_used`: The amount of gas consumed. fn exit_child_span(&mut self, _output: &ExecReturnValue, _gas_used: u64) {} - /// Called when a contract call terminates with an error + /// Called when a contract call terminates with an error. + /// + /// # Parameters + /// - `error`: The error that occurred. + /// - `gas_used`: The amount of gas consumed before the error. fn exit_child_span_with_error(&mut self, _error: DispatchError, _gas_used: u64) {} /// Check if opcode tracing is enabled. + /// + /// # Returns + /// `true` if the tracer wants to trace individual opcodes. fn is_opcode_tracing_enabled(&self) -> bool { false } - /// Called before an opcode is executed. - fn enter_opcode( - &mut self, - _pc: u64, - _opcode: u8, - _gas_before: u64, - _weight_before: Weight, - _get_stack: &dyn Fn() -> Vec, - _get_memory: &dyn Fn(usize) -> Vec, - _last_frame_output: &crate::ExecReturnValue, - ) { - } - - /// Called after an opcode is executed to record the gas cost. - fn exit_opcode(&mut self, _gas_left: u64) {} - - /// Called before an ecall is executed. - fn enter_ecall( - &mut self, - _ecall: &'static str, - _gas_before: u64, - _weight_before: Weight, - _last_frame_output: &crate::ExecReturnValue, - ) { - } - - /// Called after an ecall is executed to record the gas cost and weight consumed. - fn exit_ecall(&mut self, _gas_left: u64, _weight_consumed: Weight) {} + /// Called before an EVM opcode is executed. + /// + /// # Parameters + /// - `pc`: The current program counter. + /// - `opcode`: The opcode being executed. + /// - `trace_info`: Information about the current execution frame. + fn enter_opcode(&mut self, _pc: u64, _opcode: u8, _trace_info: &dyn EVMFrameTraceInfo) {} + + /// Called before a PVM syscall is executed. + /// + /// # Parameters + /// - `ecall`: The name of the syscall being executed. + /// - `trace_info`: Information about the current execution frame. + fn enter_ecall(&mut self, _ecall: &'static str, _trace_info: &dyn FrameTraceInfo) {} + + /// Called after an EVM opcode or PVM syscall is executed to record the gas cost. + /// + /// # Parameters + /// - `trace_info`: Information about the current execution frame. + fn exit_step(&mut self, _trace_info: &dyn FrameTraceInfo) {} } diff --git a/substrate/frame/revive/src/vm/evm.rs b/substrate/frame/revive/src/vm/evm.rs index b44c49500774d..14b87ecd0b577 100644 --- a/substrate/frame/revive/src/vm/evm.rs +++ b/substrate/frame/revive/src/vm/evm.rs @@ -154,27 +154,14 @@ fn run_plain_with_tracing( interpreter: &mut Interpreter, ) -> ControlFlow { loop { + let pc = interpreter.bytecode.pc() as u64; let opcode = interpreter.bytecode.opcode(); - tracing::if_tracing(|tracer| { - let meter = interpreter.ext.frame_meter(); - tracer.enter_opcode( - interpreter.bytecode.pc() as u64, - opcode, - meter.eth_gas_left().unwrap_or_default().try_into().unwrap_or_default(), - meter.weight_left().unwrap_or_default(), - &interpreter.stack.bytes_getter(), - &interpreter.memory.bytes_getter(), - interpreter.ext.last_frame_output(), - ); - }); + tracing::if_tracing(|tracer| tracer.enter_opcode(pc, opcode, interpreter)); interpreter.bytecode.relative_jump(1); let res = exec_instruction(interpreter, opcode); - tracing::if_tracing(|tracer| { - let gas_left = interpreter.ext.gas_left(); - tracer.exit_opcode(gas_left.into()); - }); + tracing::if_tracing(|tracer| tracer.exit_step(interpreter)); res?; } diff --git a/substrate/frame/revive/src/vm/evm/interpreter.rs b/substrate/frame/revive/src/vm/evm/interpreter.rs index be5663499ba6e..1b1038c47c616 100644 --- a/substrate/frame/revive/src/vm/evm/interpreter.rs +++ b/substrate/frame/revive/src/vm/evm/interpreter.rs @@ -18,11 +18,12 @@ use super::ExtBytecode; use crate::{ primitives::ExecReturnValue, + tracing::FrameTraceInfo, vm::{ evm::{memory::Memory, stack::Stack}, ExecResult, Ext, }, - Config, DispatchError, Error, + Config, DispatchError, Error, Weight, }; use alloc::vec::Vec; use pallet_revive_uapi::ReturnFlags; @@ -74,3 +75,29 @@ impl<'a, E: Ext> Interpreter<'a, E> { Self { ext, bytecode, input, stack: Stack::new(), memory: Memory::new() } } } + +impl FrameTraceInfo for Interpreter<'_, E> { + fn gas_left(&self) -> u64 { + let meter = self.ext.frame_meter(); + meter.eth_gas_left().unwrap_or_default().try_into().unwrap_or_default() + } + + fn weight_left(&self) -> Weight { + let meter = self.ext.frame_meter(); + meter.weight_left().unwrap_or_default() + } + + fn last_frame_output(&self) -> crate::evm::Bytes { + crate::evm::Bytes(self.ext.last_frame_output().data.clone()) + } +} + +impl crate::tracing::EVMFrameTraceInfo for Interpreter<'_, E> { + fn memory_snapshot(&self, limit: usize) -> Vec { + self.memory.snapshot(limit) + } + + fn stack_snapshot(&self) -> Vec { + self.stack.snapshot() + } +} diff --git a/substrate/frame/revive/src/vm/evm/memory.rs b/substrate/frame/revive/src/vm/evm/memory.rs index a9a2a22d37352..8a0c5f7879b41 100644 --- a/substrate/frame/revive/src/vm/evm/memory.rs +++ b/substrate/frame/revive/src/vm/evm/memory.rs @@ -132,21 +132,19 @@ impl Memory { } /// Returns a closure that returns the memory content in 32-byte chunks up to a specified limit. - pub fn bytes_getter(&self) -> impl Fn(usize) -> Vec + '_ { - |limit: usize| { - let mut memory_bytes = Vec::new(); + pub fn snapshot(&self, limit: usize) -> Vec { + let mut memory_bytes = Vec::new(); - // Read memory in 32-byte chunks, limiting to configured size - let words_to_read = core::cmp::min((self.size() + 31) / 32, limit); + // Read memory in 32-byte chunks, limiting to configured size + let words_to_read = core::cmp::min((self.size() + 31) / 32, limit); - for i in 0..words_to_read { - // Use get_word to read 32 bytes directly - let word = self.get_word(i * 32); - memory_bytes.push(crate::evm::Bytes(word.to_vec())); - } - - memory_bytes + for i in 0..words_to_read { + // Use get_word to read 32 bytes directly + let word = self.get_word(i * 32); + memory_bytes.push(crate::evm::Bytes(word.to_vec())); } + + memory_bytes } } diff --git a/substrate/frame/revive/src/vm/evm/stack.rs b/substrate/frame/revive/src/vm/evm/stack.rs index 82795b274e667..7ab47ded45527 100644 --- a/substrate/frame/revive/src/vm/evm/stack.rs +++ b/substrate/frame/revive/src/vm/evm/stack.rs @@ -176,17 +176,15 @@ impl Stack { return ControlFlow::Continue(()); } - /// Returns a closure that returns a vector of the stack items as `Bytes`. - pub fn bytes_getter(&self) -> impl Fn() -> Vec + '_ { - || { - let mut stack_bytes = Vec::new(); - for value in self.stack.iter() { - let bytes = value.to_big_endian().to_vec(); - stack_bytes.push(crate::evm::Bytes(bytes)); - } - - stack_bytes + /// Returns a snapshot of the stack as bytes. + pub fn snapshot(&self) -> Vec { + let mut stack_bytes = Vec::new(); + for value in self.stack.iter() { + let bytes = value.to_big_endian().to_vec(); + stack_bytes.push(crate::evm::Bytes(bytes)); } + + stack_bytes } } diff --git a/substrate/frame/revive/src/vm/pvm.rs b/substrate/frame/revive/src/vm/pvm.rs index 6fb4cbca9aa70..dff5979e036d4 100644 --- a/substrate/frame/revive/src/vm/pvm.rs +++ b/substrate/frame/revive/src/vm/pvm.rs @@ -19,20 +19,20 @@ pub mod env; -#[cfg(doc)] -pub use env::SyscallDoc; - use crate::{ exec::{CallResources, ExecError, ExecResult, Ext, Key}, limits, metering::ChargedAmount, precompiles::{All as AllPrecompiles, Precompiles}, primitives::ExecReturnValue, + tracing::FrameTraceInfo, Code, Config, Error, Pallet, ReentrancyProtection, RuntimeCosts, LOG_TARGET, SENTINEL, }; use alloc::{vec, vec::Vec}; use codec::Encode; use core::{fmt, marker::PhantomData, mem}; +#[cfg(doc)] +pub use env::SyscallDoc; use frame_support::{ensure, weights::Weight}; use pallet_revive_uapi::{CallFlags, ReturnErrorCode, ReturnFlags, StorageFlags}; use sp_core::{H160, H256, U256}; @@ -814,6 +814,22 @@ impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { } } +impl<'a, E: Ext, M: ?Sized + Memory> FrameTraceInfo for Runtime<'a, E, M> { + fn gas_left(&self) -> u64 { + let meter = self.ext.frame_meter(); + meter.eth_gas_left().unwrap_or_default().try_into().unwrap_or_default() + } + + fn weight_left(&self) -> Weight { + let meter = self.ext.frame_meter(); + meter.weight_left().unwrap_or_default() + } + + fn last_frame_output(&self) -> crate::evm::Bytes { + crate::evm::Bytes(self.ext.last_frame_output().data.clone()) + } +} + pub struct PreparedCall<'a, E: Ext> { module: polkavm::Module, instance: polkavm::RawInstance, From 0b7972fb512128388c76b9d5e14fb556a1a15d08 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Tue, 16 Dec 2025 11:52:29 +0100 Subject: [PATCH 48/76] wip --- .../revive/src/evm/api/debug_rpc_types.rs | 24 +++---- .../revive/src/evm/tracing/opcode_tracing.rs | 6 +- substrate/frame/revive/src/tests/sol.rs | 8 +-- .../revive/src/tests/sol/revm_tracing.rs | 62 ------------------- 4 files changed, 19 insertions(+), 81 deletions(-) delete mode 100644 substrate/frame/revive/src/tests/sol/revm_tracing.rs diff --git a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs index 5947e91cb029a..c5a2724eb8000 100644 --- a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs +++ b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs @@ -38,7 +38,7 @@ pub enum TracerType { PrestateTracer(Option), /// A tracer that traces opcodes and syscalls. - StructLogger(Option), + StructLogger(Option), } impl From for TracerType { @@ -53,8 +53,8 @@ impl From for TracerType { } } -impl From for TracerType { - fn from(config: OpcodeTracerConfig) -> Self { +impl From for TracerType { + fn from(config: StructLoggerConfig) -> Self { TracerType::StructLogger(Some(config)) } } @@ -97,7 +97,7 @@ impl<'de> Deserialize<'de> for TracerConfig { #[serde(rename_all = "camelCase")] struct TracerConfigInline { #[serde(flatten, default)] - opcode_config: OpcodeTracerConfig, + struct_logger_config: StructLoggerConfig, #[serde(with = "humantime_serde", default)] timeout: Option, } @@ -113,7 +113,7 @@ impl<'de> Deserialize<'de> for TracerConfig { TracerConfigHelper::WithType(cfg) => Ok(TracerConfig { config: cfg.config, timeout: cfg.timeout }), TracerConfigHelper::Inline(cfg) => Ok(TracerConfig { - config: TracerType::StructLogger(Some(cfg.opcode_config)), + config: TracerType::StructLogger(Some(cfg.struct_logger_config)), timeout: cfg.timeout, }), } @@ -168,10 +168,10 @@ where }) } -/// The configuration for the opcode tracer. +/// The configuration for the struct logger. #[derive(Clone, Debug, Decode, Serialize, Deserialize, Encode, PartialEq, TypeInfo)] #[serde(default, rename_all = "camelCase")] -pub struct OpcodeTracerConfig { +pub struct StructLoggerConfig { /// Whether to enable memory capture pub enable_memory: bool, @@ -192,7 +192,7 @@ pub struct OpcodeTracerConfig { pub memory_word_limit: u32, } -impl Default for OpcodeTracerConfig { +impl Default for StructLoggerConfig { fn default() -> Self { Self { enable_memory: false, @@ -226,7 +226,7 @@ fn test_tracer_config_serialization() { ( r#"{ "enableMemory": true, "disableStack": false, "disableStorage": false, "enableReturnData": true }"#, TracerConfig { - config: TracerType::StructLogger(Some(OpcodeTracerConfig { + config: TracerType::StructLogger(Some(StructLoggerConfig { enable_memory: true, disable_stack: false, disable_storage: false, @@ -240,7 +240,7 @@ fn test_tracer_config_serialization() { ( r#"{ }"#, TracerConfig { - config: TracerType::StructLogger(Some(OpcodeTracerConfig::default())), + config: TracerType::StructLogger(Some(StructLoggerConfig::default())), timeout: None, }, ), @@ -276,7 +276,7 @@ fn test_tracer_config_serialization() { ( r#"{"tracer": "structLogger", "tracerConfig": { "enableMemory": true }}"#, TracerConfig { - config: OpcodeTracerConfig { enable_memory: true, ..Default::default() }.into(), + config: StructLoggerConfig { enable_memory: true, ..Default::default() }.into(), timeout: None, }, ), @@ -540,7 +540,7 @@ pub enum ExecutionStepKind { macro_rules! define_opcode_functions { ($($op:ident),* $(,)?) => { - /// Get opcode name from byte value using REVM opcode names + /// Get opcode name from byte value using opcode names fn get_opcode_name(opcode: u8) -> &'static str { use revm::bytecode::opcode::*; match opcode { diff --git a/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs b/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs index 2beb4b621baa6..15ef22d06e24a 100644 --- a/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs +++ b/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs @@ -17,7 +17,7 @@ use crate::{ evm::{ tracing::Tracing, Bytes, ExecutionStep, ExecutionStepKind, ExecutionTrace, - OpcodeTracerConfig, + StructLoggerConfig, }, tracing::{EVMFrameTraceInfo, FrameTraceInfo}, vm::pvm::env::lookup_syscall_index, @@ -35,7 +35,7 @@ use sp_core::{H160, U256}; #[derive(Default, Debug, Clone, PartialEq)] pub struct ExecutionTracer { /// The tracer configuration. - config: OpcodeTracerConfig, + config: StructLoggerConfig, /// The collected trace steps. steps: Vec, @@ -64,7 +64,7 @@ pub struct ExecutionTracer { impl ExecutionTracer { /// Create a new [`ExecutionTracer`] instance. - pub fn new(config: OpcodeTracerConfig) -> Self { + pub fn new(config: StructLoggerConfig) -> Self { Self { config, steps: Vec::new(), diff --git a/substrate/frame/revive/src/tests/sol.rs b/substrate/frame/revive/src/tests/sol.rs index 7c6054e80872f..1c809e5f17ef5 100644 --- a/substrate/frame/revive/src/tests/sol.rs +++ b/substrate/frame/revive/src/tests/sol.rs @@ -597,7 +597,7 @@ fn eth_substrate_call_tracks_weight_correctly() { #[test] fn opcode_tracing_works() { use crate::{ - evm::{ExecutionStep, ExecutionStepKind, ExecutionTrace, ExecutionTracer, OpcodeTracerConfig}, + evm::{ExecutionStep, ExecutionStepKind, ExecutionTrace, ExecutionTracer, StructLoggerConfig}, tracing::trace, }; use sp_core::U256; @@ -607,7 +607,7 @@ fn opcode_tracing_works() { let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); - let config = OpcodeTracerConfig { + let config = StructLoggerConfig { enable_memory: false, disable_stack: false, disable_storage: true, @@ -717,7 +717,7 @@ fn opcode_tracing_works() { #[test] fn syscall_tracing_works() { use crate::{ - evm::{ExecutionStep, ExecutionStepKind, ExecutionTrace, ExecutionTracer, OpcodeTracerConfig}, + evm::{ExecutionStep, ExecutionStepKind, ExecutionTrace, ExecutionTracer, StructLoggerConfig}, tracing::trace, vm::pvm::env::lookup_syscall_index, }; @@ -728,7 +728,7 @@ fn syscall_tracing_works() { let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); - let config = OpcodeTracerConfig { + let config = StructLoggerConfig { enable_memory: false, disable_stack: true, disable_storage: true, diff --git a/substrate/frame/revive/src/tests/sol/revm_tracing.rs b/substrate/frame/revive/src/tests/sol/revm_tracing.rs deleted file mode 100644 index 6b599d793ecdc..0000000000000 --- a/substrate/frame/revive/src/tests/sol/revm_tracing.rs +++ /dev/null @@ -1,62 +0,0 @@ -use alloy_rpc_types_trace::geth::{DefaultFrame, GethDefaultTracingOptions}; -use revm::{ - context::{ContextTr, TxEnv}, - context_interface::TransactTo, - database::CacheDB, - database_interface::{DatabaseRef, EmptyDB}, - primitives::Address, - Context, ExecuteCommitEvm, InspectEvm, MainBuilder, MainContext, -}; -use revm_inspectors::tracing::{TracingInspector, TracingInspectorConfig}; - -#[derive(Debug, Default, Clone)] -pub struct RevmTracer { - db: CacheDB, -} - -impl RevmTracer { - fn get_nonce(&self, address: Address) -> u64 { - match self.db.basic_ref(address) { - Ok(Some(account_info)) => account_info.nonce, - _ => 0, - } - } - - pub fn deploy(&mut self, tx: TxEnv) -> Address { - let mut evm = Context::mainnet().with_db(self.db.clone()).build_mainnet(); - let tx = TxEnv { - gas_limit: 1000000, - kind: TransactTo::Create, - nonce: self.get_nonce(tx.caller), - ..tx - }; - let out = evm.transact_commit(tx).unwrap(); - assert!(out.is_success(), "Contract deployment failed"); - self.db = evm.db().clone(); - out.created_address().unwrap() - } - - pub fn call(&mut self, tx: TxEnv) -> DefaultFrame { - let mut insp = TracingInspector::new(TracingInspectorConfig::from_geth_config( - &GethDefaultTracingOptions::default().enable_memory(), - )); - - let evm = Context::mainnet().with_db(self.db.clone()).build_mainnet(); - let mut evm = evm.clone().build_mainnet_with_inspector(&mut insp); - let tx = TxEnv { nonce: self.get_nonce(tx.caller), ..tx }; - let res = evm.inspect_tx(tx).unwrap(); - assert!(res.result.is_success()); - self.db = evm.db().clone(); - - let trace = insp - .with_transaction_gas_used(res.result.gas_used()) - .geth_builder() - .geth_traces( - res.result.gas_used(), - res.result.output().unwrap_or_default().clone(), - GethDefaultTracingOptions::default().enable_memory(), - ); - - trace - } -} From 53738e6e4e009fac7128f96fa1b62384622149bb Mon Sep 17 00:00:00 2001 From: pgherveou Date: Tue, 16 Dec 2025 11:39:51 +0000 Subject: [PATCH 49/76] fix --- substrate/frame/revive/src/evm/tracing/call_tracing.rs | 2 +- substrate/frame/revive/src/vm/evm/memory.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/substrate/frame/revive/src/evm/tracing/call_tracing.rs b/substrate/frame/revive/src/evm/tracing/call_tracing.rs index 4e4dde0f41beb..b492458852c52 100644 --- a/substrate/frame/revive/src/evm/tracing/call_tracing.rs +++ b/substrate/frame/revive/src/evm/tracing/call_tracing.rs @@ -159,7 +159,7 @@ impl Tracing for CallTracer { if let Some(trace) = self.traces.get_mut(current_index) { trace.output = output.data.clone().into(); - trace.gas_used = gas_used.into(); + trace.gas_used = gas_used; if output.did_revert() { trace.revert_reason = decode_revert_reason(&output.data); diff --git a/substrate/frame/revive/src/vm/evm/memory.rs b/substrate/frame/revive/src/vm/evm/memory.rs index 8a0c5f7879b41..adfc7a6c97825 100644 --- a/substrate/frame/revive/src/vm/evm/memory.rs +++ b/substrate/frame/revive/src/vm/evm/memory.rs @@ -136,7 +136,7 @@ impl Memory { let mut memory_bytes = Vec::new(); // Read memory in 32-byte chunks, limiting to configured size - let words_to_read = core::cmp::min((self.size() + 31) / 32, limit); + let words_to_read = core::cmp::min(self.size().div_ceil(32), limit); for i in 0..words_to_read { // Use get_word to read 32 bytes directly From 82f5a8508f0799778a80d320af2f7ee04c5c0c0c Mon Sep 17 00:00:00 2001 From: pgherveou Date: Tue, 16 Dec 2025 12:40:13 +0100 Subject: [PATCH 50/76] wip --- substrate/frame/revive/src/evm/api/debug_rpc_types.rs | 2 -- substrate/frame/revive/src/evm/tracing/opcode_tracing.rs | 2 +- substrate/frame/revive/src/tracing.rs | 2 +- substrate/frame/revive/src/vm/evm.rs | 2 +- 4 files changed, 3 insertions(+), 5 deletions(-) diff --git a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs index c5a2724eb8000..c8075d6bb3a57 100644 --- a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs +++ b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs @@ -471,7 +471,6 @@ where #[serde(rename_all = "camelCase")] pub struct ExecutionTrace { /// Total gas used by the transaction. - #[serde(with = "super::hex_serde")] pub gas: u64, /// Whether the transaction failed. pub failed: bool, @@ -509,7 +508,6 @@ pub enum ExecutionStepKind { /// An EVM opcode execution. EVMOpcode { /// The program counter. - #[serde(with = "super::hex_serde")] pc: u64, /// The opcode being executed. #[serde(serialize_with = "serialize_opcode", deserialize_with = "deserialize_opcode")] diff --git a/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs b/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs index 15ef22d06e24a..7cb7c01501d11 100644 --- a/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs +++ b/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs @@ -93,7 +93,7 @@ impl ExecutionTracer { } impl Tracing for ExecutionTracer { - fn is_opcode_tracing_enabled(&self) -> bool { + fn is_execution_tracing_enabled(&self) -> bool { true } diff --git a/substrate/frame/revive/src/tracing.rs b/substrate/frame/revive/src/tracing.rs index 5d0e4a5c9b98b..8931943c9f888 100644 --- a/substrate/frame/revive/src/tracing.rs +++ b/substrate/frame/revive/src/tracing.rs @@ -171,7 +171,7 @@ pub trait Tracing { /// /// # Returns /// `true` if the tracer wants to trace individual opcodes. - fn is_opcode_tracing_enabled(&self) -> bool { + fn is_execution_tracing_enabled(&self) -> bool { false } diff --git a/substrate/frame/revive/src/vm/evm.rs b/substrate/frame/revive/src/vm/evm.rs index 14b87ecd0b577..5edc42349f2e4 100644 --- a/substrate/frame/revive/src/vm/evm.rs +++ b/substrate/frame/revive/src/vm/evm.rs @@ -132,7 +132,7 @@ impl ContractBlob { pub fn call(bytecode: Bytecode, ext: &mut E, input: Vec) -> ExecResult { let mut interpreter = Interpreter::new(ExtBytecode::new(bytecode), input, ext); let use_opcode_tracing = - tracing::if_tracing(|tracer| tracer.is_opcode_tracing_enabled()).unwrap_or(false); + tracing::if_tracing(|tracer| tracer.is_execution_tracing_enabled()).unwrap_or(false); let ControlFlow::Break(halt) = if use_opcode_tracing { run_plain_with_tracing(&mut interpreter) From bdeb7699b648b2d45ffe55c2fecc17bdbcd6b595 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Tue, 16 Dec 2025 13:19:09 +0100 Subject: [PATCH 51/76] simplify --- substrate/frame/revive/src/tracing.rs | 5 +---- substrate/frame/revive/src/vm/evm/interpreter.rs | 7 +------ substrate/frame/revive/src/vm/evm/memory.rs | 6 ++---- substrate/frame/revive/src/vm/pvm.rs | 5 ----- 4 files changed, 4 insertions(+), 19 deletions(-) diff --git a/substrate/frame/revive/src/tracing.rs b/substrate/frame/revive/src/tracing.rs index 8931943c9f888..f64274acc762d 100644 --- a/substrate/frame/revive/src/tracing.rs +++ b/substrate/frame/revive/src/tracing.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{evm::Bytes, primitives::ExecReturnValue, Code, DispatchError, Key, Weight}; +use crate::{evm::Bytes, primitives::ExecReturnValue, Code, DispatchError, Key}; use alloc::vec::Vec; use environmental::environmental; use sp_core::{H160, H256, U256}; @@ -45,9 +45,6 @@ pub trait FrameTraceInfo { /// Get the amount of gas remaining in the current frame. fn gas_left(&self) -> u64; - /// Get the weight remaining in the current frame. - fn weight_left(&self) -> Weight; - /// Get the output from the last frame. fn last_frame_output(&self) -> Bytes; } diff --git a/substrate/frame/revive/src/vm/evm/interpreter.rs b/substrate/frame/revive/src/vm/evm/interpreter.rs index 1b1038c47c616..99c2d00521528 100644 --- a/substrate/frame/revive/src/vm/evm/interpreter.rs +++ b/substrate/frame/revive/src/vm/evm/interpreter.rs @@ -23,7 +23,7 @@ use crate::{ evm::{memory::Memory, stack::Stack}, ExecResult, Ext, }, - Config, DispatchError, Error, Weight, + Config, DispatchError, Error, }; use alloc::vec::Vec; use pallet_revive_uapi::ReturnFlags; @@ -82,11 +82,6 @@ impl FrameTraceInfo for Interpreter<'_, E> { meter.eth_gas_left().unwrap_or_default().try_into().unwrap_or_default() } - fn weight_left(&self) -> Weight { - let meter = self.ext.frame_meter(); - meter.weight_left().unwrap_or_default() - } - fn last_frame_output(&self) -> crate::evm::Bytes { crate::evm::Bytes(self.ext.last_frame_output().data.clone()) } diff --git a/substrate/frame/revive/src/vm/evm/memory.rs b/substrate/frame/revive/src/vm/evm/memory.rs index adfc7a6c97825..035fd5eb5439b 100644 --- a/substrate/frame/revive/src/vm/evm/memory.rs +++ b/substrate/frame/revive/src/vm/evm/memory.rs @@ -131,15 +131,13 @@ impl Memory { self.data.copy_within(src..src + len, dst); } - /// Returns a closure that returns the memory content in 32-byte chunks up to a specified limit. + /// Returns a snapshot of the memory in 32-byte chunks, limited to the specified number of + /// chunks. pub fn snapshot(&self, limit: usize) -> Vec { let mut memory_bytes = Vec::new(); - - // Read memory in 32-byte chunks, limiting to configured size let words_to_read = core::cmp::min(self.size().div_ceil(32), limit); for i in 0..words_to_read { - // Use get_word to read 32 bytes directly let word = self.get_word(i * 32); memory_bytes.push(crate::evm::Bytes(word.to_vec())); } diff --git a/substrate/frame/revive/src/vm/pvm.rs b/substrate/frame/revive/src/vm/pvm.rs index dff5979e036d4..a684fc7edc45c 100644 --- a/substrate/frame/revive/src/vm/pvm.rs +++ b/substrate/frame/revive/src/vm/pvm.rs @@ -820,11 +820,6 @@ impl<'a, E: Ext, M: ?Sized + Memory> FrameTraceInfo for Runtime<'a, E, M> meter.eth_gas_left().unwrap_or_default().try_into().unwrap_or_default() } - fn weight_left(&self) -> Weight { - let meter = self.ext.frame_meter(); - meter.weight_left().unwrap_or_default() - } - fn last_frame_output(&self) -> crate::evm::Bytes { crate::evm::Bytes(self.ext.last_frame_output().data.clone()) } From ea5401c520a9196f9b052c5359af478aa53dbb3a Mon Sep 17 00:00:00 2001 From: pgherveou Date: Tue, 16 Dec 2025 13:24:34 +0100 Subject: [PATCH 52/76] smaller diff --- substrate/frame/revive/src/tracing.rs | 71 ++++----------------------- 1 file changed, 10 insertions(+), 61 deletions(-) diff --git a/substrate/frame/revive/src/tracing.rs b/substrate/frame/revive/src/tracing.rs index f64274acc762d..98c0951ccec4c 100644 --- a/substrate/frame/revive/src/tracing.rs +++ b/substrate/frame/revive/src/tracing.rs @@ -64,21 +64,9 @@ pub trait EVMFrameTraceInfo: FrameTraceInfo { /// Defines methods to trace contract interactions. pub trait Tracing { /// Register an address that should be traced. - /// - /// # Parameters - /// - `addr`: The address to watch for tracing. fn watch_address(&mut self, _addr: &H160) {} - /// Called before a contract call is executed. - /// - /// # Parameters - /// - `from`: The address initiating the call. - /// - `to`: The address being called. - /// - `delegate_call`: The original caller if this is a delegate call. - /// - `is_read_only`: Whether this is a static/read-only call. - /// - `value`: The amount of value being transferred. - /// - `input`: The input data for the call. - /// - `gas_limit`: The gas limit for this call. + /// Called before a contract call is executed fn enter_child_span( &mut self, _from: H160, @@ -91,13 +79,7 @@ pub trait Tracing { ) { } - /// Called when a contract terminates (selfdestructs). - /// - /// # Parameters - /// - `contract_address`: The address of the contract being destroyed. - /// - `beneficiary_address`: The address receiving the contract's remaining balance. - /// - `gas_left`: The amount of gas remaining. - /// - `value`: The value transferred to the beneficiary. + /// Called when a contract calls terminates (selfdestructs) fn terminate( &mut self, _contract_address: H160, @@ -107,33 +89,16 @@ pub trait Tracing { ) { } - /// Record the code and salt for the next contract instantiation. - /// - /// # Parameters - /// - `code`: The code being instantiated. - /// - `salt`: Optional salt for CREATE2 operations. + /// Record the next code and salt to be instantiated. fn instantiate_code(&mut self, _code: &Code, _salt: Option<&[u8; 32]>) {} - /// Called when a balance is read. - /// - /// # Parameters - /// - `addr`: The address whose balance was read. - /// - `value`: The balance value. + /// Called when a balance is read fn balance_read(&mut self, _addr: &H160, _value: U256) {} - /// Called when contract storage is read. - /// - /// # Parameters - /// - `key`: The storage key being read. - /// - `value`: The value read from storage. + /// Called when storage read is called fn storage_read(&mut self, _key: &Key, _value: Option<&[u8]>) {} - /// Called when contract storage is written. - /// - /// # Parameters - /// - `key`: The storage key being written. - /// - `old_value`: The previous value at this key. - /// - `new_value`: The new value being written. + /// Called when storage write is called fn storage_write( &mut self, _key: &Key, @@ -142,32 +107,16 @@ pub trait Tracing { ) { } - /// Record a log event. - /// - /// # Parameters - /// - `event`: The address emitting the event. - /// - `topics`: The indexed topics for the event. - /// - `data`: The event data. + /// Record a log event fn log_event(&mut self, _event: H160, _topics: &[H256], _data: &[u8]) {} - /// Called after a contract call completes successfully. - /// - /// # Parameters - /// - `output`: The return value from the call. - /// - `gas_used`: The amount of gas consumed. + /// Called after a contract call is executed fn exit_child_span(&mut self, _output: &ExecReturnValue, _gas_used: u64) {} - /// Called when a contract call terminates with an error. - /// - /// # Parameters - /// - `error`: The error that occurred. - /// - `gas_used`: The amount of gas consumed before the error. + /// Called when a contract call terminates with an error fn exit_child_span_with_error(&mut self, _error: DispatchError, _gas_used: u64) {} - /// Check if opcode tracing is enabled. - /// - /// # Returns - /// `true` if the tracer wants to trace individual opcodes. + /// Check if execution tracing is enabled. fn is_execution_tracing_enabled(&self) -> bool { false } From 5ee255c34b44b90b35ce4131e33773d1e5d38c42 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Tue, 16 Dec 2025 13:28:18 +0100 Subject: [PATCH 53/76] fix --- Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 1c87813849f39..539fa3162ca4f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1563,4 +1563,3 @@ wasmi = { opt-level = 3 } x25519-dalek = { opt-level = 3 } yamux = { opt-level = 3 } zeroize = { opt-level = 3 } - From 4a26e0a0a55da7fea1a4a902e15ac96b31293ad1 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Tue, 16 Dec 2025 13:31:40 +0100 Subject: [PATCH 54/76] fix silly comments --- substrate/frame/revive/src/tests/sol.rs | 9 ++++++--- substrate/frame/revive/src/vm/evm.rs | 6 ++++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/substrate/frame/revive/src/tests/sol.rs b/substrate/frame/revive/src/tests/sol.rs index 1c809e5f17ef5..b6c8130e5ef94 100644 --- a/substrate/frame/revive/src/tests/sol.rs +++ b/substrate/frame/revive/src/tests/sol.rs @@ -597,7 +597,9 @@ fn eth_substrate_call_tracks_weight_correctly() { #[test] fn opcode_tracing_works() { use crate::{ - evm::{ExecutionStep, ExecutionStepKind, ExecutionTrace, ExecutionTracer, StructLoggerConfig}, + evm::{ + ExecutionStep, ExecutionStepKind, ExecutionTrace, ExecutionTracer, StructLoggerConfig, + }, tracing::trace, }; use sp_core::U256; @@ -717,7 +719,9 @@ fn opcode_tracing_works() { #[test] fn syscall_tracing_works() { use crate::{ - evm::{ExecutionStep, ExecutionStepKind, ExecutionTrace, ExecutionTracer, StructLoggerConfig}, + evm::{ + ExecutionStep, ExecutionStepKind, ExecutionTrace, ExecutionTracer, StructLoggerConfig, + }, tracing::trace, vm::pvm::env::lookup_syscall_index, }; @@ -808,7 +812,6 @@ fn syscall_tracing_works() { ], }; - // Single assertion that verifies the complete trace structure matches exactly assert_eq!(actual_trace, expected_trace); }); } diff --git a/substrate/frame/revive/src/vm/evm.rs b/substrate/frame/revive/src/vm/evm.rs index 5edc42349f2e4..02d8f28af0439 100644 --- a/substrate/frame/revive/src/vm/evm.rs +++ b/substrate/frame/revive/src/vm/evm.rs @@ -154,9 +154,11 @@ fn run_plain_with_tracing( interpreter: &mut Interpreter, ) -> ControlFlow { loop { - let pc = interpreter.bytecode.pc() as u64; let opcode = interpreter.bytecode.opcode(); - tracing::if_tracing(|tracer| tracer.enter_opcode(pc, opcode, interpreter)); + tracing::if_tracing(|tracer| { + let pc = interpreter.bytecode.pc() as u64; + tracer.enter_opcode(pc, opcode, interpreter) + }); interpreter.bytecode.relative_jump(1); let res = exec_instruction(interpreter, opcode); From e02e1677c5a1659359b51cd117228ea8f2fa9db9 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Tue, 16 Dec 2025 13:51:06 +0100 Subject: [PATCH 55/76] Simplify --- .../revive/src/evm/api/debug_rpc_types.rs | 66 ---------------- substrate/frame/revive/src/evm/tracing.rs | 4 +- ...opcode_tracing.rs => execution_tracing.rs} | 76 ++++++++----------- 3 files changed, 34 insertions(+), 112 deletions(-) rename substrate/frame/revive/src/evm/tracing/{opcode_tracing.rs => execution_tracing.rs} (87%) diff --git a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs index c8075d6bb3a57..4f4b097f78ad4 100644 --- a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs +++ b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs @@ -856,69 +856,3 @@ where }, } } - -#[test] -fn test_gas_fields_serialize_as_hex() { - // Test CallTrace gas serialization - let call_trace = CallTrace { - from: H160::zero(), - gas: 21000, - gas_used: 20000, - to: H160::zero(), - input: Bytes::default(), - output: Bytes::default(), - error: None, - revert_reason: None, - calls: Vec::new(), - logs: Vec::new(), - value: None, - call_type: CallType::Call, - child_call_count: 0, - }; - let json = serde_json::to_string(&call_trace).expect("Serialization should succeed"); - assert!(json.contains(r#""gas":"0x5208""#), "gas should be hex: {}", json); - assert!(json.contains(r#""gasUsed":"0x4e20""#), "gas_used should be hex: {}", json); - - // Test ExecutionTrace gas serialization - let execution_trace = ExecutionTrace { - gas: 100000, - failed: false, - return_value: Bytes::default(), - struct_logs: Vec::new(), - }; - let json = serde_json::to_string(&execution_trace).expect("Serialization should succeed"); - assert!(json.contains(r#""gas":"0x186a0""#), "execution trace gas should be hex: {}", json); - - // Test ExecutionStep with EVM opcode gas serialization - let evm_execution_step = ExecutionStep { - gas: 50000, - gas_cost: 3, - depth: 1, - return_data: Bytes::default(), - error: None, - kind: ExecutionStepKind::EVMOpcode { - pc: 42, - op: 0x01, - stack: Vec::new(), - memory: Vec::new(), - storage: None, - }, - }; - let json = serde_json::to_string(&evm_execution_step).expect("Serialization should succeed"); - assert!(json.contains(r#""pc":"0x2a""#), "pc should be hex: {}", json); - assert!(json.contains(r#""gas":"0xc350""#), "evm execution step gas should be hex: {}", json); - assert!(json.contains(r#""gasCost":"0x3""#), "gas_cost should be hex: {}", json); - - // Test ExecutionStep with PVM syscall gas serialization - let pvm_execution_step = ExecutionStep { - gas: 60000, - gas_cost: 5, - depth: 1, - return_data: Bytes::default(), - error: None, - kind: ExecutionStepKind::PVMSyscall { op: 0 }, - }; - let json = serde_json::to_string(&pvm_execution_step).expect("Serialization should succeed"); - assert!(json.contains(r#""gas":"0xea60""#), "pvm execution step gas should be hex: {}", json); - assert!(json.contains(r#""gasCost":"0x5""#), "syscall step gas_cost should be hex: {}", json); -} diff --git a/substrate/frame/revive/src/evm/tracing.rs b/substrate/frame/revive/src/evm/tracing.rs index 92a7459370867..eafecf1b34692 100644 --- a/substrate/frame/revive/src/evm/tracing.rs +++ b/substrate/frame/revive/src/evm/tracing.rs @@ -26,8 +26,8 @@ pub use call_tracing::*; mod prestate_tracing; pub use prestate_tracing::*; -mod opcode_tracing; -pub use opcode_tracing::*; +mod execution_tracing; +pub use execution_tracing::*; /// A composite tracer. #[derive(derive_more::From, Debug)] diff --git a/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs b/substrate/frame/revive/src/evm/tracing/execution_tracing.rs similarity index 87% rename from substrate/frame/revive/src/evm/tracing/opcode_tracing.rs rename to substrate/frame/revive/src/evm/tracing/execution_tracing.rs index 7cb7c01501d11..a4798c675ca5e 100644 --- a/substrate/frame/revive/src/evm/tracing/opcode_tracing.rs +++ b/substrate/frame/revive/src/evm/tracing/execution_tracing.rs @@ -55,9 +55,6 @@ pub struct ExecutionTracer { /// The return value of the transaction. return_value: Bytes, - /// Pending step that's waiting for gas cost to be recorded. - pending_step: Option, - /// List of storage per call storages_per_call: Vec>, } @@ -73,7 +70,6 @@ impl ExecutionTracer { total_gas_used: 0, failed: false, return_value: Bytes::default(), - pending_step: None, storages_per_call: alloc::vec![Default::default()], } } @@ -98,7 +94,6 @@ impl Tracing for ExecutionTracer { } fn enter_opcode(&mut self, pc: u64, opcode: u8, trace_info: &dyn EVMFrameTraceInfo) { - // Check step limit - if exceeded, don't record anything if self.config.limit.map(|l| self.step_count >= l).unwrap_or(false) { return; } @@ -123,7 +118,7 @@ impl Tracing for ExecutionTracer { let step = ExecutionStep { gas: trace_info.gas_left(), - gas_cost: 0u64, // Will be set in exit_opcode + gas_cost: 0u64, // Will be set in exit_step depth: self.depth, return_data, error: None, @@ -136,14 +131,40 @@ impl Tracing for ExecutionTracer { }, }; - self.pending_step = Some(step); + self.steps.push(step); + self.step_count += 1; + } + + fn enter_ecall(&mut self, ecall: &'static str, trace_info: &dyn FrameTraceInfo) { + if self.config.limit.map(|l| self.step_count >= l).unwrap_or(false) { + return; + } + + // Extract return data if enabled + let return_data = if self.config.enable_return_data { + trace_info.last_frame_output() + } else { + crate::evm::Bytes::default() + }; + + let step = ExecutionStep { + gas: trace_info.gas_left(), + gas_cost: 0u64, // Will be set in exit_step + depth: self.depth, + return_data, + error: None, + kind: ExecutionStepKind::PVMSyscall { + op: lookup_syscall_index(ecall).unwrap_or_default(), + }, + }; + + self.steps.push(step); self.step_count += 1; } fn exit_step(&mut self, trace_info: &dyn FrameTraceInfo) { - if let Some(mut step) = self.pending_step.take() { + if let Some(step) = self.steps.last_mut() { step.gas_cost = step.gas.saturating_sub(trace_info.gas_left()); - self.steps.push(step); } } @@ -171,7 +192,6 @@ impl Tracing for ExecutionTracer { self.return_value = Bytes(output.data.to_vec()); } - // Set total gas used if this is the top-level call (depth 1, will become 0 after decrement) if self.depth == 1 { self.total_gas_used = gas_used; } @@ -205,7 +225,6 @@ impl Tracing for ExecutionTracer { return; } - // Get the last storage map for the current call depth if let Some(storage) = self.storages_per_call.last_mut() { let key_bytes = crate::evm::Bytes(key.unhashed().to_vec()); let value_bytes = crate::evm::Bytes( @@ -213,8 +232,7 @@ impl Tracing for ExecutionTracer { ); storage.insert(key_bytes, value_bytes); - // Set storage on the pending step if it's an EVM opcode - if let Some(ref mut step) = self.pending_step { + if let Some(step) = self.steps.last_mut() { if let ExecutionStepKind::EVMOpcode { storage: ref mut step_storage, .. } = step.kind { @@ -230,15 +248,13 @@ impl Tracing for ExecutionTracer { return; } - // Get the last storage map for the current call depth if let Some(storage) = self.storages_per_call.last_mut() { let key_bytes = crate::evm::Bytes(key.unhashed().to_vec()); storage.entry(key_bytes).or_insert_with(|| { crate::evm::Bytes(value.map(|v| v.to_vec()).unwrap_or_else(|| alloc::vec![0u8; 32])) }); - // Set storage on the pending step if it's an EVM opcode - if let Some(ref mut step) = self.pending_step { + if let Some(step) = self.steps.last_mut() { if let ExecutionStepKind::EVMOpcode { storage: ref mut step_storage, .. } = step.kind { @@ -247,32 +263,4 @@ impl Tracing for ExecutionTracer { } } } - - fn enter_ecall(&mut self, ecall: &'static str, trace_info: &dyn FrameTraceInfo) { - // Check step limit - if exceeded, don't record anything - if self.config.limit.map(|l| self.step_count >= l).unwrap_or(false) { - return; - } - - // Extract return data if enabled - let return_data = if self.config.enable_return_data { - trace_info.last_frame_output() - } else { - crate::evm::Bytes::default() - }; - - let step = ExecutionStep { - gas: trace_info.gas_left(), - gas_cost: 0u64, // Will be set in exit_ecall - depth: self.depth, - return_data, - error: None, - kind: ExecutionStepKind::PVMSyscall { - op: lookup_syscall_index(ecall).unwrap_or_default(), - }, - }; - - self.pending_step = Some(step); - self.step_count += 1; - } } From 350624643e73694d363a1768c97e587608ec2d8d Mon Sep 17 00:00:00 2001 From: pgherveou Date: Tue, 16 Dec 2025 13:56:46 +0100 Subject: [PATCH 56/76] add doc --- substrate/frame/revive/proc-macro/src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/substrate/frame/revive/proc-macro/src/lib.rs b/substrate/frame/revive/proc-macro/src/lib.rs index 95647f1f6c095..74a2eaf4f5acd 100644 --- a/substrate/frame/revive/proc-macro/src/lib.rs +++ b/substrate/frame/revive/proc-macro/src/lib.rs @@ -340,10 +340,12 @@ fn expand_env(def: &EnvDef) -> TokenStream2 { let lookup_syscall = expand_func_lookup(def); quote! { + /// Returns the list of all syscalls. pub fn all_syscalls() -> &'static [&'static [u8]] { #all_syscalls } + /// Returns the list of stable syscalls without unstable ones. pub fn list_syscalls(include_unstable: bool) -> &'static [&'static [u8]] { if include_unstable { #all_syscalls @@ -352,6 +354,7 @@ fn expand_env(def: &EnvDef) -> TokenStream2 { } } + /// Return the index of a syscall in the `all_syscalls()` list. pub fn lookup_syscall_index(name: &'static str) -> Option { #lookup_syscall } From 51bb52322896f70f36767fcae7bc1c10e71dfed1 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Tue, 16 Dec 2025 14:39:07 +0100 Subject: [PATCH 57/76] tweak --- .../frame/revive/dev-node/runtime/src/lib.rs | 705 +++++++------- substrate/frame/revive/proc-macro/src/lib.rs | 877 +++++++++--------- substrate/frame/revive/src/debug.rs | 18 +- .../src/evm/tracing/execution_tracing.rs | 2 +- substrate/frame/revive/src/tests/pvm.rs | 2 +- substrate/frame/revive/src/tests/sol.rs | 5 +- substrate/frame/revive/src/tracing.rs | 4 +- substrate/frame/revive/src/vm/evm.rs | 8 +- 8 files changed, 839 insertions(+), 782 deletions(-) diff --git a/substrate/frame/revive/dev-node/runtime/src/lib.rs b/substrate/frame/revive/dev-node/runtime/src/lib.rs index 046d16ea41083..d92c54a4d2d14 100644 --- a/substrate/frame/revive/dev-node/runtime/src/lib.rs +++ b/substrate/frame/revive/dev-node/runtime/src/lib.rs @@ -26,130 +26,135 @@ extern crate alloc; use alloc::{vec, vec::Vec}; use currency::*; use frame_support::weights::{ - constants::{BlockExecutionWeight, ExtrinsicBaseWeight, WEIGHT_REF_TIME_PER_SECOND}, - Weight, + constants::{BlockExecutionWeight, ExtrinsicBaseWeight, WEIGHT_REF_TIME_PER_SECOND}, + Weight, }; use frame_system::limits::BlockWeights; use pallet_revive::{ - evm::{ - fees::{BlockRatioFee, Info as FeeInfo}, - runtime::EthExtra, - }, - AccountId32Mapper, + evm::{ + fees::{BlockRatioFee, Info as FeeInfo}, + runtime::EthExtra, + }, + AccountId32Mapper, }; use pallet_transaction_payment::{ConstFeeMultiplier, FeeDetails, Multiplier, RuntimeDispatchInfo}; use polkadot_sdk::{ - polkadot_sdk_frame::{ - deps::sp_genesis_builder, - runtime::{apis, prelude::*}, - traits::Block as BlockT, - }, - *, + polkadot_sdk_frame::{ + deps::sp_genesis_builder, + runtime::{apis, prelude::*}, + traits::Block as BlockT, + }, + *, }; use sp_weights::ConstantMultiplier; pub use polkadot_sdk::{ - parachains_common::{AccountId, Balance, BlockNumber, Hash, Header, Nonce, Signature}, - polkadot_sdk_frame::runtime::types_common::OpaqueBlock, + parachains_common::{AccountId, Balance, BlockNumber, Hash, Header, Nonce, Signature}, + polkadot_sdk_frame::runtime::types_common::OpaqueBlock, }; pub mod currency { - use super::Balance; - pub const DOLLARS: Balance = 1_000_000_000_000; - pub const CENTS: Balance = DOLLARS / 100; - pub const MILLICENTS: Balance = CENTS / 1_000; + use super::Balance; + pub const DOLLARS: Balance = 1_000_000_000_000; + pub const CENTS: Balance = DOLLARS / 100; + pub const MILLICENTS: Balance = CENTS / 1_000; } /// Provides getters for genesis configuration presets. pub mod genesis_config_presets { - use super::*; - use crate::{ - currency::DOLLARS, sp_keyring::Sr25519Keyring, Balance, BalancesConfig, - RuntimeGenesisConfig, SudoConfig, - }; - - use alloc::{vec, vec::Vec}; - use serde_json::Value; - - pub const ENDOWMENT: Balance = 10_000_000_000_001 * DOLLARS; - - fn well_known_accounts() -> Vec { - Sr25519Keyring::well_known() - .map(|k| k.to_account_id()) - .chain([ - // subxt_signer::eth::dev::alith() - array_bytes::hex_n_into_unchecked( - "f24ff3a9cf04c71dbc94d0b566f7a27b94566caceeeeeeeeeeeeeeeeeeeeeeee", - ), - // subxt_signer::eth::dev::baltathar() - array_bytes::hex_n_into_unchecked( - "3cd0a705a2dc65e5b1e1205896baa2be8a07c6e0eeeeeeeeeeeeeeeeeeeeeeee", - ), - // subxt_signer::eth::dev::charleth() - array_bytes::hex_n_into_unchecked( - "798d4ba9baf0064ec19eb4f0a1a45785ae9d6dfceeeeeeeeeeeeeeeeeeeeeeee", - ), - // subxt_signer::eth::dev::dorothy() - array_bytes::hex_n_into_unchecked( - "773539d4ac0e786233d90a233654ccee26a613d9eeeeeeeeeeeeeeeeeeeeeeee", - ), - // subxt_signer::eth::dev::ethan() - array_bytes::hex_n_into_unchecked( - "ff64d3f6efe2317ee2807d223a0bdc4c0c49dfdbeeeeeeeeeeeeeeeeeeeeeeee", - ), - ]) - .collect::>() - } - - /// Returns a development genesis config preset. - pub fn development_config_genesis() -> Value { - frame_support::build_struct_json_patch!(RuntimeGenesisConfig { - balances: BalancesConfig { - balances: well_known_accounts() - .into_iter() - .map(|id| (id, ENDOWMENT)) - .collect::>(), - }, - sudo: SudoConfig { key: Some(Sr25519Keyring::Alice.to_account_id()) }, - }) - } - - /// Get the set of the available genesis config presets. - pub fn get_preset(id: &PresetId) -> Option> { - let patch = match id.as_ref() { - sp_genesis_builder::DEV_RUNTIME_PRESET => development_config_genesis(), - _ => return None, - }; - Some( - serde_json::to_string(&patch) - .expect("serialization to json is expected to work. qed.") - .into_bytes(), - ) - } - - /// List of supported presets. - pub fn preset_names() -> Vec { - vec![PresetId::from(sp_genesis_builder::DEV_RUNTIME_PRESET)] - } + use super::*; + use crate::{ + currency::DOLLARS, sp_keyring::Sr25519Keyring, Balance, BalancesConfig, + RuntimeGenesisConfig, SudoConfig, + }; + + use alloc::{vec, vec::Vec}; + use serde_json::Value; + + pub const ENDOWMENT: Balance = 10_000_000_000_001 * DOLLARS; + + fn well_known_accounts() -> Vec { + Sr25519Keyring::well_known() + .map(|k| k.to_account_id()) + .chain([ + // subxt_signer::eth::dev::alith() + array_bytes::hex_n_into_unchecked( + "f24ff3a9cf04c71dbc94d0b566f7a27b94566caceeeeeeeeeeeeeeeeeeeeeeee", + ), + // subxt_signer::eth::dev::baltathar() + array_bytes::hex_n_into_unchecked( + "3cd0a705a2dc65e5b1e1205896baa2be8a07c6e0eeeeeeeeeeeeeeeeeeeeeeee", + ), + // subxt_signer::eth::dev::charleth() + array_bytes::hex_n_into_unchecked( + "798d4ba9baf0064ec19eb4f0a1a45785ae9d6dfceeeeeeeeeeeeeeeeeeeeeeee", + ), + // subxt_signer::eth::dev::dorothy() + array_bytes::hex_n_into_unchecked( + "773539d4ac0e786233d90a233654ccee26a613d9eeeeeeeeeeeeeeeeeeeeeeee", + ), + // subxt_signer::eth::dev::ethan() + array_bytes::hex_n_into_unchecked( + "ff64d3f6efe2317ee2807d223a0bdc4c0c49dfdbeeeeeeeeeeeeeeeeeeeeeeee", + ), + ]) + .collect::>() + } + + /// Returns a development genesis config preset. + pub fn development_config_genesis() -> Value { + frame_support::build_struct_json_patch!(RuntimeGenesisConfig { + balances: BalancesConfig { + balances: well_known_accounts() + .into_iter() + .map(|id| (id, ENDOWMENT)) + .collect::>(), + }, + sudo: SudoConfig { + key: Some(Sr25519Keyring::Alice.to_account_id()) + }, + }) + } + + /// Get the set of the available genesis config presets. + pub fn get_preset(id: &PresetId) -> Option> { + let patch = match id.as_ref() { + sp_genesis_builder::DEV_RUNTIME_PRESET => development_config_genesis(), + _ => return None, + }; + Some( + serde_json::to_string(&patch) + .expect("serialization to json is expected to work. qed.") + .into_bytes(), + ) + } + + /// List of supported presets. + pub fn preset_names() -> Vec { + vec![PresetId::from(sp_genesis_builder::DEV_RUNTIME_PRESET)] + } } /// The runtime version. #[runtime_version] pub const VERSION: RuntimeVersion = RuntimeVersion { - spec_name: alloc::borrow::Cow::Borrowed("revive-dev-runtime"), - impl_name: alloc::borrow::Cow::Borrowed("revive-dev-runtime"), - authoring_version: 1, - spec_version: 0, - impl_version: 1, - apis: RUNTIME_API_VERSIONS, - transaction_version: 1, - system_version: 1, + spec_name: alloc::borrow::Cow::Borrowed("revive-dev-runtime"), + impl_name: alloc::borrow::Cow::Borrowed("revive-dev-runtime"), + authoring_version: 1, + spec_version: 0, + impl_version: 1, + apis: RUNTIME_API_VERSIONS, + transaction_version: 1, + system_version: 1, }; /// The version information used to identify this runtime when compiled natively. #[cfg(feature = "std")] pub fn native_version() -> NativeVersion { - NativeVersion { runtime_version: VERSION, can_author_with: Default::default() } + NativeVersion { + runtime_version: VERSION, + can_author_with: Default::default(), + } } /// The address format for describing accounts. @@ -158,29 +163,29 @@ pub type Address = sp_runtime::MultiAddress; pub type Block = sp_runtime::generic::Block; /// The transaction extensions that are added to the runtime. type TxExtension = ( - // Checks that the sender is not the zero address. - frame_system::CheckNonZeroSender, - // Checks that the runtime version is correct. - frame_system::CheckSpecVersion, - // Checks that the transaction version is correct. - frame_system::CheckTxVersion, - // Checks that the genesis hash is correct. - frame_system::CheckGenesis, - // Checks that the era is valid. - frame_system::CheckEra, - // Checks that the nonce is valid. - frame_system::CheckNonce, - // Checks that the weight is valid. - frame_system::CheckWeight, - // Ensures that the sender has enough funds to pay for the transaction - // and deducts the fee from the sender's account. - pallet_transaction_payment::ChargeTransactionPayment, - // Needs to be done after all extensions that rely on a signed origin. - pallet_revive::evm::tx_extension::SetOrigin, - // Reclaim the unused weight from the block using post dispatch information. - // It must be last in the pipeline in order to catch the refund in previous transaction - // extensions - frame_system::WeightReclaim, + // Checks that the sender is not the zero address. + frame_system::CheckNonZeroSender, + // Checks that the runtime version is correct. + frame_system::CheckSpecVersion, + // Checks that the transaction version is correct. + frame_system::CheckTxVersion, + // Checks that the genesis hash is correct. + frame_system::CheckGenesis, + // Checks that the era is valid. + frame_system::CheckEra, + // Checks that the nonce is valid. + frame_system::CheckNonce, + // Checks that the weight is valid. + frame_system::CheckWeight, + // Ensures that the sender has enough funds to pay for the transaction + // and deducts the fee from the sender's account. + pallet_transaction_payment::ChargeTransactionPayment, + // Needs to be done after all extensions that rely on a signed origin. + pallet_revive::evm::tx_extension::SetOrigin, + // Reclaim the unused weight from the block using post dispatch information. + // It must be last in the pipeline in order to catch the refund in previous transaction + // extensions + frame_system::WeightReclaim, ); /// Default extensions applied to Ethereum transactions. @@ -188,78 +193,78 @@ type TxExtension = ( pub struct EthExtraImpl; impl EthExtra for EthExtraImpl { - type Config = Runtime; - type Extension = TxExtension; - - fn get_eth_extension(nonce: u32, tip: Balance) -> Self::Extension { - ( - frame_system::CheckNonZeroSender::::new(), - frame_system::CheckSpecVersion::::new(), - frame_system::CheckTxVersion::::new(), - frame_system::CheckGenesis::::new(), - frame_system::CheckMortality::from(sp_runtime::generic::Era::Immortal), - frame_system::CheckNonce::::from(nonce), - frame_system::CheckWeight::::new(), - pallet_transaction_payment::ChargeTransactionPayment::::from(tip), - pallet_revive::evm::tx_extension::SetOrigin::::new_from_eth_transaction(), - frame_system::WeightReclaim::::new(), - ) - } + type Config = Runtime; + type Extension = TxExtension; + + fn get_eth_extension(nonce: u32, tip: Balance) -> Self::Extension { + ( + frame_system::CheckNonZeroSender::::new(), + frame_system::CheckSpecVersion::::new(), + frame_system::CheckTxVersion::::new(), + frame_system::CheckGenesis::::new(), + frame_system::CheckMortality::from(sp_runtime::generic::Era::Immortal), + frame_system::CheckNonce::::from(nonce), + frame_system::CheckWeight::::new(), + pallet_transaction_payment::ChargeTransactionPayment::::from(tip), + pallet_revive::evm::tx_extension::SetOrigin::::new_from_eth_transaction(), + frame_system::WeightReclaim::::new(), + ) + } } pub type UncheckedExtrinsic = - pallet_revive::evm::runtime::UncheckedExtrinsic; + pallet_revive::evm::runtime::UncheckedExtrinsic; type Executive = frame_executive::Executive< - Runtime, - Block, - frame_system::ChainContext, - Runtime, - AllPalletsWithSystem, + Runtime, + Block, + frame_system::ChainContext, + Runtime, + AllPalletsWithSystem, >; // Composes the runtime by adding all the used pallets and deriving necessary types. #[frame_construct_runtime] mod runtime { - /// The main runtime type. - #[runtime::runtime] - #[runtime::derive( - RuntimeCall, - RuntimeEvent, - RuntimeError, - RuntimeOrigin, - RuntimeFreezeReason, - RuntimeHoldReason, - RuntimeSlashReason, - RuntimeLockId, - RuntimeTask, - RuntimeViewFunction - )] - pub struct Runtime; - - /// Mandatory system pallet that should always be included in a FRAME runtime. - #[runtime::pallet_index(0)] - pub type System = frame_system::Pallet; - - /// Provides a way for consensus systems to set and check the onchain time. - #[runtime::pallet_index(1)] - pub type Timestamp = pallet_timestamp::Pallet; - - /// Provides the ability to keep track of balances. - #[runtime::pallet_index(2)] - pub type Balances = pallet_balances::Pallet; - - /// Provides a way to execute privileged functions. - #[runtime::pallet_index(3)] - pub type Sudo = pallet_sudo::Pallet; - - /// Provides the ability to charge for extrinsic execution. - #[runtime::pallet_index(4)] - pub type TransactionPayment = pallet_transaction_payment::Pallet; - - /// Provides the ability to execute Smart Contracts. - #[runtime::pallet_index(5)] - pub type Revive = pallet_revive::Pallet; + /// The main runtime type. + #[runtime::runtime] + #[runtime::derive( + RuntimeCall, + RuntimeEvent, + RuntimeError, + RuntimeOrigin, + RuntimeFreezeReason, + RuntimeHoldReason, + RuntimeSlashReason, + RuntimeLockId, + RuntimeTask, + RuntimeViewFunction + )] + pub struct Runtime; + + /// Mandatory system pallet that should always be included in a FRAME runtime. + #[runtime::pallet_index(0)] + pub type System = frame_system::Pallet; + + /// Provides a way for consensus systems to set and check the onchain time. + #[runtime::pallet_index(1)] + pub type Timestamp = pallet_timestamp::Pallet; + + /// Provides the ability to keep track of balances. + #[runtime::pallet_index(2)] + pub type Balances = pallet_balances::Pallet; + + /// Provides a way to execute privileged functions. + #[runtime::pallet_index(3)] + pub type Sudo = pallet_sudo::Pallet; + + /// Provides the ability to charge for extrinsic execution. + #[runtime::pallet_index(4)] + pub type TransactionPayment = pallet_transaction_payment::Pallet; + + /// Provides the ability to execute Smart Contracts. + #[runtime::pallet_index(5)] + pub type Revive = pallet_revive::Pallet; } /// We assume that ~10% of the block weight is consumed by `on_initialize` handlers. @@ -270,51 +275,51 @@ const AVERAGE_ON_INITIALIZE_RATIO: Perbill = Perbill::from_percent(10); const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75); /// We allow for 2 seconds of compute with a 6 second average block time, with maximum proof size. const MAXIMUM_BLOCK_WEIGHT: Weight = - Weight::from_parts(WEIGHT_REF_TIME_PER_SECOND.saturating_mul(2), u64::MAX); + Weight::from_parts(WEIGHT_REF_TIME_PER_SECOND.saturating_mul(2), u64::MAX); parameter_types! { - pub const Version: RuntimeVersion = VERSION; - pub RuntimeBlockWeights: BlockWeights = BlockWeights::builder() - .base_block(BlockExecutionWeight::get()) - .for_class(DispatchClass::all(), |weights| { - weights.base_extrinsic = ExtrinsicBaseWeight::get(); - }) - .for_class(DispatchClass::Normal, |weights| { - weights.max_total = Some(NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT); - }) - .for_class(DispatchClass::Operational, |weights| { - weights.max_total = Some(MAXIMUM_BLOCK_WEIGHT); - // Operational transactions have some extra reserved space, so that they - // are included even if block reached `MAXIMUM_BLOCK_WEIGHT`. - weights.reserved = Some( - MAXIMUM_BLOCK_WEIGHT - NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT - ); - }) - .avg_block_initialization(AVERAGE_ON_INITIALIZE_RATIO) - .build_or_panic(); + pub const Version: RuntimeVersion = VERSION; + pub RuntimeBlockWeights: BlockWeights = BlockWeights::builder() + .base_block(BlockExecutionWeight::get()) + .for_class(DispatchClass::all(), |weights| { + weights.base_extrinsic = ExtrinsicBaseWeight::get(); + }) + .for_class(DispatchClass::Normal, |weights| { + weights.max_total = Some(NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT); + }) + .for_class(DispatchClass::Operational, |weights| { + weights.max_total = Some(MAXIMUM_BLOCK_WEIGHT); + // Operational transactions have some extra reserved space, so that they + // are included even if block reached `MAXIMUM_BLOCK_WEIGHT`. + weights.reserved = Some( + MAXIMUM_BLOCK_WEIGHT - NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT + ); + }) + .avg_block_initialization(AVERAGE_ON_INITIALIZE_RATIO) + .build_or_panic(); } /// Implements the types required for the system pallet. #[derive_impl(frame_system::config_preludes::SolochainDefaultConfig)] impl frame_system::Config for Runtime { - type Block = Block; - type Version = Version; - type AccountId = AccountId; - type Hash = Hash; - type Nonce = Nonce; - type AccountData = pallet_balances::AccountData<::Balance>; + type Block = Block; + type Version = Version; + type AccountId = AccountId; + type Hash = Hash; + type Nonce = Nonce; + type AccountData = pallet_balances::AccountData<::Balance>; } parameter_types! { - pub const ExistentialDeposit: Balance = CENTS; + pub const ExistentialDeposit: Balance = CENTS; } // Implements the types required for the balances pallet. #[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] impl pallet_balances::Config for Runtime { - type AccountStore = System; - type Balance = Balance; - type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type Balance = Balance; + type ExistentialDeposit = ExistentialDeposit; } // Implements the types required for the sudo pallet. @@ -326,157 +331,157 @@ impl pallet_sudo::Config for Runtime {} impl pallet_timestamp::Config for Runtime {} parameter_types! { - pub const TransactionByteFee: Balance = 10 * MILLICENTS; - pub FeeMultiplier: Multiplier = Multiplier::one(); + pub const TransactionByteFee: Balance = 10 * MILLICENTS; + pub FeeMultiplier: Multiplier = Multiplier::one(); } // Implements the types required for the transaction payment pallet. #[derive_impl(pallet_transaction_payment::config_preludes::TestDefaultConfig)] impl pallet_transaction_payment::Config for Runtime { - type OnChargeTransaction = pallet_transaction_payment::FungibleAdapter; - type WeightToFee = BlockRatioFee<1, 1, Self>; - type LengthToFee = ConstantMultiplier; - type FeeMultiplierUpdate = ConstFeeMultiplier; + type OnChargeTransaction = pallet_transaction_payment::FungibleAdapter; + type WeightToFee = BlockRatioFee<1, 1, Self>; + type LengthToFee = ConstantMultiplier; + type FeeMultiplierUpdate = ConstFeeMultiplier; } parameter_types! { - pub CodeHashLockupDepositPercent: Perbill = Perbill::from_percent(30); + pub CodeHashLockupDepositPercent: Perbill = Perbill::from_percent(30); } #[derive_impl(pallet_revive::config_preludes::TestDefaultConfig)] impl pallet_revive::Config for Runtime { - type AddressMapper = AccountId32Mapper; - type ChainId = ConstU64<420_420_420>; - type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; - type Balance = Balance; - type Currency = Balances; - type NativeToEthRatio = ConstU32<1_000_000>; - type UploadOrigin = EnsureSigned; - type InstantiateOrigin = EnsureSigned; - type Time = Timestamp; - type FeeInfo = FeeInfo; - type DebugEnabled = ConstBool; - type GasScale = ConstU32<50000>; + type AddressMapper = AccountId32Mapper; + type ChainId = ConstU64<420_420_420>; + type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; + type Balance = Balance; + type Currency = Balances; + type NativeToEthRatio = ConstU32<1_000_000>; + type UploadOrigin = EnsureSigned; + type InstantiateOrigin = EnsureSigned; + type Time = Timestamp; + type FeeInfo = FeeInfo; + type DebugEnabled = ConstBool; + type GasScale = ConstU32<50000>; } pallet_revive::impl_runtime_apis_plus_revive_traits!( - Runtime, - Revive, - Executive, - EthExtraImpl, - - impl apis::Core for Runtime { - fn version() -> RuntimeVersion { - VERSION - } - - fn execute_block(block: ::LazyBlock) { - Executive::execute_block(block) - } - - fn initialize_block(header: &Header) -> ExtrinsicInclusionMode { - Executive::initialize_block(header) - } - } - - impl apis::Metadata for Runtime { - fn metadata() -> OpaqueMetadata { - OpaqueMetadata::new(Runtime::metadata().into()) - } - - fn metadata_at_version(version: u32) -> Option { - Runtime::metadata_at_version(version) - } - - fn metadata_versions() -> Vec { - Runtime::metadata_versions() - } - } - - impl apis::BlockBuilder for Runtime { - fn apply_extrinsic(extrinsic: ExtrinsicFor) -> ApplyExtrinsicResult { - Executive::apply_extrinsic(extrinsic) - } - - fn finalize_block() -> HeaderFor { - Executive::finalize_block() - } - - fn inherent_extrinsics(data: InherentData) -> Vec> { - data.create_extrinsics() - } - - fn check_inherents( - block: ::LazyBlock, - data: InherentData, - ) -> CheckInherentsResult { - data.check_extrinsics(&block) - } - } - - impl apis::TaggedTransactionQueue for Runtime { - fn validate_transaction( - source: TransactionSource, - tx: ExtrinsicFor, - block_hash: ::Hash, - ) -> TransactionValidity { - Executive::validate_transaction(source, tx, block_hash) - } - } - - impl apis::OffchainWorkerApi for Runtime { - fn offchain_worker(header: &HeaderFor) { - Executive::offchain_worker(header) - } - } - - impl apis::SessionKeys for Runtime { - fn generate_session_keys(_seed: Option>) -> Vec { - Default::default() - } - - fn decode_session_keys( - _encoded: Vec, - ) -> Option, apis::KeyTypeId)>> { - Default::default() - } - } - - impl apis::AccountNonceApi for Runtime { - fn account_nonce(account: AccountId) -> Nonce { - System::account_nonce(account) - } - } - - impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi< - Block, - Balance, - > for Runtime { - fn query_info(uxt: ExtrinsicFor, len: u32) -> RuntimeDispatchInfo { - TransactionPayment::query_info(uxt, len) - } - fn query_fee_details(uxt: ExtrinsicFor, len: u32) -> FeeDetails { - TransactionPayment::query_fee_details(uxt, len) - } - fn query_weight_to_fee(weight: Weight) -> Balance { - TransactionPayment::weight_to_fee(weight) - } - fn query_length_to_fee(length: u32) -> Balance { - TransactionPayment::length_to_fee(length) - } - } - - impl apis::GenesisBuilder for Runtime { - fn build_state(config: Vec) -> sp_genesis_builder::Result { - build_state::(config) - } - - fn get_preset(id: &Option) -> Option> { - get_preset::(id, self::genesis_config_presets::get_preset) - } - - fn preset_names() -> Vec { - self::genesis_config_presets::preset_names() - } - } + Runtime, + Revive, + Executive, + EthExtraImpl, + + impl apis::Core for Runtime { + fn version() -> RuntimeVersion { + VERSION + } + + fn execute_block(block: ::LazyBlock) { + Executive::execute_block(block) + } + + fn initialize_block(header: &Header) -> ExtrinsicInclusionMode { + Executive::initialize_block(header) + } + } + + impl apis::Metadata for Runtime { + fn metadata() -> OpaqueMetadata { + OpaqueMetadata::new(Runtime::metadata().into()) + } + + fn metadata_at_version(version: u32) -> Option { + Runtime::metadata_at_version(version) + } + + fn metadata_versions() -> Vec { + Runtime::metadata_versions() + } + } + + impl apis::BlockBuilder for Runtime { + fn apply_extrinsic(extrinsic: ExtrinsicFor) -> ApplyExtrinsicResult { + Executive::apply_extrinsic(extrinsic) + } + + fn finalize_block() -> HeaderFor { + Executive::finalize_block() + } + + fn inherent_extrinsics(data: InherentData) -> Vec> { + data.create_extrinsics() + } + + fn check_inherents( + block: ::LazyBlock, + data: InherentData, + ) -> CheckInherentsResult { + data.check_extrinsics(&block) + } + } + + impl apis::TaggedTransactionQueue for Runtime { + fn validate_transaction( + source: TransactionSource, + tx: ExtrinsicFor, + block_hash: ::Hash, + ) -> TransactionValidity { + Executive::validate_transaction(source, tx, block_hash) + } + } + + impl apis::OffchainWorkerApi for Runtime { + fn offchain_worker(header: &HeaderFor) { + Executive::offchain_worker(header) + } + } + + impl apis::SessionKeys for Runtime { + fn generate_session_keys(_seed: Option>) -> Vec { + Default::default() + } + + fn decode_session_keys( + _encoded: Vec, + ) -> Option, apis::KeyTypeId)>> { + Default::default() + } + } + + impl apis::AccountNonceApi for Runtime { + fn account_nonce(account: AccountId) -> Nonce { + System::account_nonce(account) + } + } + + impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi< + Block, + Balance, + > for Runtime { + fn query_info(uxt: ExtrinsicFor, len: u32) -> RuntimeDispatchInfo { + TransactionPayment::query_info(uxt, len) + } + fn query_fee_details(uxt: ExtrinsicFor, len: u32) -> FeeDetails { + TransactionPayment::query_fee_details(uxt, len) + } + fn query_weight_to_fee(weight: Weight) -> Balance { + TransactionPayment::weight_to_fee(weight) + } + fn query_length_to_fee(length: u32) -> Balance { + TransactionPayment::length_to_fee(length) + } + } + + impl apis::GenesisBuilder for Runtime { + fn build_state(config: Vec) -> sp_genesis_builder::Result { + build_state::(config) + } + + fn get_preset(id: &Option) -> Option> { + get_preset::(id, self::genesis_config_presets::get_preset) + } + + fn preset_names() -> Vec { + self::genesis_config_presets::preset_names() + } + } ); diff --git a/substrate/frame/revive/proc-macro/src/lib.rs b/substrate/frame/revive/proc-macro/src/lib.rs index 74a2eaf4f5acd..8ce6216ddd568 100644 --- a/substrate/frame/revive/proc-macro/src/lib.rs +++ b/substrate/frame/revive/proc-macro/src/lib.rs @@ -27,13 +27,13 @@ use syn::{parse_quote, punctuated::Punctuated, spanned::Spanned, token::Comma, F #[proc_macro_attribute] pub fn unstable_hostfn(_attr: TokenStream, item: TokenStream) -> TokenStream { - let input = syn::parse_macro_input!(item as syn::Item); - let expanded = quote! { - #[cfg(feature = "unstable-hostfn")] - #[cfg_attr(docsrs, doc(cfg(feature = "unstable-hostfn")))] - #input - }; - expanded.into() + let input = syn::parse_macro_input!(item as syn::Item); + let expanded = quote! { + #[cfg(feature = "unstable-hostfn")] + #[cfg_attr(docsrs, doc(cfg(feature = "unstable-hostfn")))] + #input + }; + expanded.into() } /// Defines a host functions set that can be imported by contract polkavm code. @@ -48,283 +48,299 @@ pub fn unstable_hostfn(_attr: TokenStream, item: TokenStream) -> TokenStream { /// corruption or unexpected results within the contract. #[proc_macro_attribute] pub fn define_env(attr: TokenStream, item: TokenStream) -> TokenStream { - if !attr.is_empty() { - let msg = r#"Invalid `define_env` attribute macro: expected no attributes: + if !attr.is_empty() { + let msg = r#"Invalid `define_env` attribute macro: expected no attributes: - `#[define_env]`"#; - let span = TokenStream2::from(attr).span(); - return syn::Error::new(span, msg).to_compile_error().into() - } + let span = TokenStream2::from(attr).span(); + return syn::Error::new(span, msg).to_compile_error().into(); + } - let item = syn::parse_macro_input!(item as syn::ItemMod); + let item = syn::parse_macro_input!(item as syn::ItemMod); - match EnvDef::try_from(item) { - Ok(mut def) => expand_env(&mut def).into(), - Err(e) => e.to_compile_error().into(), - } + match EnvDef::try_from(item) { + Ok(mut def) => expand_env(&mut def).into(), + Err(e) => e.to_compile_error().into(), + } } /// Parsed environment definition. struct EnvDef { - host_funcs: Vec, + host_funcs: Vec, } /// Parsed host function definition. struct HostFn { - item: syn::ItemFn, - is_stable: bool, - name: String, - returns: HostFnReturn, - cfg: Option, + item: syn::ItemFn, + is_stable: bool, + name: String, + returns: HostFnReturn, + cfg: Option, } enum HostFnReturn { - Unit, - U32, - U64, - ReturnCode, + Unit, + U32, + U64, + ReturnCode, } impl HostFnReturn { - fn map_output(&self) -> TokenStream2 { - match self { - Self::Unit => quote! { |_| None }, - _ => quote! { |ret_val| Some(ret_val.into()) }, - } - } - - fn success_type(&self) -> syn::ReturnType { - match self { - Self::Unit => syn::ReturnType::Default, - Self::U32 => parse_quote! { -> u32 }, - Self::U64 => parse_quote! { -> u64 }, - Self::ReturnCode => parse_quote! { -> ReturnErrorCode }, - } - } + fn map_output(&self) -> TokenStream2 { + match self { + Self::Unit => quote! { |_| None }, + _ => quote! { |ret_val| Some(ret_val.into()) }, + } + } + + fn success_type(&self) -> syn::ReturnType { + match self { + Self::Unit => syn::ReturnType::Default, + Self::U32 => parse_quote! { -> u32 }, + Self::U64 => parse_quote! { -> u64 }, + Self::ReturnCode => parse_quote! { -> ReturnErrorCode }, + } + } } impl EnvDef { - pub fn try_from(item: syn::ItemMod) -> syn::Result { - let span = item.span(); - let err = |msg| syn::Error::new(span, msg); - let items = &item - .content - .as_ref() - .ok_or(err("Invalid environment definition, expected `mod` to be inlined."))? - .1; - - let extract_fn = |i: &syn::Item| match i { - syn::Item::Fn(i_fn) => Some(i_fn.clone()), - _ => None, - }; - - let host_funcs = items - .iter() - .filter_map(extract_fn) - .map(HostFn::try_from) - .collect::, _>>()?; - - Ok(Self { host_funcs }) - } + pub fn try_from(item: syn::ItemMod) -> syn::Result { + let span = item.span(); + let err = |msg| syn::Error::new(span, msg); + let items = &item + .content + .as_ref() + .ok_or(err( + "Invalid environment definition, expected `mod` to be inlined.", + ))? + .1; + + let extract_fn = |i: &syn::Item| match i { + syn::Item::Fn(i_fn) => Some(i_fn.clone()), + _ => None, + }; + + let host_funcs = items + .iter() + .filter_map(extract_fn) + .map(HostFn::try_from) + .collect::, _>>()?; + + Ok(Self { host_funcs }) + } } impl HostFn { - pub fn try_from(mut item: syn::ItemFn) -> syn::Result { - let err = |span, msg| { - let msg = format!("Invalid host function definition.\n{}", msg); - syn::Error::new(span, msg) - }; - - // process attributes - let msg = "Only #[stable], #[cfg] and #[mutating] attributes are allowed."; - let span = item.span(); - let mut attrs = item.attrs.clone(); - attrs.retain(|a| !a.path().is_ident("doc")); - let mut is_stable = false; - let mut mutating = false; - let mut cfg = None; - while let Some(attr) = attrs.pop() { - let ident = attr.path().get_ident().ok_or(err(span, msg))?.to_string(); - match ident.as_str() { - "stable" => { - if is_stable { - return Err(err(span, "#[stable] can only be specified once")) - } - is_stable = true; - }, - "mutating" => { - if mutating { - return Err(err(span, "#[mutating] can only be specified once")) - } - mutating = true; - }, - "cfg" => { - if cfg.is_some() { - return Err(err(span, "#[cfg] can only be specified once")) - } - cfg = Some(attr); - }, - id => return Err(err(span, &format!("Unsupported attribute \"{id}\". {msg}"))), - } - } - - if mutating { - let stmt = syn::parse_quote! { - if self.ext().is_read_only() { - return Err(Error::::StateChangeDenied.into()); - } - }; - item.block.stmts.insert(0, stmt); - } - - let name = item.sig.ident.to_string(); - - let msg = "Every function must start with these two parameters: &mut self, memory: &mut M"; - let special_args = item - .sig - .inputs - .iter() - .take(2) - .enumerate() - .map(|(i, arg)| is_valid_special_arg(i, arg)) - .fold(0u32, |acc, valid| if valid { acc + 1 } else { acc }); - - if special_args != 2 { - return Err(err(span, msg)) - } - - // process return type - let msg = r#"Should return one of the following: + pub fn try_from(mut item: syn::ItemFn) -> syn::Result { + let err = |span, msg| { + let msg = format!("Invalid host function definition.\n{}", msg); + syn::Error::new(span, msg) + }; + + // process attributes + let msg = "Only #[stable], #[cfg] and #[mutating] attributes are allowed."; + let span = item.span(); + let mut attrs = item.attrs.clone(); + attrs.retain(|a| !a.path().is_ident("doc")); + let mut is_stable = false; + let mut mutating = false; + let mut cfg = None; + while let Some(attr) = attrs.pop() { + let ident = attr.path().get_ident().ok_or(err(span, msg))?.to_string(); + match ident.as_str() { + "stable" => { + if is_stable { + return Err(err(span, "#[stable] can only be specified once")); + } + is_stable = true; + } + "mutating" => { + if mutating { + return Err(err(span, "#[mutating] can only be specified once")); + } + mutating = true; + } + "cfg" => { + if cfg.is_some() { + return Err(err(span, "#[cfg] can only be specified once")); + } + cfg = Some(attr); + } + id => return Err(err(span, &format!("Unsupported attribute \"{id}\". {msg}"))), + } + } + + if mutating { + let stmt = syn::parse_quote! { + if self.ext().is_read_only() { + return Err(Error::::StateChangeDenied.into()); + } + }; + item.block.stmts.insert(0, stmt); + } + + let name = item.sig.ident.to_string(); + + let msg = "Every function must start with these two parameters: &mut self, memory: &mut M"; + let special_args = item + .sig + .inputs + .iter() + .take(2) + .enumerate() + .map(|(i, arg)| is_valid_special_arg(i, arg)) + .fold(0u32, |acc, valid| if valid { acc + 1 } else { acc }); + + if special_args != 2 { + return Err(err(span, msg)); + } + + // process return type + let msg = r#"Should return one of the following: - Result<(), TrapReason>, - Result, - Result, - Result"#; - let ret_ty = match item.clone().sig.output { - syn::ReturnType::Type(_, ty) => Ok(ty.clone()), - _ => Err(err(span, &msg)), - }?; - match *ret_ty { - syn::Type::Path(tp) => { - let result = &tp.path.segments.last().ok_or(err(span, &msg))?; - let (id, span) = (result.ident.to_string(), result.ident.span()); - id.eq(&"Result".to_string()).then_some(()).ok_or(err(span, &msg))?; - - match &result.arguments { - syn::PathArguments::AngleBracketed(group) => { - if group.args.len() != 2 { - return Err(err(span, &msg)) - }; - - let arg2 = group.args.last().ok_or(err(span, &msg))?; - - let err_ty = match arg2 { - syn::GenericArgument::Type(ty) => Ok(ty.clone()), - _ => Err(err(arg2.span(), &msg)), - }?; - - match err_ty { - syn::Type::Path(tp) => Ok(tp - .path - .segments - .first() - .ok_or(err(arg2.span(), &msg))? - .ident - .to_string()), - _ => Err(err(tp.span(), &msg)), - }? - .eq("TrapReason") - .then_some(()) - .ok_or(err(span, &msg))?; - - let arg1 = group.args.first().ok_or(err(span, &msg))?; - let ok_ty = match arg1 { - syn::GenericArgument::Type(ty) => Ok(ty.clone()), - _ => Err(err(arg1.span(), &msg)), - }?; - let ok_ty_str = match ok_ty { - syn::Type::Path(tp) => Ok(tp - .path - .segments - .first() - .ok_or(err(arg1.span(), &msg))? - .ident - .to_string()), - syn::Type::Tuple(tt) => { - if !tt.elems.is_empty() { - return Err(err(arg1.span(), &msg)) - }; - Ok("()".to_string()) - }, - _ => Err(err(ok_ty.span(), &msg)), - }?; - let returns = match ok_ty_str.as_str() { - "()" => Ok(HostFnReturn::Unit), - "u32" => Ok(HostFnReturn::U32), - "u64" => Ok(HostFnReturn::U64), - "ReturnErrorCode" => Ok(HostFnReturn::ReturnCode), - _ => Err(err(arg1.span(), &msg)), - }?; - - Ok(Self { item, is_stable, name, returns, cfg }) - }, - _ => Err(err(span, &msg)), - } - }, - _ => Err(err(span, &msg)), - } - } + let ret_ty = match item.clone().sig.output { + syn::ReturnType::Type(_, ty) => Ok(ty.clone()), + _ => Err(err(span, &msg)), + }?; + match *ret_ty { + syn::Type::Path(tp) => { + let result = &tp.path.segments.last().ok_or(err(span, &msg))?; + let (id, span) = (result.ident.to_string(), result.ident.span()); + id.eq(&"Result".to_string()) + .then_some(()) + .ok_or(err(span, &msg))?; + + match &result.arguments { + syn::PathArguments::AngleBracketed(group) => { + if group.args.len() != 2 { + return Err(err(span, &msg)); + }; + + let arg2 = group.args.last().ok_or(err(span, &msg))?; + + let err_ty = match arg2 { + syn::GenericArgument::Type(ty) => Ok(ty.clone()), + _ => Err(err(arg2.span(), &msg)), + }?; + + match err_ty { + syn::Type::Path(tp) => Ok(tp + .path + .segments + .first() + .ok_or(err(arg2.span(), &msg))? + .ident + .to_string()), + _ => Err(err(tp.span(), &msg)), + }? + .eq("TrapReason") + .then_some(()) + .ok_or(err(span, &msg))?; + + let arg1 = group.args.first().ok_or(err(span, &msg))?; + let ok_ty = match arg1 { + syn::GenericArgument::Type(ty) => Ok(ty.clone()), + _ => Err(err(arg1.span(), &msg)), + }?; + let ok_ty_str = match ok_ty { + syn::Type::Path(tp) => Ok(tp + .path + .segments + .first() + .ok_or(err(arg1.span(), &msg))? + .ident + .to_string()), + syn::Type::Tuple(tt) => { + if !tt.elems.is_empty() { + return Err(err(arg1.span(), &msg)); + }; + Ok("()".to_string()) + } + _ => Err(err(ok_ty.span(), &msg)), + }?; + let returns = match ok_ty_str.as_str() { + "()" => Ok(HostFnReturn::Unit), + "u32" => Ok(HostFnReturn::U32), + "u64" => Ok(HostFnReturn::U64), + "ReturnErrorCode" => Ok(HostFnReturn::ReturnCode), + _ => Err(err(arg1.span(), &msg)), + }?; + + Ok(Self { + item, + is_stable, + name, + returns, + cfg, + }) + } + _ => Err(err(span, &msg)), + } + } + _ => Err(err(span, &msg)), + } + } } fn is_valid_special_arg(idx: usize, arg: &FnArg) -> bool { - match (idx, arg) { - (0, FnArg::Receiver(rec)) => rec.reference.is_some() && rec.mutability.is_some(), - (1, FnArg::Typed(pat)) => { - let ident = - if let syn::Pat::Ident(ref ident) = *pat.pat { &ident.ident } else { return false }; - if !(ident == "memory" || ident == "_memory") { - return false - } - matches!(*pat.ty, syn::Type::Reference(_)) - }, - _ => false, - } + match (idx, arg) { + (0, FnArg::Receiver(rec)) => rec.reference.is_some() && rec.mutability.is_some(), + (1, FnArg::Typed(pat)) => { + let ident = if let syn::Pat::Ident(ref ident) = *pat.pat { + &ident.ident + } else { + return false; + }; + if !(ident == "memory" || ident == "_memory") { + return false; + } + matches!(*pat.ty, syn::Type::Reference(_)) + } + _ => false, + } } fn arg_decoder<'a, P, I>(param_names: P, param_types: I) -> TokenStream2 where - P: Iterator> + Clone, - I: Iterator> + Clone, + P: Iterator> + Clone, + I: Iterator> + Clone, { - const ALLOWED_REGISTERS: usize = 6; - - // too many arguments - if param_names.clone().count() > ALLOWED_REGISTERS { - panic!("Syscalls take a maximum of {ALLOWED_REGISTERS} arguments"); - } - - // all of them take one register but we truncate them before passing into the function - // it is important to not allow any type which has illegal bit patterns like 'bool' - if !param_types.clone().all(|ty| { - let syn::Type::Path(path) = &**ty else { - panic!("Type needs to be path"); - }; - let Some(ident) = path.path.get_ident() else { - panic!("Type needs to be ident"); - }; - matches!(ident.to_string().as_ref(), "u8" | "u16" | "u32" | "u64") - }) { - panic!("Only primitive unsigned integers are allowed as arguments to syscalls"); - } - - // one argument per register - let bindings = param_names.zip(param_types).enumerate().map(|(idx, (name, ty))| { - let reg = quote::format_ident!("__a{}__", idx); - quote! { - let #name = #reg as #ty; - } - }); - quote! { - #( #bindings )* - } + const ALLOWED_REGISTERS: usize = 6; + + // too many arguments + if param_names.clone().count() > ALLOWED_REGISTERS { + panic!("Syscalls take a maximum of {ALLOWED_REGISTERS} arguments"); + } + + // all of them take one register but we truncate them before passing into the function + // it is important to not allow any type which has illegal bit patterns like 'bool' + if !param_types.clone().all(|ty| { + let syn::Type::Path(path) = &**ty else { + panic!("Type needs to be path"); + }; + let Some(ident) = path.path.get_ident() else { + panic!("Type needs to be ident"); + }; + matches!(ident.to_string().as_ref(), "u8" | "u16" | "u32" | "u64") + }) { + panic!("Only primitive unsigned integers are allowed as arguments to syscalls"); + } + + // one argument per register + let bindings = param_names + .zip(param_types) + .enumerate() + .map(|(idx, (name, ty))| { + let reg = quote::format_ident!("__a{}__", idx); + quote! { + let #name = #reg as #ty; + } + }); + quote! { + #( #bindings )* + } } /// Expands environment definition. @@ -332,67 +348,67 @@ where /// - implementations of the host functions to be added to the polkavm runtime environment (see /// `expand_impls()`). fn expand_env(def: &EnvDef) -> TokenStream2 { - let impls = expand_functions(def); - let bench_impls = expand_bench_functions(def); - let docs = expand_func_doc(def); - let stable_syscalls = expand_func_list(def, false); - let all_syscalls = expand_func_list(def, true); - let lookup_syscall = expand_func_lookup(def); - - quote! { - /// Returns the list of all syscalls. - pub fn all_syscalls() -> &'static [&'static [u8]] { - #all_syscalls - } - - /// Returns the list of stable syscalls without unstable ones. - pub fn list_syscalls(include_unstable: bool) -> &'static [&'static [u8]] { - if include_unstable { - #all_syscalls - } else { - #stable_syscalls - } - } - - /// Return the index of a syscall in the `all_syscalls()` list. - pub fn lookup_syscall_index(name: &'static str) -> Option { - #lookup_syscall - } - - impl<'a, E: Ext, M: PolkaVmInstance> Runtime<'a, E, M> { - fn handle_ecall( - &mut self, - memory: &mut M, - __syscall_symbol__: &[u8], - ) -> Result, TrapReason> - { - #impls - } - } - - #[cfg(feature = "runtime-benchmarks")] - impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { - #bench_impls - } - - /// Documentation of the syscalls (host functions) available to contracts. - /// - /// Each of the functions in this trait represent a function that is callable - /// by the contract. Guests use the function name as the import symbol. - /// - /// # Note - /// - /// This module is not meant to be used by any code. Rather, it is meant to be - /// consumed by humans through rustdoc. - #[cfg(doc)] - pub trait SyscallDoc { - #docs - } - } + let impls = expand_functions(def); + let bench_impls = expand_bench_functions(def); + let docs = expand_func_doc(def); + let stable_syscalls = expand_func_list(def, false); + let all_syscalls = expand_func_list(def, true); + let lookup_syscall = expand_func_lookup(def); + + quote! { + /// Returns the list of all syscalls. + pub fn all_syscalls() -> &'static [&'static [u8]] { + #all_syscalls + } + + /// Returns the list of stable syscalls without unstable ones. + pub fn list_syscalls(include_unstable: bool) -> &'static [&'static [u8]] { + if include_unstable { + #all_syscalls + } else { + #stable_syscalls + } + } + + /// Return the index of a syscall in the `all_syscalls()` list. + pub fn lookup_syscall_index(name: &'static str) -> Option { + #lookup_syscall + } + + impl<'a, E: Ext, M: PolkaVmInstance> Runtime<'a, E, M> { + fn handle_ecall( + &mut self, + memory: &mut M, + __syscall_symbol__: &[u8], + ) -> Result, TrapReason> + { + #impls + } + } + + #[cfg(feature = "runtime-benchmarks")] + impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { + #bench_impls + } + + /// Documentation of the syscalls (host functions) available to contracts. + /// + /// Each of the functions in this trait represent a function that is callable + /// by the contract. Guests use the function name as the import symbol. + /// + /// # Note + /// + /// This module is not meant to be used by any code. Rather, it is meant to be + /// consumed by humans through rustdoc. + #[cfg(doc)] + pub trait SyscallDoc { + #docs + } + } } fn expand_functions(def: &EnvDef) -> TokenStream2 { - let impls = def.host_funcs.iter().map(|f| { + let impls = def.host_funcs.iter().map(|f| { // skip the self and memory argument let params = f.item.sig.inputs.iter().skip(2); let param_names = params.clone().filter_map(|arg| { @@ -434,13 +450,21 @@ fn expand_functions(def: &EnvDef) -> TokenStream2 { let trace_fmt_str = format!("{}({}) = {{:?}} weight_consumed: {{:?}}", name, params_fmt_str); quote! { - crate::tracing::if_tracing(|tracer| tracer.enter_ecall(#name, self)); + crate::tracing::if_tracing(|tracer| { + if DebugSettings::is_execution_tracing_enabled::() { + tracer.enter_ecall(#name, self) + } + }); // wrap body in closure to make sure the tracing is always executed let result = (|| #body)(); ::log::trace!(target: "runtime::revive::strace", #trace_fmt_str, #( #trace_fmt_args, )* result, self.ext.frame_meter().weight_consumed()); - crate::tracing::if_tracing(|tracer| tracer.exit_step(self)); + crate::tracing::if_tracing(|tracer| { + if DebugSettings::is_execution_tracing_enabled::() { + tracer.exit_step(self) + } + }); result } @@ -458,130 +482,139 @@ fn expand_functions(def: &EnvDef) -> TokenStream2 { } }); - quote! { - // Write gas from polkavm into pallet-revive before entering the host function. - self.ext - .frame_meter_mut() - .sync_from_executor(memory.gas()) - .map_err(TrapReason::from)?; - - // This is the overhead to call an empty syscall that always needs to be charged. - self.charge_gas(crate::vm::RuntimeCosts::HostFn).map_err(TrapReason::from)?; - - // They will be mapped to variable names by the syscall specific code. - let (__a0__, __a1__, __a2__, __a3__, __a4__, __a5__) = memory.read_input_regs(); - - // Execute the syscall specific logic in a closure so that the gas metering code is always executed. - let result = (|| match __syscall_symbol__ { - #( #impls )* - _ => Err(TrapReason::SupervisorError(Error::::InvalidSyscall.into())) - })(); - - // Write gas from pallet-revive into polkavm after leaving the host function. - let gas = self.ext.frame_meter_mut().sync_to_executor(); - memory.set_gas(gas.into()); - result - } + quote! { + // Write gas from polkavm into pallet-revive before entering the host function. + self.ext + .frame_meter_mut() + .sync_from_executor(memory.gas()) + .map_err(TrapReason::from)?; + + // This is the overhead to call an empty syscall that always needs to be charged. + self.charge_gas(crate::vm::RuntimeCosts::HostFn).map_err(TrapReason::from)?; + + // They will be mapped to variable names by the syscall specific code. + let (__a0__, __a1__, __a2__, __a3__, __a4__, __a5__) = memory.read_input_regs(); + + // Execute the syscall specific logic in a closure so that the gas metering code is always executed. + let result = (|| match __syscall_symbol__ { + #( #impls )* + _ => Err(TrapReason::SupervisorError(Error::::InvalidSyscall.into())) + })(); + + // Write gas from pallet-revive into polkavm after leaving the host function. + let gas = self.ext.frame_meter_mut().sync_to_executor(); + memory.set_gas(gas.into()); + result + } } fn expand_bench_functions(def: &EnvDef) -> TokenStream2 { - let impls = def.host_funcs.iter().map(|f| { - // skip the context and memory argument - let params = f.item.sig.inputs.iter().skip(2); - let cfg = &f.cfg; - let name = &f.name; - let body = &f.item.block; - let output = &f.item.sig.output; - - let name = Ident::new(&format!("bench_{name}"), Span::call_site()); - quote! { - #cfg - pub fn #name(&mut self, memory: &mut M, #(#params),*) #output { - #body - } - } - }); - - quote! { - #( #impls )* - } + let impls = def.host_funcs.iter().map(|f| { + // skip the context and memory argument + let params = f.item.sig.inputs.iter().skip(2); + let cfg = &f.cfg; + let name = &f.name; + let body = &f.item.block; + let output = &f.item.sig.output; + + let name = Ident::new(&format!("bench_{name}"), Span::call_site()); + quote! { + #cfg + pub fn #name(&mut self, memory: &mut M, #(#params),*) #output { + #body + } + } + }); + + quote! { + #( #impls )* + } } fn expand_func_doc(def: &EnvDef) -> TokenStream2 { - let docs = def.host_funcs.iter().map(|func| { - // Remove auxiliary args: `ctx: _` and `memory: _` - let func_decl = { - let mut sig = func.item.sig.clone(); - sig.inputs = sig - .inputs - .iter() - .skip(2) - .map(|p| p.clone()) - .collect::>(); - sig.output = func.returns.success_type(); - sig.to_token_stream() - }; - let func_doc = { - let func_docs = { - let docs = func.item.attrs.iter().filter(|a| a.path().is_ident("doc")).map(|d| { - let docs = d.to_token_stream(); - quote! { #docs } - }); - quote! { #( #docs )* } - }; - let availability = if func.is_stable { - let info = "\n# Stable API\nThis API is stable and will never change."; - quote! { #[doc = #info] } - } else { - let info = + let docs = def.host_funcs.iter().map(|func| { + // Remove auxiliary args: `ctx: _` and `memory: _` + let func_decl = { + let mut sig = func.item.sig.clone(); + sig.inputs = sig + .inputs + .iter() + .skip(2) + .map(|p| p.clone()) + .collect::>(); + sig.output = func.returns.success_type(); + sig.to_token_stream() + }; + let func_doc = { + let func_docs = { + let docs = func + .item + .attrs + .iter() + .filter(|a| a.path().is_ident("doc")) + .map(|d| { + let docs = d.to_token_stream(); + quote! { #docs } + }); + quote! { #( #docs )* } + }; + let availability = if func.is_stable { + let info = "\n# Stable API\nThis API is stable and will never change."; + quote! { #[doc = #info] } + } else { + let info = "\n# Unstable API\nThis API is not standardized and only available for testing."; - quote! { #[doc = #info] } - }; - quote! { - #func_docs - #availability - } - }; - quote! { - #func_doc - #func_decl; - } - }); - - quote! { - #( #docs )* - } + quote! { #[doc = #info] } + }; + quote! { + #func_docs + #availability + } + }; + quote! { + #func_doc + #func_decl; + } + }); + + quote! { + #( #docs )* + } } fn expand_func_list(def: &EnvDef, include_unstable: bool) -> TokenStream2 { - let docs = def.host_funcs.iter().filter(|f| include_unstable || f.is_stable).map(|f| { - let name = Literal::byte_string(f.name.as_bytes()); - quote! { - #name.as_slice() - } - }); - let len = docs.clone().count(); - - quote! { - { - static FUNCS: [&[u8]; #len] = [#(#docs),*]; - FUNCS.as_slice() - } - } + let docs = def + .host_funcs + .iter() + .filter(|f| include_unstable || f.is_stable) + .map(|f| { + let name = Literal::byte_string(f.name.as_bytes()); + quote! { + #name.as_slice() + } + }); + let len = docs.clone().count(); + + quote! { + { + static FUNCS: [&[u8]; #len] = [#(#docs),*]; + FUNCS.as_slice() + } + } } fn expand_func_lookup(def: &EnvDef) -> TokenStream2 { - let arms = def.host_funcs.iter().enumerate().map(|(idx, f)| { - let name_str = &f.name; - quote! { - #name_str => Some(#idx as u32) - } - }); - - quote! { - match name { - #( #arms, )* - _ => None, - } - } + let arms = def.host_funcs.iter().enumerate().map(|(idx, f)| { + let name_str = &f.name; + quote! { + #name_str => Some(#idx as u32) + } + }); + + quote! { + match name { + #( #arms, )* + _ => None, + } + } } diff --git a/substrate/frame/revive/src/debug.rs b/substrate/frame/revive/src/debug.rs index 6d569523459c0..f0f7ffa1c19d6 100644 --- a/substrate/frame/revive/src/debug.rs +++ b/substrate/frame/revive/src/debug.rs @@ -36,6 +36,8 @@ use Debug; Deserialize, )] pub struct DebugSettings { + /// Whether to disable execution tracing. + disable_execution_tracing: bool, /// Whether to allow unlimited contract size. allow_unlimited_contract_size: bool, /// Whether to allow bypassing EIP-3607 (allowing transactions coming from contract or @@ -46,8 +48,20 @@ pub struct DebugSettings { } impl DebugSettings { - pub fn new(allow_unlimited_contract_size: bool, bypass_eip_3607: bool, pvm_logs: bool) -> Self { - Self { allow_unlimited_contract_size, bypass_eip_3607, pvm_logs } + #[cfg(test)] + pub fn set_bypass_eip_3607(mut self, value: bool) -> Self { + self.bypass_eip_3607 = value; + self + } + + #[cfg(test)] + pub fn set_allow_unlimited_contract_size(mut self, value: bool) -> Self { + self.allow_unlimited_contract_size = value; + self + } + + pub fn is_execution_tracing_enabled() -> bool { + T::DebugEnabled::get() && !DebugSettingsOf::::get().disable_execution_tracing } /// Returns true if unlimited contract size is allowed. diff --git a/substrate/frame/revive/src/evm/tracing/execution_tracing.rs b/substrate/frame/revive/src/evm/tracing/execution_tracing.rs index a4798c675ca5e..d35cfd0a908cf 100644 --- a/substrate/frame/revive/src/evm/tracing/execution_tracing.rs +++ b/substrate/frame/revive/src/evm/tracing/execution_tracing.rs @@ -89,7 +89,7 @@ impl ExecutionTracer { } impl Tracing for ExecutionTracer { - fn is_execution_tracing_enabled(&self) -> bool { + fn is_execution_tracer(&self) -> bool { true } diff --git a/substrate/frame/revive/src/tests/pvm.rs b/substrate/frame/revive/src/tests/pvm.rs index 2db40a333ed5a..bcedcb27805d4 100644 --- a/substrate/frame/revive/src/tests/pvm.rs +++ b/substrate/frame/revive/src/tests/pvm.rs @@ -4982,7 +4982,7 @@ fn eip3607_allow_tx_from_contract_or_precompile_if_debug_setting_configured() { let (binary, code_hash) = compile_module("dummy").unwrap(); let genesis_config = GenesisConfig:: { - debug_settings: Some(DebugSettings::new(false, true, false)), + debug_settings: Some(DebugSettings::default().set_bypass_eip_3607(true)), ..Default::default() }; diff --git a/substrate/frame/revive/src/tests/sol.rs b/substrate/frame/revive/src/tests/sol.rs index b6c8130e5ef94..aa20cbfe5947f 100644 --- a/substrate/frame/revive/src/tests/sol.rs +++ b/substrate/frame/revive/src/tests/sol.rs @@ -190,7 +190,10 @@ fn eth_contract_too_large() { // Initialize genesis config with allow_unlimited_contract_size let genesis_config = GenesisConfig:: { - debug_settings: Some(DebugSettings::new(allow_unlimited_contract_size, false, false)), + debug_settings: Some( + DebugSettings::default() + .set_allow_unlimited_contract_size(allow_unlimited_contract_size), + ), ..Default::default() }; diff --git a/substrate/frame/revive/src/tracing.rs b/substrate/frame/revive/src/tracing.rs index 98c0951ccec4c..92e78028a4f72 100644 --- a/substrate/frame/revive/src/tracing.rs +++ b/substrate/frame/revive/src/tracing.rs @@ -116,8 +116,8 @@ pub trait Tracing { /// Called when a contract call terminates with an error fn exit_child_span_with_error(&mut self, _error: DispatchError, _gas_used: u64) {} - /// Check if execution tracing is enabled. - fn is_execution_tracing_enabled(&self) -> bool { + /// Check if the tracer is an execution tracer. + fn is_execution_tracer(&self) -> bool { false } diff --git a/substrate/frame/revive/src/vm/evm.rs b/substrate/frame/revive/src/vm/evm.rs index 02d8f28af0439..66d710c678445 100644 --- a/substrate/frame/revive/src/vm/evm.rs +++ b/substrate/frame/revive/src/vm/evm.rs @@ -131,10 +131,12 @@ impl ContractBlob { /// Calls the EVM interpreter with the provided bytecode and inputs. pub fn call(bytecode: Bytecode, ext: &mut E, input: Vec) -> ExecResult { let mut interpreter = Interpreter::new(ExtBytecode::new(bytecode), input, ext); - let use_opcode_tracing = - tracing::if_tracing(|tracer| tracer.is_execution_tracing_enabled()).unwrap_or(false); + let is_execution_tracing_enabled = tracing::if_tracing(|tracer| { + tracer.is_execution_tracer() && DebugSettings::is_execution_tracing_enabled::() + }) + .unwrap_or(false); - let ControlFlow::Break(halt) = if use_opcode_tracing { + let ControlFlow::Break(halt) = if is_execution_tracing_enabled { run_plain_with_tracing(&mut interpreter) } else { run_plain(&mut interpreter) From b1571d6b31d2ca6f0bedc6157f2717ed7122334f Mon Sep 17 00:00:00 2001 From: "cmd[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 16 Dec 2025 14:19:54 +0000 Subject: [PATCH 58/76] Update from github-actions[bot] running command 'prdoc --audience runtime_dev --bump patch' --- prdoc/pr_9722.prdoc | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 prdoc/pr_9722.prdoc diff --git a/prdoc/pr_9722.prdoc b/prdoc/pr_9722.prdoc new file mode 100644 index 0000000000000..c13a2dcab802b --- /dev/null +++ b/prdoc/pr_9722.prdoc @@ -0,0 +1,37 @@ +title: '[pallet-revive] opcode tracer' +doc: +- audience: Runtime Dev + description: "This PR introduces a **Geth-compatible execution tracer** ([StructLogger](https://geth.ethereum.org/docs/developers/evm-tracing/built-in-tracers#struct-opcode-logger))\ + \ for pallet-revive\n\n The tracer can be used to capture both EVM opcode and\ + \ PVM syscall.\n It can be used with the same RPC endpoint as Geth StructLogger.\n\ + \n\nSince it can be quite resource intensive, It can only be queried from the\ + \ node when the **DebugSettings** are enabled (This is turned on now by default\ + \ in the dev-node)\n\nTested in https://github.com/paritytech/evm-test-suite/pull/138\n\ + \n\nexample:\n\n```sh\n\u276F cast rpc debug_traceTransaction \"\" |\ + \ jq\n\n# or with options\n# See list of options https://geth.ethereum.org/docs/developers/evm-tracing/built-in-tracers#struct-opcode-logger\n\ + \n \u276F cast rpc debug_traceTransaction \"\", { \"tracer\": { \"enableMemory\"\ + : true } } | jq\n```\n\nFor an EVM execution, the output will look like this\n\ + \n```json\n{\n \"gas\": 16208049,\n \"failed\": false,\n \"returnValue\": \"\ + 0x\",\n \"structLogs\": [\n {\n \"gas\": 14609533,\n \"gasCost\"\ + : 1,\n \"depth\": 1,\n \"pc\": 0,\n \"op\": \"PUSH1\",\n \"\ + stack\": []\n },\n {\n \"gas\": 14609532,\n \"gasCost\": 1,\n\ + \ \"depth\": 1,\n \"pc\": 2,\n \"op\": \"PUSH1\",\n \"stack\"\ + : [\n \"0x80\"\n ]\n },\n {\n \"gas\": 14609531,\n \ + \ \"gasCost\": 1,\n \"depth\": 1,\n \"pc\": 4,\n \"op\": \"MSTORE\"\ + ,\n \"stack\": [\n \"0x80\",\n \"0x40\"\n ]\n }]\n\ + }\n```\n\nFor a PVM execution the response will look like this\n\n```json\n{\n\ + \ \"gas\": 279456,\n \"failed\": false,\n \"returnValue\": \"0x\",\n \"structLogs\"\ + : [\n {\n \"gas\": 27108,\n \"gasCost\": 7,\n \"depth\": 1,\n\ + \ \"op\": \"call_data_size\"\n },\n {\n \"gas\": 26977,\n \ + \ \"gasCost\": 7,\n \"depth\": 1,\n \"op\": \"call_data_load\"\n \ + \ },\n {\n \"gas\": 26957,\n \"gasCost\": 7,\n \"depth\": 1,\n\ + \ \"op\": \"value_transferred\"\n }]\n}\n```" +crates: +- name: pallet-revive + bump: patch +- name: pallet-revive-eth-rpc + bump: patch +- name: pallet-revive-proc-macro + bump: patch +- name: revive-dev-runtime + bump: patch From 3828cd0285b435adcff2e12db495139eb71cbd72 Mon Sep 17 00:00:00 2001 From: "cmd[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 16 Dec 2025 14:20:11 +0000 Subject: [PATCH 59/76] Update from github-actions[bot] running command 'fmt' --- .../frame/revive/dev-node/runtime/src/lib.rs | 705 +++++++------- substrate/frame/revive/proc-macro/src/lib.rs | 868 +++++++++--------- substrate/frame/revive/src/lib.rs | 2 +- 3 files changed, 774 insertions(+), 801 deletions(-) diff --git a/substrate/frame/revive/dev-node/runtime/src/lib.rs b/substrate/frame/revive/dev-node/runtime/src/lib.rs index d92c54a4d2d14..44a9f2eba1158 100644 --- a/substrate/frame/revive/dev-node/runtime/src/lib.rs +++ b/substrate/frame/revive/dev-node/runtime/src/lib.rs @@ -26,135 +26,130 @@ extern crate alloc; use alloc::{vec, vec::Vec}; use currency::*; use frame_support::weights::{ - constants::{BlockExecutionWeight, ExtrinsicBaseWeight, WEIGHT_REF_TIME_PER_SECOND}, - Weight, + constants::{BlockExecutionWeight, ExtrinsicBaseWeight, WEIGHT_REF_TIME_PER_SECOND}, + Weight, }; use frame_system::limits::BlockWeights; use pallet_revive::{ - evm::{ - fees::{BlockRatioFee, Info as FeeInfo}, - runtime::EthExtra, - }, - AccountId32Mapper, + evm::{ + fees::{BlockRatioFee, Info as FeeInfo}, + runtime::EthExtra, + }, + AccountId32Mapper, }; use pallet_transaction_payment::{ConstFeeMultiplier, FeeDetails, Multiplier, RuntimeDispatchInfo}; use polkadot_sdk::{ - polkadot_sdk_frame::{ - deps::sp_genesis_builder, - runtime::{apis, prelude::*}, - traits::Block as BlockT, - }, - *, + polkadot_sdk_frame::{ + deps::sp_genesis_builder, + runtime::{apis, prelude::*}, + traits::Block as BlockT, + }, + *, }; use sp_weights::ConstantMultiplier; pub use polkadot_sdk::{ - parachains_common::{AccountId, Balance, BlockNumber, Hash, Header, Nonce, Signature}, - polkadot_sdk_frame::runtime::types_common::OpaqueBlock, + parachains_common::{AccountId, Balance, BlockNumber, Hash, Header, Nonce, Signature}, + polkadot_sdk_frame::runtime::types_common::OpaqueBlock, }; pub mod currency { - use super::Balance; - pub const DOLLARS: Balance = 1_000_000_000_000; - pub const CENTS: Balance = DOLLARS / 100; - pub const MILLICENTS: Balance = CENTS / 1_000; + use super::Balance; + pub const DOLLARS: Balance = 1_000_000_000_000; + pub const CENTS: Balance = DOLLARS / 100; + pub const MILLICENTS: Balance = CENTS / 1_000; } /// Provides getters for genesis configuration presets. pub mod genesis_config_presets { - use super::*; - use crate::{ - currency::DOLLARS, sp_keyring::Sr25519Keyring, Balance, BalancesConfig, - RuntimeGenesisConfig, SudoConfig, - }; - - use alloc::{vec, vec::Vec}; - use serde_json::Value; - - pub const ENDOWMENT: Balance = 10_000_000_000_001 * DOLLARS; - - fn well_known_accounts() -> Vec { - Sr25519Keyring::well_known() - .map(|k| k.to_account_id()) - .chain([ - // subxt_signer::eth::dev::alith() - array_bytes::hex_n_into_unchecked( - "f24ff3a9cf04c71dbc94d0b566f7a27b94566caceeeeeeeeeeeeeeeeeeeeeeee", - ), - // subxt_signer::eth::dev::baltathar() - array_bytes::hex_n_into_unchecked( - "3cd0a705a2dc65e5b1e1205896baa2be8a07c6e0eeeeeeeeeeeeeeeeeeeeeeee", - ), - // subxt_signer::eth::dev::charleth() - array_bytes::hex_n_into_unchecked( - "798d4ba9baf0064ec19eb4f0a1a45785ae9d6dfceeeeeeeeeeeeeeeeeeeeeeee", - ), - // subxt_signer::eth::dev::dorothy() - array_bytes::hex_n_into_unchecked( - "773539d4ac0e786233d90a233654ccee26a613d9eeeeeeeeeeeeeeeeeeeeeeee", - ), - // subxt_signer::eth::dev::ethan() - array_bytes::hex_n_into_unchecked( - "ff64d3f6efe2317ee2807d223a0bdc4c0c49dfdbeeeeeeeeeeeeeeeeeeeeeeee", - ), - ]) - .collect::>() - } - - /// Returns a development genesis config preset. - pub fn development_config_genesis() -> Value { - frame_support::build_struct_json_patch!(RuntimeGenesisConfig { - balances: BalancesConfig { - balances: well_known_accounts() - .into_iter() - .map(|id| (id, ENDOWMENT)) - .collect::>(), - }, - sudo: SudoConfig { - key: Some(Sr25519Keyring::Alice.to_account_id()) - }, - }) - } - - /// Get the set of the available genesis config presets. - pub fn get_preset(id: &PresetId) -> Option> { - let patch = match id.as_ref() { - sp_genesis_builder::DEV_RUNTIME_PRESET => development_config_genesis(), - _ => return None, - }; - Some( - serde_json::to_string(&patch) - .expect("serialization to json is expected to work. qed.") - .into_bytes(), - ) - } - - /// List of supported presets. - pub fn preset_names() -> Vec { - vec![PresetId::from(sp_genesis_builder::DEV_RUNTIME_PRESET)] - } + use super::*; + use crate::{ + currency::DOLLARS, sp_keyring::Sr25519Keyring, Balance, BalancesConfig, + RuntimeGenesisConfig, SudoConfig, + }; + + use alloc::{vec, vec::Vec}; + use serde_json::Value; + + pub const ENDOWMENT: Balance = 10_000_000_000_001 * DOLLARS; + + fn well_known_accounts() -> Vec { + Sr25519Keyring::well_known() + .map(|k| k.to_account_id()) + .chain([ + // subxt_signer::eth::dev::alith() + array_bytes::hex_n_into_unchecked( + "f24ff3a9cf04c71dbc94d0b566f7a27b94566caceeeeeeeeeeeeeeeeeeeeeeee", + ), + // subxt_signer::eth::dev::baltathar() + array_bytes::hex_n_into_unchecked( + "3cd0a705a2dc65e5b1e1205896baa2be8a07c6e0eeeeeeeeeeeeeeeeeeeeeeee", + ), + // subxt_signer::eth::dev::charleth() + array_bytes::hex_n_into_unchecked( + "798d4ba9baf0064ec19eb4f0a1a45785ae9d6dfceeeeeeeeeeeeeeeeeeeeeeee", + ), + // subxt_signer::eth::dev::dorothy() + array_bytes::hex_n_into_unchecked( + "773539d4ac0e786233d90a233654ccee26a613d9eeeeeeeeeeeeeeeeeeeeeeee", + ), + // subxt_signer::eth::dev::ethan() + array_bytes::hex_n_into_unchecked( + "ff64d3f6efe2317ee2807d223a0bdc4c0c49dfdbeeeeeeeeeeeeeeeeeeeeeeee", + ), + ]) + .collect::>() + } + + /// Returns a development genesis config preset. + pub fn development_config_genesis() -> Value { + frame_support::build_struct_json_patch!(RuntimeGenesisConfig { + balances: BalancesConfig { + balances: well_known_accounts() + .into_iter() + .map(|id| (id, ENDOWMENT)) + .collect::>(), + }, + sudo: SudoConfig { key: Some(Sr25519Keyring::Alice.to_account_id()) }, + }) + } + + /// Get the set of the available genesis config presets. + pub fn get_preset(id: &PresetId) -> Option> { + let patch = match id.as_ref() { + sp_genesis_builder::DEV_RUNTIME_PRESET => development_config_genesis(), + _ => return None, + }; + Some( + serde_json::to_string(&patch) + .expect("serialization to json is expected to work. qed.") + .into_bytes(), + ) + } + + /// List of supported presets. + pub fn preset_names() -> Vec { + vec![PresetId::from(sp_genesis_builder::DEV_RUNTIME_PRESET)] + } } /// The runtime version. #[runtime_version] pub const VERSION: RuntimeVersion = RuntimeVersion { - spec_name: alloc::borrow::Cow::Borrowed("revive-dev-runtime"), - impl_name: alloc::borrow::Cow::Borrowed("revive-dev-runtime"), - authoring_version: 1, - spec_version: 0, - impl_version: 1, - apis: RUNTIME_API_VERSIONS, - transaction_version: 1, - system_version: 1, + spec_name: alloc::borrow::Cow::Borrowed("revive-dev-runtime"), + impl_name: alloc::borrow::Cow::Borrowed("revive-dev-runtime"), + authoring_version: 1, + spec_version: 0, + impl_version: 1, + apis: RUNTIME_API_VERSIONS, + transaction_version: 1, + system_version: 1, }; /// The version information used to identify this runtime when compiled natively. #[cfg(feature = "std")] pub fn native_version() -> NativeVersion { - NativeVersion { - runtime_version: VERSION, - can_author_with: Default::default(), - } + NativeVersion { runtime_version: VERSION, can_author_with: Default::default() } } /// The address format for describing accounts. @@ -163,29 +158,29 @@ pub type Address = sp_runtime::MultiAddress; pub type Block = sp_runtime::generic::Block; /// The transaction extensions that are added to the runtime. type TxExtension = ( - // Checks that the sender is not the zero address. - frame_system::CheckNonZeroSender, - // Checks that the runtime version is correct. - frame_system::CheckSpecVersion, - // Checks that the transaction version is correct. - frame_system::CheckTxVersion, - // Checks that the genesis hash is correct. - frame_system::CheckGenesis, - // Checks that the era is valid. - frame_system::CheckEra, - // Checks that the nonce is valid. - frame_system::CheckNonce, - // Checks that the weight is valid. - frame_system::CheckWeight, - // Ensures that the sender has enough funds to pay for the transaction - // and deducts the fee from the sender's account. - pallet_transaction_payment::ChargeTransactionPayment, - // Needs to be done after all extensions that rely on a signed origin. - pallet_revive::evm::tx_extension::SetOrigin, - // Reclaim the unused weight from the block using post dispatch information. - // It must be last in the pipeline in order to catch the refund in previous transaction - // extensions - frame_system::WeightReclaim, + // Checks that the sender is not the zero address. + frame_system::CheckNonZeroSender, + // Checks that the runtime version is correct. + frame_system::CheckSpecVersion, + // Checks that the transaction version is correct. + frame_system::CheckTxVersion, + // Checks that the genesis hash is correct. + frame_system::CheckGenesis, + // Checks that the era is valid. + frame_system::CheckEra, + // Checks that the nonce is valid. + frame_system::CheckNonce, + // Checks that the weight is valid. + frame_system::CheckWeight, + // Ensures that the sender has enough funds to pay for the transaction + // and deducts the fee from the sender's account. + pallet_transaction_payment::ChargeTransactionPayment, + // Needs to be done after all extensions that rely on a signed origin. + pallet_revive::evm::tx_extension::SetOrigin, + // Reclaim the unused weight from the block using post dispatch information. + // It must be last in the pipeline in order to catch the refund in previous transaction + // extensions + frame_system::WeightReclaim, ); /// Default extensions applied to Ethereum transactions. @@ -193,78 +188,78 @@ type TxExtension = ( pub struct EthExtraImpl; impl EthExtra for EthExtraImpl { - type Config = Runtime; - type Extension = TxExtension; - - fn get_eth_extension(nonce: u32, tip: Balance) -> Self::Extension { - ( - frame_system::CheckNonZeroSender::::new(), - frame_system::CheckSpecVersion::::new(), - frame_system::CheckTxVersion::::new(), - frame_system::CheckGenesis::::new(), - frame_system::CheckMortality::from(sp_runtime::generic::Era::Immortal), - frame_system::CheckNonce::::from(nonce), - frame_system::CheckWeight::::new(), - pallet_transaction_payment::ChargeTransactionPayment::::from(tip), - pallet_revive::evm::tx_extension::SetOrigin::::new_from_eth_transaction(), - frame_system::WeightReclaim::::new(), - ) - } + type Config = Runtime; + type Extension = TxExtension; + + fn get_eth_extension(nonce: u32, tip: Balance) -> Self::Extension { + ( + frame_system::CheckNonZeroSender::::new(), + frame_system::CheckSpecVersion::::new(), + frame_system::CheckTxVersion::::new(), + frame_system::CheckGenesis::::new(), + frame_system::CheckMortality::from(sp_runtime::generic::Era::Immortal), + frame_system::CheckNonce::::from(nonce), + frame_system::CheckWeight::::new(), + pallet_transaction_payment::ChargeTransactionPayment::::from(tip), + pallet_revive::evm::tx_extension::SetOrigin::::new_from_eth_transaction(), + frame_system::WeightReclaim::::new(), + ) + } } pub type UncheckedExtrinsic = - pallet_revive::evm::runtime::UncheckedExtrinsic; + pallet_revive::evm::runtime::UncheckedExtrinsic; type Executive = frame_executive::Executive< - Runtime, - Block, - frame_system::ChainContext, - Runtime, - AllPalletsWithSystem, + Runtime, + Block, + frame_system::ChainContext, + Runtime, + AllPalletsWithSystem, >; // Composes the runtime by adding all the used pallets and deriving necessary types. #[frame_construct_runtime] mod runtime { - /// The main runtime type. - #[runtime::runtime] - #[runtime::derive( - RuntimeCall, - RuntimeEvent, - RuntimeError, - RuntimeOrigin, - RuntimeFreezeReason, - RuntimeHoldReason, - RuntimeSlashReason, - RuntimeLockId, - RuntimeTask, - RuntimeViewFunction - )] - pub struct Runtime; - - /// Mandatory system pallet that should always be included in a FRAME runtime. - #[runtime::pallet_index(0)] - pub type System = frame_system::Pallet; - - /// Provides a way for consensus systems to set and check the onchain time. - #[runtime::pallet_index(1)] - pub type Timestamp = pallet_timestamp::Pallet; - - /// Provides the ability to keep track of balances. - #[runtime::pallet_index(2)] - pub type Balances = pallet_balances::Pallet; - - /// Provides a way to execute privileged functions. - #[runtime::pallet_index(3)] - pub type Sudo = pallet_sudo::Pallet; - - /// Provides the ability to charge for extrinsic execution. - #[runtime::pallet_index(4)] - pub type TransactionPayment = pallet_transaction_payment::Pallet; - - /// Provides the ability to execute Smart Contracts. - #[runtime::pallet_index(5)] - pub type Revive = pallet_revive::Pallet; + /// The main runtime type. + #[runtime::runtime] + #[runtime::derive( + RuntimeCall, + RuntimeEvent, + RuntimeError, + RuntimeOrigin, + RuntimeFreezeReason, + RuntimeHoldReason, + RuntimeSlashReason, + RuntimeLockId, + RuntimeTask, + RuntimeViewFunction + )] + pub struct Runtime; + + /// Mandatory system pallet that should always be included in a FRAME runtime. + #[runtime::pallet_index(0)] + pub type System = frame_system::Pallet; + + /// Provides a way for consensus systems to set and check the onchain time. + #[runtime::pallet_index(1)] + pub type Timestamp = pallet_timestamp::Pallet; + + /// Provides the ability to keep track of balances. + #[runtime::pallet_index(2)] + pub type Balances = pallet_balances::Pallet; + + /// Provides a way to execute privileged functions. + #[runtime::pallet_index(3)] + pub type Sudo = pallet_sudo::Pallet; + + /// Provides the ability to charge for extrinsic execution. + #[runtime::pallet_index(4)] + pub type TransactionPayment = pallet_transaction_payment::Pallet; + + /// Provides the ability to execute Smart Contracts. + #[runtime::pallet_index(5)] + pub type Revive = pallet_revive::Pallet; } /// We assume that ~10% of the block weight is consumed by `on_initialize` handlers. @@ -275,51 +270,51 @@ const AVERAGE_ON_INITIALIZE_RATIO: Perbill = Perbill::from_percent(10); const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75); /// We allow for 2 seconds of compute with a 6 second average block time, with maximum proof size. const MAXIMUM_BLOCK_WEIGHT: Weight = - Weight::from_parts(WEIGHT_REF_TIME_PER_SECOND.saturating_mul(2), u64::MAX); + Weight::from_parts(WEIGHT_REF_TIME_PER_SECOND.saturating_mul(2), u64::MAX); parameter_types! { - pub const Version: RuntimeVersion = VERSION; - pub RuntimeBlockWeights: BlockWeights = BlockWeights::builder() - .base_block(BlockExecutionWeight::get()) - .for_class(DispatchClass::all(), |weights| { - weights.base_extrinsic = ExtrinsicBaseWeight::get(); - }) - .for_class(DispatchClass::Normal, |weights| { - weights.max_total = Some(NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT); - }) - .for_class(DispatchClass::Operational, |weights| { - weights.max_total = Some(MAXIMUM_BLOCK_WEIGHT); - // Operational transactions have some extra reserved space, so that they - // are included even if block reached `MAXIMUM_BLOCK_WEIGHT`. - weights.reserved = Some( - MAXIMUM_BLOCK_WEIGHT - NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT - ); - }) - .avg_block_initialization(AVERAGE_ON_INITIALIZE_RATIO) - .build_or_panic(); + pub const Version: RuntimeVersion = VERSION; + pub RuntimeBlockWeights: BlockWeights = BlockWeights::builder() + .base_block(BlockExecutionWeight::get()) + .for_class(DispatchClass::all(), |weights| { + weights.base_extrinsic = ExtrinsicBaseWeight::get(); + }) + .for_class(DispatchClass::Normal, |weights| { + weights.max_total = Some(NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT); + }) + .for_class(DispatchClass::Operational, |weights| { + weights.max_total = Some(MAXIMUM_BLOCK_WEIGHT); + // Operational transactions have some extra reserved space, so that they + // are included even if block reached `MAXIMUM_BLOCK_WEIGHT`. + weights.reserved = Some( + MAXIMUM_BLOCK_WEIGHT - NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT + ); + }) + .avg_block_initialization(AVERAGE_ON_INITIALIZE_RATIO) + .build_or_panic(); } /// Implements the types required for the system pallet. #[derive_impl(frame_system::config_preludes::SolochainDefaultConfig)] impl frame_system::Config for Runtime { - type Block = Block; - type Version = Version; - type AccountId = AccountId; - type Hash = Hash; - type Nonce = Nonce; - type AccountData = pallet_balances::AccountData<::Balance>; + type Block = Block; + type Version = Version; + type AccountId = AccountId; + type Hash = Hash; + type Nonce = Nonce; + type AccountData = pallet_balances::AccountData<::Balance>; } parameter_types! { - pub const ExistentialDeposit: Balance = CENTS; + pub const ExistentialDeposit: Balance = CENTS; } // Implements the types required for the balances pallet. #[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] impl pallet_balances::Config for Runtime { - type AccountStore = System; - type Balance = Balance; - type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type Balance = Balance; + type ExistentialDeposit = ExistentialDeposit; } // Implements the types required for the sudo pallet. @@ -331,157 +326,157 @@ impl pallet_sudo::Config for Runtime {} impl pallet_timestamp::Config for Runtime {} parameter_types! { - pub const TransactionByteFee: Balance = 10 * MILLICENTS; - pub FeeMultiplier: Multiplier = Multiplier::one(); + pub const TransactionByteFee: Balance = 10 * MILLICENTS; + pub FeeMultiplier: Multiplier = Multiplier::one(); } // Implements the types required for the transaction payment pallet. #[derive_impl(pallet_transaction_payment::config_preludes::TestDefaultConfig)] impl pallet_transaction_payment::Config for Runtime { - type OnChargeTransaction = pallet_transaction_payment::FungibleAdapter; - type WeightToFee = BlockRatioFee<1, 1, Self>; - type LengthToFee = ConstantMultiplier; - type FeeMultiplierUpdate = ConstFeeMultiplier; + type OnChargeTransaction = pallet_transaction_payment::FungibleAdapter; + type WeightToFee = BlockRatioFee<1, 1, Self>; + type LengthToFee = ConstantMultiplier; + type FeeMultiplierUpdate = ConstFeeMultiplier; } parameter_types! { - pub CodeHashLockupDepositPercent: Perbill = Perbill::from_percent(30); + pub CodeHashLockupDepositPercent: Perbill = Perbill::from_percent(30); } #[derive_impl(pallet_revive::config_preludes::TestDefaultConfig)] impl pallet_revive::Config for Runtime { - type AddressMapper = AccountId32Mapper; - type ChainId = ConstU64<420_420_420>; - type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; - type Balance = Balance; - type Currency = Balances; - type NativeToEthRatio = ConstU32<1_000_000>; - type UploadOrigin = EnsureSigned; - type InstantiateOrigin = EnsureSigned; - type Time = Timestamp; - type FeeInfo = FeeInfo; - type DebugEnabled = ConstBool; - type GasScale = ConstU32<50000>; + type AddressMapper = AccountId32Mapper; + type ChainId = ConstU64<420_420_420>; + type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; + type Balance = Balance; + type Currency = Balances; + type NativeToEthRatio = ConstU32<1_000_000>; + type UploadOrigin = EnsureSigned; + type InstantiateOrigin = EnsureSigned; + type Time = Timestamp; + type FeeInfo = FeeInfo; + type DebugEnabled = ConstBool; + type GasScale = ConstU32<50000>; } pallet_revive::impl_runtime_apis_plus_revive_traits!( - Runtime, - Revive, - Executive, - EthExtraImpl, - - impl apis::Core for Runtime { - fn version() -> RuntimeVersion { - VERSION - } - - fn execute_block(block: ::LazyBlock) { - Executive::execute_block(block) - } - - fn initialize_block(header: &Header) -> ExtrinsicInclusionMode { - Executive::initialize_block(header) - } - } - - impl apis::Metadata for Runtime { - fn metadata() -> OpaqueMetadata { - OpaqueMetadata::new(Runtime::metadata().into()) - } - - fn metadata_at_version(version: u32) -> Option { - Runtime::metadata_at_version(version) - } - - fn metadata_versions() -> Vec { - Runtime::metadata_versions() - } - } - - impl apis::BlockBuilder for Runtime { - fn apply_extrinsic(extrinsic: ExtrinsicFor) -> ApplyExtrinsicResult { - Executive::apply_extrinsic(extrinsic) - } - - fn finalize_block() -> HeaderFor { - Executive::finalize_block() - } - - fn inherent_extrinsics(data: InherentData) -> Vec> { - data.create_extrinsics() - } - - fn check_inherents( - block: ::LazyBlock, - data: InherentData, - ) -> CheckInherentsResult { - data.check_extrinsics(&block) - } - } - - impl apis::TaggedTransactionQueue for Runtime { - fn validate_transaction( - source: TransactionSource, - tx: ExtrinsicFor, - block_hash: ::Hash, - ) -> TransactionValidity { - Executive::validate_transaction(source, tx, block_hash) - } - } - - impl apis::OffchainWorkerApi for Runtime { - fn offchain_worker(header: &HeaderFor) { - Executive::offchain_worker(header) - } - } - - impl apis::SessionKeys for Runtime { - fn generate_session_keys(_seed: Option>) -> Vec { - Default::default() - } - - fn decode_session_keys( - _encoded: Vec, - ) -> Option, apis::KeyTypeId)>> { - Default::default() - } - } - - impl apis::AccountNonceApi for Runtime { - fn account_nonce(account: AccountId) -> Nonce { - System::account_nonce(account) - } - } - - impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi< - Block, - Balance, - > for Runtime { - fn query_info(uxt: ExtrinsicFor, len: u32) -> RuntimeDispatchInfo { - TransactionPayment::query_info(uxt, len) - } - fn query_fee_details(uxt: ExtrinsicFor, len: u32) -> FeeDetails { - TransactionPayment::query_fee_details(uxt, len) - } - fn query_weight_to_fee(weight: Weight) -> Balance { - TransactionPayment::weight_to_fee(weight) - } - fn query_length_to_fee(length: u32) -> Balance { - TransactionPayment::length_to_fee(length) - } - } - - impl apis::GenesisBuilder for Runtime { - fn build_state(config: Vec) -> sp_genesis_builder::Result { - build_state::(config) - } - - fn get_preset(id: &Option) -> Option> { - get_preset::(id, self::genesis_config_presets::get_preset) - } - - fn preset_names() -> Vec { - self::genesis_config_presets::preset_names() - } - } + Runtime, + Revive, + Executive, + EthExtraImpl, + + impl apis::Core for Runtime { + fn version() -> RuntimeVersion { + VERSION + } + + fn execute_block(block: ::LazyBlock) { + Executive::execute_block(block) + } + + fn initialize_block(header: &Header) -> ExtrinsicInclusionMode { + Executive::initialize_block(header) + } + } + + impl apis::Metadata for Runtime { + fn metadata() -> OpaqueMetadata { + OpaqueMetadata::new(Runtime::metadata().into()) + } + + fn metadata_at_version(version: u32) -> Option { + Runtime::metadata_at_version(version) + } + + fn metadata_versions() -> Vec { + Runtime::metadata_versions() + } + } + + impl apis::BlockBuilder for Runtime { + fn apply_extrinsic(extrinsic: ExtrinsicFor) -> ApplyExtrinsicResult { + Executive::apply_extrinsic(extrinsic) + } + + fn finalize_block() -> HeaderFor { + Executive::finalize_block() + } + + fn inherent_extrinsics(data: InherentData) -> Vec> { + data.create_extrinsics() + } + + fn check_inherents( + block: ::LazyBlock, + data: InherentData, + ) -> CheckInherentsResult { + data.check_extrinsics(&block) + } + } + + impl apis::TaggedTransactionQueue for Runtime { + fn validate_transaction( + source: TransactionSource, + tx: ExtrinsicFor, + block_hash: ::Hash, + ) -> TransactionValidity { + Executive::validate_transaction(source, tx, block_hash) + } + } + + impl apis::OffchainWorkerApi for Runtime { + fn offchain_worker(header: &HeaderFor) { + Executive::offchain_worker(header) + } + } + + impl apis::SessionKeys for Runtime { + fn generate_session_keys(_seed: Option>) -> Vec { + Default::default() + } + + fn decode_session_keys( + _encoded: Vec, + ) -> Option, apis::KeyTypeId)>> { + Default::default() + } + } + + impl apis::AccountNonceApi for Runtime { + fn account_nonce(account: AccountId) -> Nonce { + System::account_nonce(account) + } + } + + impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi< + Block, + Balance, + > for Runtime { + fn query_info(uxt: ExtrinsicFor, len: u32) -> RuntimeDispatchInfo { + TransactionPayment::query_info(uxt, len) + } + fn query_fee_details(uxt: ExtrinsicFor, len: u32) -> FeeDetails { + TransactionPayment::query_fee_details(uxt, len) + } + fn query_weight_to_fee(weight: Weight) -> Balance { + TransactionPayment::weight_to_fee(weight) + } + fn query_length_to_fee(length: u32) -> Balance { + TransactionPayment::length_to_fee(length) + } + } + + impl apis::GenesisBuilder for Runtime { + fn build_state(config: Vec) -> sp_genesis_builder::Result { + build_state::(config) + } + + fn get_preset(id: &Option) -> Option> { + get_preset::(id, self::genesis_config_presets::get_preset) + } + + fn preset_names() -> Vec { + self::genesis_config_presets::preset_names() + } + } ); diff --git a/substrate/frame/revive/proc-macro/src/lib.rs b/substrate/frame/revive/proc-macro/src/lib.rs index 8ce6216ddd568..e5b564f6008c2 100644 --- a/substrate/frame/revive/proc-macro/src/lib.rs +++ b/substrate/frame/revive/proc-macro/src/lib.rs @@ -27,13 +27,13 @@ use syn::{parse_quote, punctuated::Punctuated, spanned::Spanned, token::Comma, F #[proc_macro_attribute] pub fn unstable_hostfn(_attr: TokenStream, item: TokenStream) -> TokenStream { - let input = syn::parse_macro_input!(item as syn::Item); - let expanded = quote! { - #[cfg(feature = "unstable-hostfn")] - #[cfg_attr(docsrs, doc(cfg(feature = "unstable-hostfn")))] - #input - }; - expanded.into() + let input = syn::parse_macro_input!(item as syn::Item); + let expanded = quote! { + #[cfg(feature = "unstable-hostfn")] + #[cfg_attr(docsrs, doc(cfg(feature = "unstable-hostfn")))] + #input + }; + expanded.into() } /// Defines a host functions set that can be imported by contract polkavm code. @@ -48,299 +48,286 @@ pub fn unstable_hostfn(_attr: TokenStream, item: TokenStream) -> TokenStream { /// corruption or unexpected results within the contract. #[proc_macro_attribute] pub fn define_env(attr: TokenStream, item: TokenStream) -> TokenStream { - if !attr.is_empty() { - let msg = r#"Invalid `define_env` attribute macro: expected no attributes: + if !attr.is_empty() { + let msg = r#"Invalid `define_env` attribute macro: expected no attributes: - `#[define_env]`"#; - let span = TokenStream2::from(attr).span(); - return syn::Error::new(span, msg).to_compile_error().into(); - } + let span = TokenStream2::from(attr).span(); + return syn::Error::new(span, msg).to_compile_error().into(); + } - let item = syn::parse_macro_input!(item as syn::ItemMod); + let item = syn::parse_macro_input!(item as syn::ItemMod); - match EnvDef::try_from(item) { - Ok(mut def) => expand_env(&mut def).into(), - Err(e) => e.to_compile_error().into(), - } + match EnvDef::try_from(item) { + Ok(mut def) => expand_env(&mut def).into(), + Err(e) => e.to_compile_error().into(), + } } /// Parsed environment definition. struct EnvDef { - host_funcs: Vec, + host_funcs: Vec, } /// Parsed host function definition. struct HostFn { - item: syn::ItemFn, - is_stable: bool, - name: String, - returns: HostFnReturn, - cfg: Option, + item: syn::ItemFn, + is_stable: bool, + name: String, + returns: HostFnReturn, + cfg: Option, } enum HostFnReturn { - Unit, - U32, - U64, - ReturnCode, + Unit, + U32, + U64, + ReturnCode, } impl HostFnReturn { - fn map_output(&self) -> TokenStream2 { - match self { - Self::Unit => quote! { |_| None }, - _ => quote! { |ret_val| Some(ret_val.into()) }, - } - } - - fn success_type(&self) -> syn::ReturnType { - match self { - Self::Unit => syn::ReturnType::Default, - Self::U32 => parse_quote! { -> u32 }, - Self::U64 => parse_quote! { -> u64 }, - Self::ReturnCode => parse_quote! { -> ReturnErrorCode }, - } - } + fn map_output(&self) -> TokenStream2 { + match self { + Self::Unit => quote! { |_| None }, + _ => quote! { |ret_val| Some(ret_val.into()) }, + } + } + + fn success_type(&self) -> syn::ReturnType { + match self { + Self::Unit => syn::ReturnType::Default, + Self::U32 => parse_quote! { -> u32 }, + Self::U64 => parse_quote! { -> u64 }, + Self::ReturnCode => parse_quote! { -> ReturnErrorCode }, + } + } } impl EnvDef { - pub fn try_from(item: syn::ItemMod) -> syn::Result { - let span = item.span(); - let err = |msg| syn::Error::new(span, msg); - let items = &item - .content - .as_ref() - .ok_or(err( - "Invalid environment definition, expected `mod` to be inlined.", - ))? - .1; - - let extract_fn = |i: &syn::Item| match i { - syn::Item::Fn(i_fn) => Some(i_fn.clone()), - _ => None, - }; - - let host_funcs = items - .iter() - .filter_map(extract_fn) - .map(HostFn::try_from) - .collect::, _>>()?; - - Ok(Self { host_funcs }) - } + pub fn try_from(item: syn::ItemMod) -> syn::Result { + let span = item.span(); + let err = |msg| syn::Error::new(span, msg); + let items = &item + .content + .as_ref() + .ok_or(err("Invalid environment definition, expected `mod` to be inlined."))? + .1; + + let extract_fn = |i: &syn::Item| match i { + syn::Item::Fn(i_fn) => Some(i_fn.clone()), + _ => None, + }; + + let host_funcs = items + .iter() + .filter_map(extract_fn) + .map(HostFn::try_from) + .collect::, _>>()?; + + Ok(Self { host_funcs }) + } } impl HostFn { - pub fn try_from(mut item: syn::ItemFn) -> syn::Result { - let err = |span, msg| { - let msg = format!("Invalid host function definition.\n{}", msg); - syn::Error::new(span, msg) - }; - - // process attributes - let msg = "Only #[stable], #[cfg] and #[mutating] attributes are allowed."; - let span = item.span(); - let mut attrs = item.attrs.clone(); - attrs.retain(|a| !a.path().is_ident("doc")); - let mut is_stable = false; - let mut mutating = false; - let mut cfg = None; - while let Some(attr) = attrs.pop() { - let ident = attr.path().get_ident().ok_or(err(span, msg))?.to_string(); - match ident.as_str() { - "stable" => { - if is_stable { - return Err(err(span, "#[stable] can only be specified once")); - } - is_stable = true; - } - "mutating" => { - if mutating { - return Err(err(span, "#[mutating] can only be specified once")); - } - mutating = true; - } - "cfg" => { - if cfg.is_some() { - return Err(err(span, "#[cfg] can only be specified once")); - } - cfg = Some(attr); - } - id => return Err(err(span, &format!("Unsupported attribute \"{id}\". {msg}"))), - } - } - - if mutating { - let stmt = syn::parse_quote! { - if self.ext().is_read_only() { - return Err(Error::::StateChangeDenied.into()); - } - }; - item.block.stmts.insert(0, stmt); - } - - let name = item.sig.ident.to_string(); - - let msg = "Every function must start with these two parameters: &mut self, memory: &mut M"; - let special_args = item - .sig - .inputs - .iter() - .take(2) - .enumerate() - .map(|(i, arg)| is_valid_special_arg(i, arg)) - .fold(0u32, |acc, valid| if valid { acc + 1 } else { acc }); - - if special_args != 2 { - return Err(err(span, msg)); - } - - // process return type - let msg = r#"Should return one of the following: + pub fn try_from(mut item: syn::ItemFn) -> syn::Result { + let err = |span, msg| { + let msg = format!("Invalid host function definition.\n{}", msg); + syn::Error::new(span, msg) + }; + + // process attributes + let msg = "Only #[stable], #[cfg] and #[mutating] attributes are allowed."; + let span = item.span(); + let mut attrs = item.attrs.clone(); + attrs.retain(|a| !a.path().is_ident("doc")); + let mut is_stable = false; + let mut mutating = false; + let mut cfg = None; + while let Some(attr) = attrs.pop() { + let ident = attr.path().get_ident().ok_or(err(span, msg))?.to_string(); + match ident.as_str() { + "stable" => { + if is_stable { + return Err(err(span, "#[stable] can only be specified once")); + } + is_stable = true; + }, + "mutating" => { + if mutating { + return Err(err(span, "#[mutating] can only be specified once")); + } + mutating = true; + }, + "cfg" => { + if cfg.is_some() { + return Err(err(span, "#[cfg] can only be specified once")); + } + cfg = Some(attr); + }, + id => return Err(err(span, &format!("Unsupported attribute \"{id}\". {msg}"))), + } + } + + if mutating { + let stmt = syn::parse_quote! { + if self.ext().is_read_only() { + return Err(Error::::StateChangeDenied.into()); + } + }; + item.block.stmts.insert(0, stmt); + } + + let name = item.sig.ident.to_string(); + + let msg = "Every function must start with these two parameters: &mut self, memory: &mut M"; + let special_args = item + .sig + .inputs + .iter() + .take(2) + .enumerate() + .map(|(i, arg)| is_valid_special_arg(i, arg)) + .fold(0u32, |acc, valid| if valid { acc + 1 } else { acc }); + + if special_args != 2 { + return Err(err(span, msg)); + } + + // process return type + let msg = r#"Should return one of the following: - Result<(), TrapReason>, - Result, - Result, - Result"#; - let ret_ty = match item.clone().sig.output { - syn::ReturnType::Type(_, ty) => Ok(ty.clone()), - _ => Err(err(span, &msg)), - }?; - match *ret_ty { - syn::Type::Path(tp) => { - let result = &tp.path.segments.last().ok_or(err(span, &msg))?; - let (id, span) = (result.ident.to_string(), result.ident.span()); - id.eq(&"Result".to_string()) - .then_some(()) - .ok_or(err(span, &msg))?; - - match &result.arguments { - syn::PathArguments::AngleBracketed(group) => { - if group.args.len() != 2 { - return Err(err(span, &msg)); - }; - - let arg2 = group.args.last().ok_or(err(span, &msg))?; - - let err_ty = match arg2 { - syn::GenericArgument::Type(ty) => Ok(ty.clone()), - _ => Err(err(arg2.span(), &msg)), - }?; - - match err_ty { - syn::Type::Path(tp) => Ok(tp - .path - .segments - .first() - .ok_or(err(arg2.span(), &msg))? - .ident - .to_string()), - _ => Err(err(tp.span(), &msg)), - }? - .eq("TrapReason") - .then_some(()) - .ok_or(err(span, &msg))?; - - let arg1 = group.args.first().ok_or(err(span, &msg))?; - let ok_ty = match arg1 { - syn::GenericArgument::Type(ty) => Ok(ty.clone()), - _ => Err(err(arg1.span(), &msg)), - }?; - let ok_ty_str = match ok_ty { - syn::Type::Path(tp) => Ok(tp - .path - .segments - .first() - .ok_or(err(arg1.span(), &msg))? - .ident - .to_string()), - syn::Type::Tuple(tt) => { - if !tt.elems.is_empty() { - return Err(err(arg1.span(), &msg)); - }; - Ok("()".to_string()) - } - _ => Err(err(ok_ty.span(), &msg)), - }?; - let returns = match ok_ty_str.as_str() { - "()" => Ok(HostFnReturn::Unit), - "u32" => Ok(HostFnReturn::U32), - "u64" => Ok(HostFnReturn::U64), - "ReturnErrorCode" => Ok(HostFnReturn::ReturnCode), - _ => Err(err(arg1.span(), &msg)), - }?; - - Ok(Self { - item, - is_stable, - name, - returns, - cfg, - }) - } - _ => Err(err(span, &msg)), - } - } - _ => Err(err(span, &msg)), - } - } + let ret_ty = match item.clone().sig.output { + syn::ReturnType::Type(_, ty) => Ok(ty.clone()), + _ => Err(err(span, &msg)), + }?; + match *ret_ty { + syn::Type::Path(tp) => { + let result = &tp.path.segments.last().ok_or(err(span, &msg))?; + let (id, span) = (result.ident.to_string(), result.ident.span()); + id.eq(&"Result".to_string()).then_some(()).ok_or(err(span, &msg))?; + + match &result.arguments { + syn::PathArguments::AngleBracketed(group) => { + if group.args.len() != 2 { + return Err(err(span, &msg)); + }; + + let arg2 = group.args.last().ok_or(err(span, &msg))?; + + let err_ty = match arg2 { + syn::GenericArgument::Type(ty) => Ok(ty.clone()), + _ => Err(err(arg2.span(), &msg)), + }?; + + match err_ty { + syn::Type::Path(tp) => Ok(tp + .path + .segments + .first() + .ok_or(err(arg2.span(), &msg))? + .ident + .to_string()), + _ => Err(err(tp.span(), &msg)), + }? + .eq("TrapReason") + .then_some(()) + .ok_or(err(span, &msg))?; + + let arg1 = group.args.first().ok_or(err(span, &msg))?; + let ok_ty = match arg1 { + syn::GenericArgument::Type(ty) => Ok(ty.clone()), + _ => Err(err(arg1.span(), &msg)), + }?; + let ok_ty_str = match ok_ty { + syn::Type::Path(tp) => Ok(tp + .path + .segments + .first() + .ok_or(err(arg1.span(), &msg))? + .ident + .to_string()), + syn::Type::Tuple(tt) => { + if !tt.elems.is_empty() { + return Err(err(arg1.span(), &msg)); + }; + Ok("()".to_string()) + }, + _ => Err(err(ok_ty.span(), &msg)), + }?; + let returns = match ok_ty_str.as_str() { + "()" => Ok(HostFnReturn::Unit), + "u32" => Ok(HostFnReturn::U32), + "u64" => Ok(HostFnReturn::U64), + "ReturnErrorCode" => Ok(HostFnReturn::ReturnCode), + _ => Err(err(arg1.span(), &msg)), + }?; + + Ok(Self { item, is_stable, name, returns, cfg }) + }, + _ => Err(err(span, &msg)), + } + }, + _ => Err(err(span, &msg)), + } + } } fn is_valid_special_arg(idx: usize, arg: &FnArg) -> bool { - match (idx, arg) { - (0, FnArg::Receiver(rec)) => rec.reference.is_some() && rec.mutability.is_some(), - (1, FnArg::Typed(pat)) => { - let ident = if let syn::Pat::Ident(ref ident) = *pat.pat { - &ident.ident - } else { - return false; - }; - if !(ident == "memory" || ident == "_memory") { - return false; - } - matches!(*pat.ty, syn::Type::Reference(_)) - } - _ => false, - } + match (idx, arg) { + (0, FnArg::Receiver(rec)) => rec.reference.is_some() && rec.mutability.is_some(), + (1, FnArg::Typed(pat)) => { + let ident = if let syn::Pat::Ident(ref ident) = *pat.pat { + &ident.ident + } else { + return false; + }; + if !(ident == "memory" || ident == "_memory") { + return false; + } + matches!(*pat.ty, syn::Type::Reference(_)) + }, + _ => false, + } } fn arg_decoder<'a, P, I>(param_names: P, param_types: I) -> TokenStream2 where - P: Iterator> + Clone, - I: Iterator> + Clone, + P: Iterator> + Clone, + I: Iterator> + Clone, { - const ALLOWED_REGISTERS: usize = 6; - - // too many arguments - if param_names.clone().count() > ALLOWED_REGISTERS { - panic!("Syscalls take a maximum of {ALLOWED_REGISTERS} arguments"); - } - - // all of them take one register but we truncate them before passing into the function - // it is important to not allow any type which has illegal bit patterns like 'bool' - if !param_types.clone().all(|ty| { - let syn::Type::Path(path) = &**ty else { - panic!("Type needs to be path"); - }; - let Some(ident) = path.path.get_ident() else { - panic!("Type needs to be ident"); - }; - matches!(ident.to_string().as_ref(), "u8" | "u16" | "u32" | "u64") - }) { - panic!("Only primitive unsigned integers are allowed as arguments to syscalls"); - } - - // one argument per register - let bindings = param_names - .zip(param_types) - .enumerate() - .map(|(idx, (name, ty))| { - let reg = quote::format_ident!("__a{}__", idx); - quote! { - let #name = #reg as #ty; - } - }); - quote! { - #( #bindings )* - } + const ALLOWED_REGISTERS: usize = 6; + + // too many arguments + if param_names.clone().count() > ALLOWED_REGISTERS { + panic!("Syscalls take a maximum of {ALLOWED_REGISTERS} arguments"); + } + + // all of them take one register but we truncate them before passing into the function + // it is important to not allow any type which has illegal bit patterns like 'bool' + if !param_types.clone().all(|ty| { + let syn::Type::Path(path) = &**ty else { + panic!("Type needs to be path"); + }; + let Some(ident) = path.path.get_ident() else { + panic!("Type needs to be ident"); + }; + matches!(ident.to_string().as_ref(), "u8" | "u16" | "u32" | "u64") + }) { + panic!("Only primitive unsigned integers are allowed as arguments to syscalls"); + } + + // one argument per register + let bindings = param_names.zip(param_types).enumerate().map(|(idx, (name, ty))| { + let reg = quote::format_ident!("__a{}__", idx); + quote! { + let #name = #reg as #ty; + } + }); + quote! { + #( #bindings )* + } } /// Expands environment definition. @@ -348,67 +335,67 @@ where /// - implementations of the host functions to be added to the polkavm runtime environment (see /// `expand_impls()`). fn expand_env(def: &EnvDef) -> TokenStream2 { - let impls = expand_functions(def); - let bench_impls = expand_bench_functions(def); - let docs = expand_func_doc(def); - let stable_syscalls = expand_func_list(def, false); - let all_syscalls = expand_func_list(def, true); - let lookup_syscall = expand_func_lookup(def); - - quote! { - /// Returns the list of all syscalls. - pub fn all_syscalls() -> &'static [&'static [u8]] { - #all_syscalls - } - - /// Returns the list of stable syscalls without unstable ones. - pub fn list_syscalls(include_unstable: bool) -> &'static [&'static [u8]] { - if include_unstable { - #all_syscalls - } else { - #stable_syscalls - } - } - - /// Return the index of a syscall in the `all_syscalls()` list. - pub fn lookup_syscall_index(name: &'static str) -> Option { - #lookup_syscall - } - - impl<'a, E: Ext, M: PolkaVmInstance> Runtime<'a, E, M> { - fn handle_ecall( - &mut self, - memory: &mut M, - __syscall_symbol__: &[u8], - ) -> Result, TrapReason> - { - #impls - } - } - - #[cfg(feature = "runtime-benchmarks")] - impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { - #bench_impls - } - - /// Documentation of the syscalls (host functions) available to contracts. - /// - /// Each of the functions in this trait represent a function that is callable - /// by the contract. Guests use the function name as the import symbol. - /// - /// # Note - /// - /// This module is not meant to be used by any code. Rather, it is meant to be - /// consumed by humans through rustdoc. - #[cfg(doc)] - pub trait SyscallDoc { - #docs - } - } + let impls = expand_functions(def); + let bench_impls = expand_bench_functions(def); + let docs = expand_func_doc(def); + let stable_syscalls = expand_func_list(def, false); + let all_syscalls = expand_func_list(def, true); + let lookup_syscall = expand_func_lookup(def); + + quote! { + /// Returns the list of all syscalls. + pub fn all_syscalls() -> &'static [&'static [u8]] { + #all_syscalls + } + + /// Returns the list of stable syscalls without unstable ones. + pub fn list_syscalls(include_unstable: bool) -> &'static [&'static [u8]] { + if include_unstable { + #all_syscalls + } else { + #stable_syscalls + } + } + + /// Return the index of a syscall in the `all_syscalls()` list. + pub fn lookup_syscall_index(name: &'static str) -> Option { + #lookup_syscall + } + + impl<'a, E: Ext, M: PolkaVmInstance> Runtime<'a, E, M> { + fn handle_ecall( + &mut self, + memory: &mut M, + __syscall_symbol__: &[u8], + ) -> Result, TrapReason> + { + #impls + } + } + + #[cfg(feature = "runtime-benchmarks")] + impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { + #bench_impls + } + + /// Documentation of the syscalls (host functions) available to contracts. + /// + /// Each of the functions in this trait represent a function that is callable + /// by the contract. Guests use the function name as the import symbol. + /// + /// # Note + /// + /// This module is not meant to be used by any code. Rather, it is meant to be + /// consumed by humans through rustdoc. + #[cfg(doc)] + pub trait SyscallDoc { + #docs + } + } } fn expand_functions(def: &EnvDef) -> TokenStream2 { - let impls = def.host_funcs.iter().map(|f| { + let impls = def.host_funcs.iter().map(|f| { // skip the self and memory argument let params = f.item.sig.inputs.iter().skip(2); let param_names = params.clone().filter_map(|arg| { @@ -482,139 +469,130 @@ fn expand_functions(def: &EnvDef) -> TokenStream2 { } }); - quote! { - // Write gas from polkavm into pallet-revive before entering the host function. - self.ext - .frame_meter_mut() - .sync_from_executor(memory.gas()) - .map_err(TrapReason::from)?; - - // This is the overhead to call an empty syscall that always needs to be charged. - self.charge_gas(crate::vm::RuntimeCosts::HostFn).map_err(TrapReason::from)?; - - // They will be mapped to variable names by the syscall specific code. - let (__a0__, __a1__, __a2__, __a3__, __a4__, __a5__) = memory.read_input_regs(); - - // Execute the syscall specific logic in a closure so that the gas metering code is always executed. - let result = (|| match __syscall_symbol__ { - #( #impls )* - _ => Err(TrapReason::SupervisorError(Error::::InvalidSyscall.into())) - })(); - - // Write gas from pallet-revive into polkavm after leaving the host function. - let gas = self.ext.frame_meter_mut().sync_to_executor(); - memory.set_gas(gas.into()); - result - } + quote! { + // Write gas from polkavm into pallet-revive before entering the host function. + self.ext + .frame_meter_mut() + .sync_from_executor(memory.gas()) + .map_err(TrapReason::from)?; + + // This is the overhead to call an empty syscall that always needs to be charged. + self.charge_gas(crate::vm::RuntimeCosts::HostFn).map_err(TrapReason::from)?; + + // They will be mapped to variable names by the syscall specific code. + let (__a0__, __a1__, __a2__, __a3__, __a4__, __a5__) = memory.read_input_regs(); + + // Execute the syscall specific logic in a closure so that the gas metering code is always executed. + let result = (|| match __syscall_symbol__ { + #( #impls )* + _ => Err(TrapReason::SupervisorError(Error::::InvalidSyscall.into())) + })(); + + // Write gas from pallet-revive into polkavm after leaving the host function. + let gas = self.ext.frame_meter_mut().sync_to_executor(); + memory.set_gas(gas.into()); + result + } } fn expand_bench_functions(def: &EnvDef) -> TokenStream2 { - let impls = def.host_funcs.iter().map(|f| { - // skip the context and memory argument - let params = f.item.sig.inputs.iter().skip(2); - let cfg = &f.cfg; - let name = &f.name; - let body = &f.item.block; - let output = &f.item.sig.output; - - let name = Ident::new(&format!("bench_{name}"), Span::call_site()); - quote! { - #cfg - pub fn #name(&mut self, memory: &mut M, #(#params),*) #output { - #body - } - } - }); - - quote! { - #( #impls )* - } + let impls = def.host_funcs.iter().map(|f| { + // skip the context and memory argument + let params = f.item.sig.inputs.iter().skip(2); + let cfg = &f.cfg; + let name = &f.name; + let body = &f.item.block; + let output = &f.item.sig.output; + + let name = Ident::new(&format!("bench_{name}"), Span::call_site()); + quote! { + #cfg + pub fn #name(&mut self, memory: &mut M, #(#params),*) #output { + #body + } + } + }); + + quote! { + #( #impls )* + } } fn expand_func_doc(def: &EnvDef) -> TokenStream2 { - let docs = def.host_funcs.iter().map(|func| { - // Remove auxiliary args: `ctx: _` and `memory: _` - let func_decl = { - let mut sig = func.item.sig.clone(); - sig.inputs = sig - .inputs - .iter() - .skip(2) - .map(|p| p.clone()) - .collect::>(); - sig.output = func.returns.success_type(); - sig.to_token_stream() - }; - let func_doc = { - let func_docs = { - let docs = func - .item - .attrs - .iter() - .filter(|a| a.path().is_ident("doc")) - .map(|d| { - let docs = d.to_token_stream(); - quote! { #docs } - }); - quote! { #( #docs )* } - }; - let availability = if func.is_stable { - let info = "\n# Stable API\nThis API is stable and will never change."; - quote! { #[doc = #info] } - } else { - let info = + let docs = def.host_funcs.iter().map(|func| { + // Remove auxiliary args: `ctx: _` and `memory: _` + let func_decl = { + let mut sig = func.item.sig.clone(); + sig.inputs = sig + .inputs + .iter() + .skip(2) + .map(|p| p.clone()) + .collect::>(); + sig.output = func.returns.success_type(); + sig.to_token_stream() + }; + let func_doc = { + let func_docs = { + let docs = func.item.attrs.iter().filter(|a| a.path().is_ident("doc")).map(|d| { + let docs = d.to_token_stream(); + quote! { #docs } + }); + quote! { #( #docs )* } + }; + let availability = if func.is_stable { + let info = "\n# Stable API\nThis API is stable and will never change."; + quote! { #[doc = #info] } + } else { + let info = "\n# Unstable API\nThis API is not standardized and only available for testing."; - quote! { #[doc = #info] } - }; - quote! { - #func_docs - #availability - } - }; - quote! { - #func_doc - #func_decl; - } - }); - - quote! { - #( #docs )* - } + quote! { #[doc = #info] } + }; + quote! { + #func_docs + #availability + } + }; + quote! { + #func_doc + #func_decl; + } + }); + + quote! { + #( #docs )* + } } fn expand_func_list(def: &EnvDef, include_unstable: bool) -> TokenStream2 { - let docs = def - .host_funcs - .iter() - .filter(|f| include_unstable || f.is_stable) - .map(|f| { - let name = Literal::byte_string(f.name.as_bytes()); - quote! { - #name.as_slice() - } - }); - let len = docs.clone().count(); - - quote! { - { - static FUNCS: [&[u8]; #len] = [#(#docs),*]; - FUNCS.as_slice() - } - } + let docs = def.host_funcs.iter().filter(|f| include_unstable || f.is_stable).map(|f| { + let name = Literal::byte_string(f.name.as_bytes()); + quote! { + #name.as_slice() + } + }); + let len = docs.clone().count(); + + quote! { + { + static FUNCS: [&[u8]; #len] = [#(#docs),*]; + FUNCS.as_slice() + } + } } fn expand_func_lookup(def: &EnvDef) -> TokenStream2 { - let arms = def.host_funcs.iter().enumerate().map(|(idx, f)| { - let name_str = &f.name; - quote! { - #name_str => Some(#idx as u32) - } - }); - - quote! { - match name { - #( #arms, )* - _ => None, - } - } + let arms = def.host_funcs.iter().enumerate().map(|(idx, f)| { + let name_str = &f.name; + quote! { + #name_str => Some(#idx as u32) + } + }); + + quote! { + match name { + #( #arms, )* + _ => None, + } + } } diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 5b3a8a536ecd5..44a30ebf0114a 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -49,7 +49,7 @@ pub mod weights; use crate::{ evm::{ block_hash::EthereumBlockBuilderIR, block_storage, fees::InfoT as FeeInfo, - runtime::SetWeightLimit, CallTracer, CreateCallMode, GenericTransaction, ExecutionTracer, + runtime::SetWeightLimit, CallTracer, CreateCallMode, ExecutionTracer, GenericTransaction, PrestateTracer, Trace, Tracer, TracerType, TYPE_EIP1559, }, exec::{AccountIdOf, ExecError, ReentrancyProtection, Stack as ExecStack}, From 86f00cff49e0ac46667508009c63f32b37c93434 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Tue, 16 Dec 2025 15:26:18 +0100 Subject: [PATCH 60/76] fix --- .../frame/revive/rpc/src/apis/debug_apis.rs | 18 +++++++++--------- .../revive/src/evm/api/debug_rpc_types.rs | 2 +- substrate/frame/revive/src/gas.rs | 0 3 files changed, 10 insertions(+), 10 deletions(-) delete mode 100644 substrate/frame/revive/src/gas.rs diff --git a/substrate/frame/revive/rpc/src/apis/debug_apis.rs b/substrate/frame/revive/rpc/src/apis/debug_apis.rs index 6704d74b13f8d..3eb1822fb8b64 100644 --- a/substrate/frame/revive/rpc/src/apis/debug_apis.rs +++ b/substrate/frame/revive/rpc/src/apis/debug_apis.rs @@ -29,7 +29,7 @@ pub trait DebugRpc { async fn trace_block_by_number( &self, block: BlockNumberOrTag, - tracer_config: TracerConfig, + tracer_config: Option, ) -> RpcResult>; /// Returns a transaction's traces by replaying it. @@ -41,7 +41,7 @@ pub trait DebugRpc { async fn trace_transaction( &self, transaction_hash: H256, - tracer_config: TracerConfig, + tracer_config: Option, ) -> RpcResult; /// Dry run a call and returns the transaction's traces. @@ -54,7 +54,7 @@ pub trait DebugRpc { &self, transaction: GenericTransaction, block: BlockNumberOrTagOrHash, - tracer_config: TracerConfig, + tracer_config: Option, ) -> RpcResult; #[method(name = "debug_getAutomine")] @@ -94,18 +94,18 @@ impl DebugRpcServer for DebugRpcServerImpl { async fn trace_block_by_number( &self, block: BlockNumberOrTag, - tracer_config: TracerConfig, + tracer_config: Option, ) -> RpcResult> { - let TracerConfig { config, timeout } = tracer_config; + let TracerConfig { config, timeout } = tracer_config.unwrap_or_default(); with_timeout(timeout, self.client.trace_block_by_number(block, config)).await } async fn trace_transaction( &self, transaction_hash: H256, - tracer_config: TracerConfig, + tracer_config: Option, ) -> RpcResult { - let TracerConfig { config, timeout } = tracer_config; + let TracerConfig { config, timeout } = tracer_config.unwrap_or_default(); with_timeout(timeout, self.client.trace_transaction(transaction_hash, config)).await } @@ -113,9 +113,9 @@ impl DebugRpcServer for DebugRpcServerImpl { &self, transaction: GenericTransaction, block: BlockNumberOrTagOrHash, - tracer_config: TracerConfig, + tracer_config: Option, ) -> RpcResult { - let TracerConfig { config, timeout } = tracer_config; + let TracerConfig { config, timeout } = tracer_config.unwrap_or_default(); with_timeout(timeout, self.client.trace_call(transaction, block, config)).await } diff --git a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs index 4f4b097f78ad4..cba531ccbebfd 100644 --- a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs +++ b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs @@ -61,7 +61,7 @@ impl From for TracerType { impl Default for TracerType { fn default() -> Self { - TracerType::CallTracer(Some(CallTracerConfig::default())) + TracerType::StructLogger(Some(StructLoggerConfig::default())) } } diff --git a/substrate/frame/revive/src/gas.rs b/substrate/frame/revive/src/gas.rs deleted file mode 100644 index e69de29bb2d1d..0000000000000 From 0428a8a471ef14022fb257f344d471444eb1a363 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Tue, 16 Dec 2025 15:44:03 +0100 Subject: [PATCH 61/76] fix PR doc --- prdoc/pr_9722.prdoc | 116 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 91 insertions(+), 25 deletions(-) diff --git a/prdoc/pr_9722.prdoc b/prdoc/pr_9722.prdoc index c13a2dcab802b..09e607c686b6b 100644 --- a/prdoc/pr_9722.prdoc +++ b/prdoc/pr_9722.prdoc @@ -1,31 +1,97 @@ title: '[pallet-revive] opcode tracer' doc: - audience: Runtime Dev - description: "This PR introduces a **Geth-compatible execution tracer** ([StructLogger](https://geth.ethereum.org/docs/developers/evm-tracing/built-in-tracers#struct-opcode-logger))\ - \ for pallet-revive\n\n The tracer can be used to capture both EVM opcode and\ - \ PVM syscall.\n It can be used with the same RPC endpoint as Geth StructLogger.\n\ - \n\nSince it can be quite resource intensive, It can only be queried from the\ - \ node when the **DebugSettings** are enabled (This is turned on now by default\ - \ in the dev-node)\n\nTested in https://github.com/paritytech/evm-test-suite/pull/138\n\ - \n\nexample:\n\n```sh\n\u276F cast rpc debug_traceTransaction \"\" |\ - \ jq\n\n# or with options\n# See list of options https://geth.ethereum.org/docs/developers/evm-tracing/built-in-tracers#struct-opcode-logger\n\ - \n \u276F cast rpc debug_traceTransaction \"\", { \"tracer\": { \"enableMemory\"\ - : true } } | jq\n```\n\nFor an EVM execution, the output will look like this\n\ - \n```json\n{\n \"gas\": 16208049,\n \"failed\": false,\n \"returnValue\": \"\ - 0x\",\n \"structLogs\": [\n {\n \"gas\": 14609533,\n \"gasCost\"\ - : 1,\n \"depth\": 1,\n \"pc\": 0,\n \"op\": \"PUSH1\",\n \"\ - stack\": []\n },\n {\n \"gas\": 14609532,\n \"gasCost\": 1,\n\ - \ \"depth\": 1,\n \"pc\": 2,\n \"op\": \"PUSH1\",\n \"stack\"\ - : [\n \"0x80\"\n ]\n },\n {\n \"gas\": 14609531,\n \ - \ \"gasCost\": 1,\n \"depth\": 1,\n \"pc\": 4,\n \"op\": \"MSTORE\"\ - ,\n \"stack\": [\n \"0x80\",\n \"0x40\"\n ]\n }]\n\ - }\n```\n\nFor a PVM execution the response will look like this\n\n```json\n{\n\ - \ \"gas\": 279456,\n \"failed\": false,\n \"returnValue\": \"0x\",\n \"structLogs\"\ - : [\n {\n \"gas\": 27108,\n \"gasCost\": 7,\n \"depth\": 1,\n\ - \ \"op\": \"call_data_size\"\n },\n {\n \"gas\": 26977,\n \ - \ \"gasCost\": 7,\n \"depth\": 1,\n \"op\": \"call_data_load\"\n \ - \ },\n {\n \"gas\": 26957,\n \"gasCost\": 7,\n \"depth\": 1,\n\ - \ \"op\": \"value_transferred\"\n }]\n}\n```" + description: | + This PR introduces a **Geth-compatible execution tracer** ([StructLogger](https://geth.ethereum.org/docs/developers/evm-tracing/built-in-tracers#struct-opcode-logger)) for pallet-revive + + The tracer can be used to capture both EVM opcode and PVM syscall. + It can be used with the same RPC endpoint as Geth StructLogger. + + + Since it can be quite resource intensive, It can only be queried from the node when the **DebugSettings** are enabled (This is turned on now by default in the dev-node) + + Tested in https://github.com/paritytech/evm-test-suite/pull/138 + + + example: + + ```sh + ❯ cast rpc debug_traceTransaction "" | jq + + # or with options + # See list of options https://geth.ethereum.org/docs/developers/evm-tracing/built-in-tracers#struct-opcode-logger + + ❯ cast rpc debug_traceTransaction "", { "tracer": { "enableMemory": true } } | jq + ``` + + For an EVM execution, the output will look like this + + ```json + { + "gas": 16208049, + "failed": false, + "returnValue": "0x", + "structLogs": [ + { + "gas": 14609533, + "gasCost": 1, + "depth": 1, + "pc": 0, + "op": "PUSH1", + "stack": [] + }, + { + "gas": 14609532, + "gasCost": 1, + "depth": 1, + "pc": 2, + "op": "PUSH1", + "stack": [ + "0x80" + ] + }, + { + "gas": 14609531, + "gasCost": 1, + "depth": 1, + "pc": 4, + "op": "MSTORE", + "stack": [ + "0x80", + "0x40" + ] + }] + } + ``` + + For a PVM execution the response will look like this + + ```json + { + "gas": 279456, + "failed": false, + "returnValue": "0x", + "structLogs": [ + { + "gas": 27108, + "gasCost": 7, + "depth": 1, + "op": "call_data_size" + }, + { + "gas": 26977, + "gasCost": 7, + "depth": 1, + "op": "call_data_load" + }, + { + "gas": 26957, + "gasCost": 7, + "depth": 1, + "op": "value_transferred" + }] + } + ``` crates: - name: pallet-revive bump: patch From 3f8c06624ec9f56888c85d1ebd4c6ef143640ff6 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Tue, 16 Dec 2025 16:01:00 +0100 Subject: [PATCH 62/76] rm diff --- substrate/frame/revive/proc-macro/src/lib.rs | 23 +++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/substrate/frame/revive/proc-macro/src/lib.rs b/substrate/frame/revive/proc-macro/src/lib.rs index e5b564f6008c2..f775c87c081ef 100644 --- a/substrate/frame/revive/proc-macro/src/lib.rs +++ b/substrate/frame/revive/proc-macro/src/lib.rs @@ -52,7 +52,7 @@ pub fn define_env(attr: TokenStream, item: TokenStream) -> TokenStream { let msg = r#"Invalid `define_env` attribute macro: expected no attributes: - `#[define_env]`"#; let span = TokenStream2::from(attr).span(); - return syn::Error::new(span, msg).to_compile_error().into(); + return syn::Error::new(span, msg).to_compile_error().into() } let item = syn::parse_macro_input!(item as syn::ItemMod); @@ -147,19 +147,19 @@ impl HostFn { match ident.as_str() { "stable" => { if is_stable { - return Err(err(span, "#[stable] can only be specified once")); + return Err(err(span, "#[stable] can only be specified once")) } is_stable = true; }, "mutating" => { if mutating { - return Err(err(span, "#[mutating] can only be specified once")); + return Err(err(span, "#[mutating] can only be specified once")) } mutating = true; }, "cfg" => { if cfg.is_some() { - return Err(err(span, "#[cfg] can only be specified once")); + return Err(err(span, "#[cfg] can only be specified once")) } cfg = Some(attr); }, @@ -189,7 +189,7 @@ impl HostFn { .fold(0u32, |acc, valid| if valid { acc + 1 } else { acc }); if special_args != 2 { - return Err(err(span, msg)); + return Err(err(span, msg)) } // process return type @@ -211,7 +211,7 @@ impl HostFn { match &result.arguments { syn::PathArguments::AngleBracketed(group) => { if group.args.len() != 2 { - return Err(err(span, &msg)); + return Err(err(span, &msg)) }; let arg2 = group.args.last().ok_or(err(span, &msg))?; @@ -250,7 +250,7 @@ impl HostFn { .to_string()), syn::Type::Tuple(tt) => { if !tt.elems.is_empty() { - return Err(err(arg1.span(), &msg)); + return Err(err(arg1.span(), &msg)) }; Ok("()".to_string()) }, @@ -278,13 +278,10 @@ fn is_valid_special_arg(idx: usize, arg: &FnArg) -> bool { match (idx, arg) { (0, FnArg::Receiver(rec)) => rec.reference.is_some() && rec.mutability.is_some(), (1, FnArg::Typed(pat)) => { - let ident = if let syn::Pat::Ident(ref ident) = *pat.pat { - &ident.ident - } else { - return false; - }; + let ident = + if let syn::Pat::Ident(ref ident) = *pat.pat { &ident.ident } else { return false }; if !(ident == "memory" || ident == "_memory") { - return false; + return false } matches!(*pat.ty, syn::Type::Reference(_)) }, From 8dbd983cd8c51b773e10c987614c7fc1bc152b9c Mon Sep 17 00:00:00 2001 From: pgherveou Date: Tue, 16 Dec 2025 16:05:23 +0100 Subject: [PATCH 63/76] fix --- substrate/frame/revive/proc-macro/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substrate/frame/revive/proc-macro/src/lib.rs b/substrate/frame/revive/proc-macro/src/lib.rs index f775c87c081ef..8a5cc38ad6b34 100644 --- a/substrate/frame/revive/proc-macro/src/lib.rs +++ b/substrate/frame/revive/proc-macro/src/lib.rs @@ -345,7 +345,7 @@ fn expand_env(def: &EnvDef) -> TokenStream2 { #all_syscalls } - /// Returns the list of stable syscalls without unstable ones. + /// Returns the list of syscalls with or without unstable ones. pub fn list_syscalls(include_unstable: bool) -> &'static [&'static [u8]] { if include_unstable { #all_syscalls From 5193cc8145bd8ffac671a7ad8b2e5659ef335844 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Tue, 16 Dec 2025 16:35:52 +0100 Subject: [PATCH 64/76] simplify --- substrate/frame/revive/proc-macro/src/lib.rs | 13 ++----- .../revive/src/evm/api/debug_rpc_types.rs | 34 +++++++++---------- .../src/evm/tracing/execution_tracing.rs | 6 ++-- substrate/frame/revive/src/lib.rs | 22 +++++++++++- substrate/frame/revive/src/tests/sol.rs | 19 +++++------ substrate/frame/revive/src/vm/evm.rs | 7 ++-- 6 files changed, 54 insertions(+), 47 deletions(-) diff --git a/substrate/frame/revive/proc-macro/src/lib.rs b/substrate/frame/revive/proc-macro/src/lib.rs index 8a5cc38ad6b34..7b847ad2bb60c 100644 --- a/substrate/frame/revive/proc-macro/src/lib.rs +++ b/substrate/frame/revive/proc-macro/src/lib.rs @@ -434,22 +434,13 @@ fn expand_functions(def: &EnvDef) -> TokenStream2 { let trace_fmt_str = format!("{}({}) = {{:?}} weight_consumed: {{:?}}", name, params_fmt_str); quote! { - crate::tracing::if_tracing(|tracer| { - if DebugSettings::is_execution_tracing_enabled::() { - tracer.enter_ecall(#name, self) - } - }); + crate::tracing::if_tracing(|tracer| tracer.enter_ecall(#name, self)); // wrap body in closure to make sure the tracing is always executed let result = (|| #body)(); ::log::trace!(target: "runtime::revive::strace", #trace_fmt_str, #( #trace_fmt_args, )* result, self.ext.frame_meter().weight_consumed()); - crate::tracing::if_tracing(|tracer| { - if DebugSettings::is_execution_tracing_enabled::() { - tracer.exit_step(self) - } - }); - + crate::tracing::if_tracing(|tracer| tracer.exit_step(self)); result } }; diff --git a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs index cba531ccbebfd..d1a40a4d1afb0 100644 --- a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs +++ b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs @@ -38,7 +38,7 @@ pub enum TracerType { PrestateTracer(Option), /// A tracer that traces opcodes and syscalls. - StructLogger(Option), + ExecutionTracer(Option), } impl From for TracerType { @@ -53,15 +53,15 @@ impl From for TracerType { } } -impl From for TracerType { - fn from(config: StructLoggerConfig) -> Self { - TracerType::StructLogger(Some(config)) +impl From for TracerType { + fn from(config: ExecutionTracerConfig) -> Self { + TracerType::ExecutionTracer(Some(config)) } } impl Default for TracerType { fn default() -> Self { - TracerType::StructLogger(Some(StructLoggerConfig::default())) + TracerType::ExecutionTracer(Some(ExecutionTracerConfig::default())) } } @@ -97,7 +97,7 @@ impl<'de> Deserialize<'de> for TracerConfig { #[serde(rename_all = "camelCase")] struct TracerConfigInline { #[serde(flatten, default)] - struct_logger_config: StructLoggerConfig, + execution_tracer_config: ExecutionTracerConfig, #[serde(with = "humantime_serde", default)] timeout: Option, } @@ -113,7 +113,7 @@ impl<'de> Deserialize<'de> for TracerConfig { TracerConfigHelper::WithType(cfg) => Ok(TracerConfig { config: cfg.config, timeout: cfg.timeout }), TracerConfigHelper::Inline(cfg) => Ok(TracerConfig { - config: TracerType::StructLogger(Some(cfg.struct_logger_config)), + config: TracerType::ExecutionTracer(Some(cfg.execution_tracer_config)), timeout: cfg.timeout, }), } @@ -168,10 +168,10 @@ where }) } -/// The configuration for the struct logger. +/// The configuration for the execution tracer. #[derive(Clone, Debug, Decode, Serialize, Deserialize, Encode, PartialEq, TypeInfo)] #[serde(default, rename_all = "camelCase")] -pub struct StructLoggerConfig { +pub struct ExecutionTracerConfig { /// Whether to enable memory capture pub enable_memory: bool, @@ -192,7 +192,7 @@ pub struct StructLoggerConfig { pub memory_word_limit: u32, } -impl Default for StructLoggerConfig { +impl Default for ExecutionTracerConfig { fn default() -> Self { Self { enable_memory: false, @@ -215,7 +215,7 @@ impl Default for StructLoggerConfig { /// { "tracer": "callTracer" } /// ``` /// -/// By default if not specified the tracer is a StructLogger, and it's config is passed inline +/// By default if not specified the tracer is an ExecutionTracer, and it's config is passed inline /// /// ```json /// { "tracer": null, "enableMemory": true, "disableStack": false, "disableStorage": false, "enableReturnData": true } @@ -226,7 +226,7 @@ fn test_tracer_config_serialization() { ( r#"{ "enableMemory": true, "disableStack": false, "disableStorage": false, "enableReturnData": true }"#, TracerConfig { - config: TracerType::StructLogger(Some(StructLoggerConfig { + config: TracerType::ExecutionTracer(Some(ExecutionTracerConfig { enable_memory: true, disable_stack: false, disable_storage: false, @@ -240,7 +240,7 @@ fn test_tracer_config_serialization() { ( r#"{ }"#, TracerConfig { - config: TracerType::StructLogger(Some(StructLoggerConfig::default())), + config: TracerType::ExecutionTracer(Some(ExecutionTracerConfig::default())), timeout: None, }, ), @@ -270,13 +270,13 @@ fn test_tracer_config_serialization() { }, ), ( - r#"{"tracer": "structLogger"}"#, - TracerConfig { config: TracerType::StructLogger(None), timeout: None }, + r#"{"tracer": "ExecutionTracer"}"#, + TracerConfig { config: TracerType::ExecutionTracer(None), timeout: None }, ), ( - r#"{"tracer": "structLogger", "tracerConfig": { "enableMemory": true }}"#, + r#"{"tracer": "ExecutionTracer", "tracerConfig": { "enableMemory": true }}"#, TracerConfig { - config: StructLoggerConfig { enable_memory: true, ..Default::default() }.into(), + config: ExecutionTracerConfig { enable_memory: true, ..Default::default() }.into(), timeout: None, }, ), diff --git a/substrate/frame/revive/src/evm/tracing/execution_tracing.rs b/substrate/frame/revive/src/evm/tracing/execution_tracing.rs index d35cfd0a908cf..45c5f7bcecc8c 100644 --- a/substrate/frame/revive/src/evm/tracing/execution_tracing.rs +++ b/substrate/frame/revive/src/evm/tracing/execution_tracing.rs @@ -17,7 +17,7 @@ use crate::{ evm::{ tracing::Tracing, Bytes, ExecutionStep, ExecutionStepKind, ExecutionTrace, - StructLoggerConfig, + ExecutionTracerConfig, }, tracing::{EVMFrameTraceInfo, FrameTraceInfo}, vm::pvm::env::lookup_syscall_index, @@ -35,7 +35,7 @@ use sp_core::{H160, U256}; #[derive(Default, Debug, Clone, PartialEq)] pub struct ExecutionTracer { /// The tracer configuration. - config: StructLoggerConfig, + config: ExecutionTracerConfig, /// The collected trace steps. steps: Vec, @@ -61,7 +61,7 @@ pub struct ExecutionTracer { impl ExecutionTracer { /// Create a new [`ExecutionTracer`] instance. - pub fn new(config: StructLoggerConfig) -> Self { + pub fn new(config: ExecutionTracerConfig) -> Self { Self { config, steps: Vec::new(), diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 44a30ebf0114a..400bef6661fc3 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -2148,7 +2148,7 @@ impl Pallet { TracerType::CallTracer(config) => CallTracer::new(config.unwrap_or_default()).into(), TracerType::PrestateTracer(config) => PrestateTracer::new(config.unwrap_or_default()).into(), - TracerType::StructLogger(config) => + TracerType::ExecutionTracer(config) => ExecutionTracer::new(config.unwrap_or_default()).into(), } } @@ -2892,6 +2892,13 @@ macro_rules! impl_runtime_apis_plus_revive_traits { tracer_type: $crate::evm::TracerType, ) -> Vec<(u32, $crate::evm::Trace)> { use $crate::{sp_runtime::traits::Block, tracing::trace}; + + if matches!(tracer_type, $crate::evm::TracerType::ExecutionTracer(_)) && + !$crate::DebugSettings::is_execution_tracing_enabled::() + { + return Default::default() + } + let mut traces = vec![]; let (header, extrinsics) = block.deconstruct(); <$Executive>::initialize_block(&header); @@ -2915,6 +2922,12 @@ macro_rules! impl_runtime_apis_plus_revive_traits { ) -> Option<$crate::evm::Trace> { use $crate::{sp_runtime::traits::Block, tracing::trace}; + if matches!(tracer_type, $crate::evm::TracerType::ExecutionTracer(_)) && + !$crate::DebugSettings::is_execution_tracing_enabled::() + { + return None + } + let mut tracer = $crate::Pallet::::evm_tracer(tracer_type); let (header, extrinsics) = block.deconstruct(); @@ -2937,6 +2950,13 @@ macro_rules! impl_runtime_apis_plus_revive_traits { tracer_type: $crate::evm::TracerType, ) -> Result<$crate::evm::Trace, $crate::EthTransactError> { use $crate::tracing::trace; + + if matches!(tracer_type, $crate::evm::TracerType::ExecutionTracer(_)) && + !$crate::DebugSettings::is_execution_tracing_enabled::() + { + return Err($crate::EthTransactError::Message("Execution Tracing is disabled".to_string())) + } + let mut tracer = $crate::Pallet::::evm_tracer(tracer_type.clone()); let t = tracer.as_tracing(); diff --git a/substrate/frame/revive/src/tests/sol.rs b/substrate/frame/revive/src/tests/sol.rs index aa20cbfe5947f..5e1e2b00a5151 100644 --- a/substrate/frame/revive/src/tests/sol.rs +++ b/substrate/frame/revive/src/tests/sol.rs @@ -598,10 +598,11 @@ fn eth_substrate_call_tracks_weight_correctly() { } #[test] -fn opcode_tracing_works() { +fn execution_tracing_works_for_evm() { use crate::{ evm::{ - ExecutionStep, ExecutionStepKind, ExecutionTrace, ExecutionTracer, StructLoggerConfig, + ExecutionStep, ExecutionStepKind, ExecutionTrace, ExecutionTracer, + ExecutionTracerConfig, }, tracing::trace, }; @@ -612,7 +613,7 @@ fn opcode_tracing_works() { let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); - let config = StructLoggerConfig { + let config = ExecutionTracerConfig { enable_memory: false, disable_stack: false, disable_storage: true, @@ -720,10 +721,11 @@ fn opcode_tracing_works() { } #[test] -fn syscall_tracing_works() { +fn execution_tracing_works_for_pvm() { use crate::{ evm::{ - ExecutionStep, ExecutionStepKind, ExecutionTrace, ExecutionTracer, StructLoggerConfig, + ExecutionStep, ExecutionStepKind, ExecutionTrace, ExecutionTracer, + ExecutionTracerConfig, }, tracing::trace, vm::pvm::env::lookup_syscall_index, @@ -735,13 +737,10 @@ fn syscall_tracing_works() { let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); - let config = StructLoggerConfig { - enable_memory: false, - disable_stack: true, - disable_storage: true, + let config = ExecutionTracerConfig { enable_return_data: true, limit: Some(5), - memory_word_limit: 16, + ..Default::default() }; let mut tracer = ExecutionTracer::new(config); diff --git a/substrate/frame/revive/src/vm/evm.rs b/substrate/frame/revive/src/vm/evm.rs index 66d710c678445..6eec17d028cc1 100644 --- a/substrate/frame/revive/src/vm/evm.rs +++ b/substrate/frame/revive/src/vm/evm.rs @@ -131,12 +131,9 @@ impl ContractBlob { /// Calls the EVM interpreter with the provided bytecode and inputs. pub fn call(bytecode: Bytecode, ext: &mut E, input: Vec) -> ExecResult { let mut interpreter = Interpreter::new(ExtBytecode::new(bytecode), input, ext); - let is_execution_tracing_enabled = tracing::if_tracing(|tracer| { - tracer.is_execution_tracer() && DebugSettings::is_execution_tracing_enabled::() - }) - .unwrap_or(false); + let tracing_enabled = tracing::if_tracing(|t| t.is_execution_tracer()).unwrap_or(false); - let ControlFlow::Break(halt) = if is_execution_tracing_enabled { + let ControlFlow::Break(halt) = if tracing_enabled { run_plain_with_tracing(&mut interpreter) } else { run_plain(&mut interpreter) From e7a7895a0875caeffea4fd386da07de58d9ea546 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Tue, 16 Dec 2025 16:39:26 +0100 Subject: [PATCH 65/76] fix --- substrate/frame/revive/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 400bef6661fc3..6930c65ae910d 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -2954,7 +2954,7 @@ macro_rules! impl_runtime_apis_plus_revive_traits { if matches!(tracer_type, $crate::evm::TracerType::ExecutionTracer(_)) && !$crate::DebugSettings::is_execution_tracing_enabled::() { - return Err($crate::EthTransactError::Message("Execution Tracing is disabled".to_string())) + return Err($crate::EthTransactError::Message("Execution Tracing is disabled".into())) } let mut tracer = $crate::Pallet::::evm_tracer(tracer_type.clone()); From e8c01073a3a0ebf06e8a0ca8b8f2007179680149 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Tue, 16 Dec 2025 16:46:43 +0100 Subject: [PATCH 66/76] fix --- substrate/frame/revive/src/evm/tracing/execution_tracing.rs | 2 +- substrate/frame/revive/src/vm/evm.rs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/substrate/frame/revive/src/evm/tracing/execution_tracing.rs b/substrate/frame/revive/src/evm/tracing/execution_tracing.rs index 45c5f7bcecc8c..01ac290b392f9 100644 --- a/substrate/frame/revive/src/evm/tracing/execution_tracing.rs +++ b/substrate/frame/revive/src/evm/tracing/execution_tracing.rs @@ -81,7 +81,7 @@ impl ExecutionTracer { } /// Record an error in the current step. - pub fn record_error(&mut self, error: String) { + fn record_error(&mut self, error: String) { if let Some(last_step) = self.steps.last_mut() { last_step.error = Some(error); } diff --git a/substrate/frame/revive/src/vm/evm.rs b/substrate/frame/revive/src/vm/evm.rs index 6eec17d028cc1..d09fba49a9dc3 100644 --- a/substrate/frame/revive/src/vm/evm.rs +++ b/substrate/frame/revive/src/vm/evm.rs @@ -39,7 +39,6 @@ use ext_bytecode::ExtBytecode; mod memory; mod stack; - mod util; /// Hard-coded value returned by the EVM `DIFFICULTY` opcode. From 2730f591ecc02d638316ae169b1446101e95db0a Mon Sep 17 00:00:00 2001 From: pgherveou Date: Wed, 17 Dec 2025 12:46:09 +0100 Subject: [PATCH 67/76] increase limit in dev --- substrate/frame/revive/dev-node/node/src/command.rs | 4 ++++ substrate/frame/revive/proc-macro/src/lib.rs | 4 ++-- substrate/frame/revive/rpc/src/cli.rs | 6 +++++- substrate/frame/revive/rpc/src/client.rs | 4 ++++ .../frame/revive/src/evm/api/debug_rpc_types.rs | 12 ++++++++---- .../revive/src/evm/tracing/execution_tracing.rs | 4 ++-- 6 files changed, 25 insertions(+), 9 deletions(-) diff --git a/substrate/frame/revive/dev-node/node/src/command.rs b/substrate/frame/revive/dev-node/node/src/command.rs index b38869a38eee8..6767f0fed55cd 100644 --- a/substrate/frame/revive/dev-node/node/src/command.rs +++ b/substrate/frame/revive/dev-node/node/src/command.rs @@ -125,6 +125,10 @@ pub fn run_with_args(args: Vec) -> sc_cli::Result<()> { // Enforce dev cli.run.shared_params.dev = true; + // Increase max_response_size for large trace responses + cli.run.rpc_params.rpc_max_response_size = + cli.run.rpc_params.rpc_max_response_size.max(50); + // Pass Default logging settings if none are specified if std::env::var("RUST_LOG").is_err() && cli.run.shared_params.log.is_empty() { cli.run.shared_params.log = "error,sc_rpc_server=info,runtime::revive=debug" diff --git a/substrate/frame/revive/proc-macro/src/lib.rs b/substrate/frame/revive/proc-macro/src/lib.rs index 7b847ad2bb60c..02ae14fcaf2cd 100644 --- a/substrate/frame/revive/proc-macro/src/lib.rs +++ b/substrate/frame/revive/proc-macro/src/lib.rs @@ -355,7 +355,7 @@ fn expand_env(def: &EnvDef) -> TokenStream2 { } /// Return the index of a syscall in the `all_syscalls()` list. - pub fn lookup_syscall_index(name: &'static str) -> Option { + pub fn lookup_syscall_index(name: &'static str) -> Option { #lookup_syscall } @@ -573,7 +573,7 @@ fn expand_func_lookup(def: &EnvDef) -> TokenStream2 { let arms = def.host_funcs.iter().enumerate().map(|(idx, f)| { let name_str = &f.name; quote! { - #name_str => Some(#idx as u32) + #name_str => Some(#idx as u8) } }); diff --git a/substrate/frame/revive/rpc/src/cli.rs b/substrate/frame/revive/rpc/src/cli.rs index 4607b6f14e1e2..c84de750da116 100644 --- a/substrate/frame/revive/rpc/src/cli.rs +++ b/substrate/frame/revive/rpc/src/cli.rs @@ -105,10 +105,12 @@ fn build_client( earliest_receipt_block: Option, node_rpc_url: &str, database_url: &str, + max_request_size: u32, + max_response_size: u32, abort_signal: Signals, ) -> anyhow::Result { let fut = async { - let (api, rpc_client, rpc) = connect(node_rpc_url).await?; + let (api, rpc_client, rpc) = connect(node_rpc_url, max_request_size, max_response_size).await?; let block_provider = SubxtBlockInfoProvider::new( api.clone(), rpc.clone()).await?; let (pool, keep_latest_n_blocks) = if database_url == IN_MEMORY_DB { @@ -206,6 +208,8 @@ pub fn run(cmd: CliCommand) -> anyhow::Result<()> { earliest_receipt_block, &node_rpc_url, &database_url, + rpc_config.max_request_size * 1024 * 1024, + rpc_config.max_response_size * 1024 * 1024, tokio_runtime.block_on(async { Signals::capture() })?, )?; diff --git a/substrate/frame/revive/rpc/src/client.rs b/substrate/frame/revive/rpc/src/client.rs index 908adebd3e2a5..e1c9aae495fa6 100644 --- a/substrate/frame/revive/rpc/src/client.rs +++ b/substrate/frame/revive/rpc/src/client.rs @@ -209,11 +209,15 @@ async fn get_automine(rpc_client: &RpcClient) -> bool { /// clients. pub async fn connect( node_rpc_url: &str, + max_request_size: u32, + max_response_size: u32, ) -> Result<(OnlineClient, RpcClient, LegacyRpcMethods), ClientError> { log::info!(target: LOG_TARGET, "🌐 Connecting to node at: {node_rpc_url} ..."); let rpc_client = ReconnectingRpcClient::builder() .retry_policy(ExponentialBackoff::from_millis(100).max_delay(Duration::from_secs(10))) + .max_request_size(max_request_size) + .max_response_size(max_response_size) .build(node_rpc_url.to_string()) .await?; let rpc_client = RpcClient::new(rpc_client); diff --git a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs index d1a40a4d1afb0..192d6491d92f0 100644 --- a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs +++ b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs @@ -471,6 +471,7 @@ where #[serde(rename_all = "camelCase")] pub struct ExecutionTrace { /// Total gas used by the transaction. + #[codec(compact)] pub gas: u64, /// Whether the transaction failed. pub failed: bool, @@ -485,11 +486,13 @@ pub struct ExecutionTrace { #[serde(rename_all = "camelCase")] pub struct ExecutionStep { /// Remaining gas before executing this step. + #[codec(compact)] pub gas: u64, /// Cost of executing this step. + #[codec(compact)] pub gas_cost: u64, /// Current call depth. - pub depth: u32, + pub depth: u16, /// Return data from last frame output. #[serde(skip_serializing_if = "Bytes::is_empty")] pub return_data: Bytes, @@ -508,7 +511,8 @@ pub enum ExecutionStepKind { /// An EVM opcode execution. EVMOpcode { /// The program counter. - pc: u64, + #[codec(compact)] + pc: u32, /// The opcode being executed. #[serde(serialize_with = "serialize_opcode", deserialize_with = "deserialize_opcode")] op: u8, @@ -532,7 +536,7 @@ pub enum ExecutionStepKind { PVMSyscall { /// The executed syscall. #[serde(serialize_with = "serialize_syscall_op")] - op: u32, + op: u8, }, } @@ -724,7 +728,7 @@ where } /// Serialize a syscall index to its name -fn serialize_syscall_op(idx: &u32, serializer: S) -> Result +fn serialize_syscall_op(idx: &u8, serializer: S) -> Result where S: serde::Serializer, { diff --git a/substrate/frame/revive/src/evm/tracing/execution_tracing.rs b/substrate/frame/revive/src/evm/tracing/execution_tracing.rs index 01ac290b392f9..06b82b5263cef 100644 --- a/substrate/frame/revive/src/evm/tracing/execution_tracing.rs +++ b/substrate/frame/revive/src/evm/tracing/execution_tracing.rs @@ -41,7 +41,7 @@ pub struct ExecutionTracer { steps: Vec, /// Current call depth. - depth: u32, + depth: u16, /// Number of steps captured (for limiting). step_count: u64, @@ -123,7 +123,7 @@ impl Tracing for ExecutionTracer { return_data, error: None, kind: ExecutionStepKind::EVMOpcode { - pc, + pc: pc as u32, op: opcode, stack: stack_data, memory: memory_data, From 2a1905821c010520d1de790552b844f0270c2b5a Mon Sep 17 00:00:00 2001 From: pgherveou Date: Tue, 6 Jan 2026 10:59:16 +0100 Subject: [PATCH 68/76] fix tests --- substrate/frame/revive/rpc/src/tests.rs | 7 +- .../revive/src/evm/api/debug_rpc_types.rs | 30 ++++++--- .../frame/revive/src/evm/block_storage.rs | 10 +-- .../src/evm/tracing/execution_tracing.rs | 34 ++++++++-- substrate/frame/revive/src/tests/sol.rs | 64 ++++++++++++------- substrate/frame/revive/src/tracing.rs | 14 +++- .../frame/revive/src/vm/evm/interpreter.rs | 7 +- substrate/frame/revive/src/vm/pvm.rs | 4 ++ 8 files changed, 124 insertions(+), 46 deletions(-) diff --git a/substrate/frame/revive/rpc/src/tests.rs b/substrate/frame/revive/rpc/src/tests.rs index 269dffb8c7572..8201b434d59f7 100644 --- a/substrate/frame/revive/rpc/src/tests.rs +++ b/substrate/frame/revive/rpc/src/tests.rs @@ -486,7 +486,7 @@ async fn get_evm_block_from_storage( async fn test_evm_blocks_should_match(client: Arc) -> anyhow::Result<()> { let (node_client, node_rpc_client, _) = - client::connect(SharedResources::node_rpc_url()).await.unwrap(); + client::connect(SharedResources::node_rpc_url(), 1024, 1024).await.unwrap(); // Deploy a contract to have some interesting blocks let (bytes, _) = pallet_revive_fixtures::compile_module("dummy")?; @@ -705,7 +705,8 @@ async fn test_mixed_evm_substrate_transactions(client: Arc) -> anyhow: // Prepare substrate transactions (simple remarks) log::trace!(target: LOG_TARGET, "Creating {num_substrate_txs} substrate remark transactions"); let alice_signer = subxt_signer::sr25519::dev::alice(); - let (node_client, _, _) = client::connect(SharedResources::node_rpc_url()).await.unwrap(); + let (node_client, _, _) = + client::connect(SharedResources::node_rpc_url(), 1024, 1024).await.unwrap(); let substrate_txs = prepare_substrate_transactions(&node_client, &alice_signer, num_substrate_txs).await?; @@ -736,7 +737,7 @@ async fn test_mixed_evm_substrate_transactions(client: Arc) -> anyhow: async fn test_runtime_pallets_address_upload_code(client: Arc) -> anyhow::Result<()> { let (node_client, node_rpc_client, _) = - client::connect(SharedResources::node_rpc_url()).await?; + client::connect(SharedResources::node_rpc_url(), 1024, 1024).await?; let (bytecode, _) = pallet_revive_fixtures::compile_module("dummy")?; let signer = Account::default(); diff --git a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs index 192d6491d92f0..f711fdfd6ce03 100644 --- a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs +++ b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::evm::Bytes; +use crate::{evm::Bytes, Weight}; use alloc::{collections::BTreeMap, string::String, vec::Vec}; use codec::{Decode, Encode}; use derive_more::From; @@ -224,7 +224,8 @@ impl Default for ExecutionTracerConfig { fn test_tracer_config_serialization() { let tracers = vec![ ( - r#"{ "enableMemory": true, "disableStack": false, "disableStorage": false, "enableReturnData": true }"#, + r#"{ "enableMemory": true, "disableStack": false, "disableStorage": false, + "enableReturnData": true }"#, TracerConfig { config: TracerType::ExecutionTracer(Some(ExecutionTracerConfig { enable_memory: true, @@ -263,18 +264,26 @@ fn test_tracer_config_serialization() { }, ), ( - r#"{"tracer": "callTracer", "tracerConfig": { "onlyTopCall": true }, "timeout": "10ms"}"#, + r#"{"tracer": "callTracer", "tracerConfig": { "onlyTopCall": true }, "timeout": + "10ms"}"#, TracerConfig { config: CallTracerConfig { with_logs: true, only_top_call: true }.into(), timeout: Some(core::time::Duration::from_millis(10)), }, ), ( - r#"{"tracer": "ExecutionTracer"}"#, + r#"{"tracer": "executionTracer"}"#, TracerConfig { config: TracerType::ExecutionTracer(None), timeout: None }, ), ( - r#"{"tracer": "ExecutionTracer", "tracerConfig": { "enableMemory": true }}"#, + r#"{"tracer": "executionTracer", "tracerConfig": { "enableMemory": true }}"#, + TracerConfig { + config: ExecutionTracerConfig { enable_memory: true, ..Default::default() }.into(), + timeout: None, + }, + ), + ( + r#"{ "enableMemory": true }"#, TracerConfig { config: ExecutionTracerConfig { enable_memory: true, ..Default::default() }.into(), timeout: None, @@ -285,7 +294,7 @@ fn test_tracer_config_serialization() { for (json_data, expected) in tracers { let result: TracerConfig = serde_json::from_str(json_data).expect("Deserialization should succeed"); - assert_eq!(result, expected); + assert_eq!(result, expected, "invalid serialization for {json_data}"); } } @@ -471,8 +480,11 @@ where #[serde(rename_all = "camelCase")] pub struct ExecutionTrace { /// Total gas used by the transaction. - #[codec(compact)] pub gas: u64, + /// The weight consumed by the transaction meter. + pub weight_consumed: Weight, + /// The base call weight of the transaction. + pub base_call_weight: Weight, /// Whether the transaction failed. pub failed: bool, /// The return value of the transaction. @@ -488,9 +500,11 @@ pub struct ExecutionStep { /// Remaining gas before executing this step. #[codec(compact)] pub gas: u64, - /// Cost of executing this step. + /// Gas Cost of executing this step. #[codec(compact)] pub gas_cost: u64, + /// Weight cost of executing this step. + pub weight_cost: Weight, /// Current call depth. pub depth: u16, /// Return data from last frame output. diff --git a/substrate/frame/revive/src/evm/block_storage.rs b/substrate/frame/revive/src/evm/block_storage.rs index cafc76a8e413c..6d2f453e3f37f 100644 --- a/substrate/frame/revive/src/evm/block_storage.rs +++ b/substrate/frame/revive/src/evm/block_storage.rs @@ -70,7 +70,7 @@ impl EthereumCallResult { pub(crate) fn new( signer: AccountIdOf, mut output: ContractResult>, - base_call_weight: Weight, + mut base_call_weight: Weight, encoded_len: u32, info: &DispatchInfo, effective_gas_price: U256, @@ -85,11 +85,13 @@ impl EthereumCallResult { // Refund pre-charged revert event weight if the call succeeds. if output.result.is_ok() { - output - .weight_consumed - .saturating_reduce(T::WeightInfo::deposit_eth_extrinsic_revert_event()) + base_call_weight.saturating_reduce(T::WeightInfo::deposit_eth_extrinsic_revert_event()) } + crate::if_tracing(|tracer| { + tracer.dispatch_result(base_call_weight, output.weight_consumed); + }); + let result = dispatch_result(output.result, output.weight_consumed, base_call_weight); let native_fee = T::FeeInfo::compute_actual_fee(encoded_len, &info, &result); let result = T::FeeInfo::ensure_not_overdrawn(native_fee, result); diff --git a/substrate/frame/revive/src/evm/tracing/execution_tracing.rs b/substrate/frame/revive/src/evm/tracing/execution_tracing.rs index 06b82b5263cef..75a810295597d 100644 --- a/substrate/frame/revive/src/evm/tracing/execution_tracing.rs +++ b/substrate/frame/revive/src/evm/tracing/execution_tracing.rs @@ -21,7 +21,7 @@ use crate::{ }, tracing::{EVMFrameTraceInfo, FrameTraceInfo}, vm::pvm::env::lookup_syscall_index, - DispatchError, ExecReturnValue, Key, + DispatchError, ExecReturnValue, Key, Weight, }; use alloc::{ collections::BTreeMap, @@ -49,6 +49,12 @@ pub struct ExecutionTracer { /// Total gas used by the transaction. total_gas_used: u64, + /// The base call weight of the transaction. + base_call_weight: Weight, + + /// The Weight consumed by the transaction meter. + weight_consumed: Weight, + /// Whether the transaction failed. failed: bool, @@ -68,6 +74,8 @@ impl ExecutionTracer { depth: 0, step_count: 0, total_gas_used: 0, + base_call_weight: Default::default(), + weight_consumed: Default::default(), failed: false, return_value: Bytes::default(), storages_per_call: alloc::vec![Default::default()], @@ -76,8 +84,16 @@ impl ExecutionTracer { /// Collect the traces and return them. pub fn collect_trace(self) -> ExecutionTrace { - let Self { steps: struct_logs, return_value, total_gas_used: gas, failed, .. } = self; - ExecutionTrace { gas, failed, return_value, struct_logs } + let Self { + steps: struct_logs, + weight_consumed, + base_call_weight, + return_value, + total_gas_used: gas, + failed, + .. + } = self; + ExecutionTrace { gas, weight_consumed, base_call_weight, failed, return_value, struct_logs } } /// Record an error in the current step. @@ -93,6 +109,11 @@ impl Tracing for ExecutionTracer { true } + fn dispatch_result(&mut self, base_call_weight: Weight, weight_consumed: Weight) { + self.base_call_weight = base_call_weight; + self.weight_consumed = weight_consumed; + } + fn enter_opcode(&mut self, pc: u64, opcode: u8, trace_info: &dyn EVMFrameTraceInfo) { if self.config.limit.map(|l| self.step_count >= l).unwrap_or(false) { return; @@ -118,7 +139,8 @@ impl Tracing for ExecutionTracer { let step = ExecutionStep { gas: trace_info.gas_left(), - gas_cost: 0u64, // Will be set in exit_step + gas_cost: Default::default(), + weight_cost: trace_info.weight_consumed(), depth: self.depth, return_data, error: None, @@ -149,7 +171,8 @@ impl Tracing for ExecutionTracer { let step = ExecutionStep { gas: trace_info.gas_left(), - gas_cost: 0u64, // Will be set in exit_step + gas_cost: Default::default(), + weight_cost: trace_info.weight_consumed(), depth: self.depth, return_data, error: None, @@ -165,6 +188,7 @@ impl Tracing for ExecutionTracer { fn exit_step(&mut self, trace_info: &dyn FrameTraceInfo) { if let Some(step) = self.steps.last_mut() { step.gas_cost = step.gas.saturating_sub(trace_info.gas_left()); + step.weight_cost = trace_info.weight_consumed().saturating_sub(step.weight_cost); } } diff --git a/substrate/frame/revive/src/tests/sol.rs b/substrate/frame/revive/src/tests/sol.rs index 5e1e2b00a5151..808cc1e056063 100644 --- a/substrate/frame/revive/src/tests/sol.rs +++ b/substrate/frame/revive/src/tests/sol.rs @@ -631,20 +631,24 @@ fn execution_tracing_works_for_evm() { let mut actual_trace = tracer.collect_trace(); actual_trace.struct_logs.iter_mut().for_each(|step| { - step.gas = 0u64; - step.gas_cost = 0u64; + step.gas = Default::default(); + step.gas_cost = Default::default(); + step.weight_cost = Default::default(); }); let expected_trace = ExecutionTrace { gas: actual_trace.gas, + base_call_weight: Default::default(), + weight_consumed: Default::default(), failed: false, return_value: crate::evm::Bytes(U256::from(2).to_big_endian().to_vec()), struct_logs: vec![ ExecutionStep { - gas: 0u64, - gas_cost: 0u64, depth: 1, return_data: crate::evm::Bytes::default(), + gas: Default::default(), + gas_cost: Default::default(), + weight_cost: Default::default(), error: None, kind: ExecutionStepKind::EVMOpcode { pc: 0, @@ -655,11 +659,12 @@ fn execution_tracing_works_for_evm() { }, }, ExecutionStep { - gas: 0u64, - gas_cost: 0u64, depth: 1, return_data: crate::evm::Bytes::default(), error: None, + gas: Default::default(), + gas_cost: Default::default(), + weight_cost: Default::default(), kind: ExecutionStepKind::EVMOpcode { pc: 2, op: PUSH1, @@ -669,11 +674,12 @@ fn execution_tracing_works_for_evm() { }, }, ExecutionStep { - gas: 0u64, - gas_cost: 0u64, depth: 1, return_data: crate::evm::Bytes::default(), error: None, + gas: Default::default(), + gas_cost: Default::default(), + weight_cost: Default::default(), kind: ExecutionStepKind::EVMOpcode { pc: 4, op: MSTORE, @@ -686,11 +692,12 @@ fn execution_tracing_works_for_evm() { }, }, ExecutionStep { - gas: 0u64, - gas_cost: 0u64, depth: 1, return_data: crate::evm::Bytes::default(), error: None, + gas: Default::default(), + gas_cost: Default::default(), + weight_cost: Default::default(), kind: ExecutionStepKind::EVMOpcode { pc: 5, op: CALLVALUE, @@ -700,11 +707,12 @@ fn execution_tracing_works_for_evm() { }, }, ExecutionStep { - gas: 0u64, - gas_cost: 0u64, depth: 1, return_data: crate::evm::Bytes::default(), error: None, + gas: Default::default(), + gas_cost: Default::default(), + weight_cost: Default::default(), kind: ExecutionStepKind::EVMOpcode { pc: 6, op: DUP1, @@ -752,61 +760,69 @@ fn execution_tracing_works_for_pvm() { let mut actual_trace = tracer.collect_trace(); actual_trace.struct_logs.iter_mut().for_each(|step| { - step.gas = 0u64; - step.gas_cost = 0u64; + step.gas = Default::default(); + step.gas_cost = Default::default(); + step.weight_cost = Default::default(); }); let expected_trace = ExecutionTrace { gas: actual_trace.gas, + base_call_weight: Default::default(), + weight_consumed: Default::default(), failed: false, return_value: crate::evm::Bytes(U256::from(2).to_big_endian().to_vec()), struct_logs: vec![ ExecutionStep { - gas: 0u64, - gas_cost: 0u64, depth: 1, return_data: crate::evm::Bytes::default(), error: None, + gas: Default::default(), + gas_cost: Default::default(), + weight_cost: Default::default(), kind: ExecutionStepKind::PVMSyscall { op: lookup_syscall_index("call_data_size").unwrap_or_default(), }, }, ExecutionStep { - gas: 0u64, - gas_cost: 0u64, depth: 1, return_data: crate::evm::Bytes::default(), error: None, + gas: Default::default(), + gas_cost: Default::default(), + weight_cost: Default::default(), kind: ExecutionStepKind::PVMSyscall { op: lookup_syscall_index("call_data_load").unwrap_or_default(), }, }, ExecutionStep { - gas: 0u64, - gas_cost: 0u64, depth: 1, return_data: crate::evm::Bytes::default(), error: None, + gas: Default::default(), + gas_cost: Default::default(), + weight_cost: Default::default(), kind: ExecutionStepKind::PVMSyscall { op: lookup_syscall_index("value_transferred").unwrap_or_default(), }, }, ExecutionStep { - gas: 0u64, - gas_cost: 0u64, depth: 1, return_data: crate::evm::Bytes::default(), error: None, + gas: Default::default(), + gas_cost: Default::default(), + weight_cost: Default::default(), kind: ExecutionStepKind::PVMSyscall { op: lookup_syscall_index("call_data_load").unwrap_or_default(), }, }, ExecutionStep { - gas: 0u64, - gas_cost: 0u64, depth: 1, return_data: crate::evm::Bytes::default(), error: None, + gas: Default::default(), + gas_cost: Default::default(), + weight_cost: Default::default(), kind: ExecutionStepKind::PVMSyscall { op: lookup_syscall_index("seal_return").unwrap_or_default(), }, diff --git a/substrate/frame/revive/src/tracing.rs b/substrate/frame/revive/src/tracing.rs index 92e78028a4f72..aed836bcace20 100644 --- a/substrate/frame/revive/src/tracing.rs +++ b/substrate/frame/revive/src/tracing.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{evm::Bytes, primitives::ExecReturnValue, Code, DispatchError, Key}; +use crate::{evm::Bytes, primitives::ExecReturnValue, Code, DispatchError, Key, Weight}; use alloc::vec::Vec; use environmental::environmental; use sp_core::{H160, H256, U256}; @@ -45,6 +45,9 @@ pub trait FrameTraceInfo { /// Get the amount of gas remaining in the current frame. fn gas_left(&self) -> u64; + /// Returns how much weight was spent + fn weight_consumed(&self) -> Weight; + /// Get the output from the last frame. fn last_frame_output(&self) -> Bytes; } @@ -141,4 +144,13 @@ pub trait Tracing { /// # Parameters /// - `trace_info`: Information about the current execution frame. fn exit_step(&mut self, _trace_info: &dyn FrameTraceInfo) {} + + /// Called once the transaction completes to report the gas consumed by the meter. + /// + /// # Parameters + /// - `base_call_weight`: Extrinsic base weight that is added on top of `weight_consumed` when + /// charging. + /// - `weight_consumed`: Weight used by the transaction logic, excluding the extrinsic base + /// weight. + fn dispatch_result(&mut self, _base_call_weight: Weight, _weight_consumed: Weight) {} } diff --git a/substrate/frame/revive/src/vm/evm/interpreter.rs b/substrate/frame/revive/src/vm/evm/interpreter.rs index 99c2d00521528..97fd16a87b1d3 100644 --- a/substrate/frame/revive/src/vm/evm/interpreter.rs +++ b/substrate/frame/revive/src/vm/evm/interpreter.rs @@ -23,7 +23,7 @@ use crate::{ evm::{memory::Memory, stack::Stack}, ExecResult, Ext, }, - Config, DispatchError, Error, + Config, DispatchError, Error, Weight, }; use alloc::vec::Vec; use pallet_revive_uapi::ReturnFlags; @@ -82,6 +82,11 @@ impl FrameTraceInfo for Interpreter<'_, E> { meter.eth_gas_left().unwrap_or_default().try_into().unwrap_or_default() } + fn weight_consumed(&self) -> Weight { + let meter = self.ext.frame_meter(); + meter.weight_consumed() + } + fn last_frame_output(&self) -> crate::evm::Bytes { crate::evm::Bytes(self.ext.last_frame_output().data.clone()) } diff --git a/substrate/frame/revive/src/vm/pvm.rs b/substrate/frame/revive/src/vm/pvm.rs index a684fc7edc45c..ba7cf44aa2d35 100644 --- a/substrate/frame/revive/src/vm/pvm.rs +++ b/substrate/frame/revive/src/vm/pvm.rs @@ -819,6 +819,10 @@ impl<'a, E: Ext, M: ?Sized + Memory> FrameTraceInfo for Runtime<'a, E, M> let meter = self.ext.frame_meter(); meter.eth_gas_left().unwrap_or_default().try_into().unwrap_or_default() } + fn weight_consumed(&self) -> Weight { + let meter = self.ext.frame_meter(); + meter.weight_consumed() + } fn last_frame_output(&self) -> crate::evm::Bytes { crate::evm::Bytes(self.ext.last_frame_output().data.clone()) From 02e8e79a0706268daaf53691c36aaca9d1b3c8c0 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Thu, 15 Jan 2026 09:15:55 +0100 Subject: [PATCH 69/76] update pr doc --- prdoc/pr_9722.prdoc | 45 ++++++++++++++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/prdoc/pr_9722.prdoc b/prdoc/pr_9722.prdoc index 09e607c686b6b..d579d2e72e5f1 100644 --- a/prdoc/pr_9722.prdoc +++ b/prdoc/pr_9722.prdoc @@ -24,25 +24,38 @@ doc: ❯ cast rpc debug_traceTransaction "", { "tracer": { "enableMemory": true } } | jq ``` + The response includes additional fields compared to the original Geth debug RPC endpoints: + + For the trace: + - `weight_consumed`: same as gas but expressed in Weight + - `base_call_weight`: the base cost of the transaction + + For each step: + - `weight_cost`: same as gas_cost but expressed in Weight + For an EVM execution, the output will look like this ```json { - "gas": 16208049, + "gas": 4208049, + "weight_consumed": { "ref_time": 126241470000, "proof_size": 4208 }, + "base_call_weight": { "ref_time": 9000000000, "proof_size": 3000 }, "failed": false, "returnValue": "0x", "structLogs": [ { - "gas": 14609533, - "gasCost": 1, + "gas": 4109533, + "gasCost": 3, + "weight_cost": { "ref_time": 90000, "proof_size": 0 }, "depth": 1, "pc": 0, "op": "PUSH1", "stack": [] }, { - "gas": 14609532, - "gasCost": 1, + "gas": 4109530, + "gasCost": 3, + "weight_cost": { "ref_time": 90000, "proof_size": 0 }, "depth": 1, "pc": 2, "op": "PUSH1", @@ -51,8 +64,9 @@ doc: ] }, { - "gas": 14609531, - "gasCost": 1, + "gas": 4109527, + "gasCost": 3, + "weight_cost": { "ref_time": 90000, "proof_size": 0 }, "depth": 1, "pc": 4, "op": "MSTORE", @@ -68,25 +82,30 @@ doc: ```json { - "gas": 279456, + "gas": 97945, + "weight_consumed": { "ref_time": 2938350000, "proof_size": 97 }, + "base_call_weight": { "ref_time": 9000000000, "proof_size": 3000 }, "failed": false, "returnValue": "0x", "structLogs": [ { - "gas": 27108, - "gasCost": 7, + "gas": 97108, + "gasCost": 131, + "weight_cost": { "ref_time": 3930000, "proof_size": 0 }, "depth": 1, "op": "call_data_size" }, { - "gas": 26977, - "gasCost": 7, + "gas": 96977, + "gasCost": 20, + "weight_cost": { "ref_time": 600000, "proof_size": 0 }, "depth": 1, "op": "call_data_load" }, { - "gas": 26957, + "gas": 96957, "gasCost": 7, + "weight_cost": { "ref_time": 210000, "proof_size": 0 }, "depth": 1, "op": "value_transferred" }] From 4fe1445463ce3b3ccdac2de03defc94512e9990b Mon Sep 17 00:00:00 2001 From: pgherveou Date: Fri, 16 Jan 2026 13:02:03 +0100 Subject: [PATCH 70/76] add syscall details --- substrate/frame/revive/proc-macro/src/lib.rs | 18 ++++++++++--- .../revive/src/evm/api/debug_rpc_types.rs | 26 +++++++++++++++++++ .../src/evm/tracing/execution_tracing.rs | 15 +++++++++-- substrate/frame/revive/src/tests/sol.rs | 16 ++++++++++++ substrate/frame/revive/src/tracing.rs | 12 +++++++-- substrate/frame/revive/src/vm/evm.rs | 2 +- substrate/frame/revive/uapi/src/lib.rs | 2 +- 7 files changed, 82 insertions(+), 9 deletions(-) diff --git a/substrate/frame/revive/proc-macro/src/lib.rs b/substrate/frame/revive/proc-macro/src/lib.rs index 294addaa48d69..1eab47cf53b72 100644 --- a/substrate/frame/revive/proc-macro/src/lib.rs +++ b/substrate/frame/revive/proc-macro/src/lib.rs @@ -88,6 +88,15 @@ impl HostFnReturn { Self::ReturnCode => parse_quote! { -> ReturnErrorCode }, } } + + fn trace_return_value(&self) -> TokenStream2 { + match self { + Self::Unit => quote! { None }, + Self::U32 => quote! { result.as_ref().ok().map(|r| *r as u64) }, + Self::ReturnCode => quote! { result.as_ref().ok().copied().map(u64::from) }, + Self::U64 => quote! { result.as_ref().ok().copied() }, + } + } } impl EnvDef { @@ -384,10 +393,10 @@ fn expand_functions(def: &EnvDef) -> TokenStream2 { let syscall_symbol = Literal::byte_string(name.as_bytes()); let body = &f.item.block; let map_output = f.returns.map_output(); + let trace_return = f.returns.trace_return_value(); let output = &f.item.sig.output; // wrapped host function body call with host function traces - // see https://github.com/paritytech/polkadot-sdk/tree/master/substrate/frame/contracts#host-function-tracing let wrapped_body_with_trace = { let trace_fmt_args = params.clone().filter_map(|arg| match arg { syn::FnArg::Receiver(_) => None, @@ -403,15 +412,18 @@ fn expand_functions(def: &EnvDef) -> TokenStream2 { .collect::>() .join(", "); let trace_fmt_str = format!("{}({}) = {{:?}} weight_consumed: {{:?}}", name, params_fmt_str); + let trace_args_for_tracer: Vec<_> = trace_fmt_args.clone().collect(); quote! { - crate::tracing::if_tracing(|tracer| tracer.enter_ecall(#name, self)); + crate::tracing::if_tracing(|tracer| { + tracer.enter_ecall(#name, &[#( #trace_args_for_tracer as u64 ),*], self) + }); // wrap body in closure to make sure the tracing is always executed let result = (|| #body)(); ::log::trace!(target: "runtime::revive::strace", #trace_fmt_str, #( #trace_fmt_args, )* result, self.ext.frame_meter().weight_consumed()); - crate::tracing::if_tracing(|tracer| tracer.exit_step(self)); + crate::tracing::if_tracing(|tracer| tracer.exit_step(self, #trace_return)); result } }; diff --git a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs index efc4366c074fc..3ba6b22edf1fc 100644 --- a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs +++ b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs @@ -184,6 +184,9 @@ pub struct ExecutionTracerConfig { /// Whether to enable return data capture pub enable_return_data: bool, + /// Whether to enable syscall details capture, including arguments and return value (PVM only) + pub enable_syscall_details: bool, + /// Limit number of steps captured #[serde(skip_serializing_if = "Option::is_none", deserialize_with = "zero_to_none")] pub limit: Option, @@ -199,6 +202,7 @@ impl Default for ExecutionTracerConfig { disable_stack: false, disable_storage: false, enable_return_data: false, + enable_syscall_details: false, limit: None, memory_word_limit: 16, } @@ -232,6 +236,7 @@ fn test_tracer_config_serialization() { disable_stack: false, disable_storage: false, enable_return_data: true, + enable_syscall_details: false, limit: None, memory_word_limit: 16, })), @@ -551,6 +556,14 @@ pub enum ExecutionStepKind { /// The executed syscall. #[serde(serialize_with = "serialize_syscall_op")] op: u8, + /// The syscall arguments (register values a0-a5). + /// Only populated when `enable_syscall_details` is true in ExecutionTracerConfig. + #[serde(skip_serializing_if = "Vec::is_empty", serialize_with = "serialize_syscall_args")] + args: Vec, + /// The syscall return value. + /// Only populated when `enable_syscall_details` is true in ExecutionTracerConfig. + #[serde(skip_serializing_if = "Option::is_none")] + returned: Option, }, } @@ -754,6 +767,19 @@ where serializer.serialize_str(name) } +/// Serialize syscall arguments as hex values +fn serialize_syscall_args(args: &Vec, serializer: S) -> Result +where + S: serde::Serializer, +{ + use serde::ser::SerializeSeq; + let mut seq = serializer.serialize_seq(Some(args.len()))?; + for arg in args { + seq.serialize_element(&alloc::format!("0x{:x}", arg))?; + } + seq.end() +} + /// Deserialize opcode from string using reverse lookup table fn deserialize_opcode<'de, D>(deserializer: D) -> Result where diff --git a/substrate/frame/revive/src/evm/tracing/execution_tracing.rs b/substrate/frame/revive/src/evm/tracing/execution_tracing.rs index 75a810295597d..1173ea15c0acb 100644 --- a/substrate/frame/revive/src/evm/tracing/execution_tracing.rs +++ b/substrate/frame/revive/src/evm/tracing/execution_tracing.rs @@ -157,7 +157,7 @@ impl Tracing for ExecutionTracer { self.step_count += 1; } - fn enter_ecall(&mut self, ecall: &'static str, trace_info: &dyn FrameTraceInfo) { + fn enter_ecall(&mut self, ecall: &'static str, args: &[u64], trace_info: &dyn FrameTraceInfo) { if self.config.limit.map(|l| self.step_count >= l).unwrap_or(false) { return; } @@ -169,6 +169,10 @@ impl Tracing for ExecutionTracer { crate::evm::Bytes::default() }; + // Extract syscall args if enabled + let syscall_args = + if self.config.enable_syscall_details { args.to_vec() } else { Vec::new() }; + let step = ExecutionStep { gas: trace_info.gas_left(), gas_cost: Default::default(), @@ -178,6 +182,8 @@ impl Tracing for ExecutionTracer { error: None, kind: ExecutionStepKind::PVMSyscall { op: lookup_syscall_index(ecall).unwrap_or_default(), + args: syscall_args, + returned: None, }, }; @@ -185,10 +191,15 @@ impl Tracing for ExecutionTracer { self.step_count += 1; } - fn exit_step(&mut self, trace_info: &dyn FrameTraceInfo) { + fn exit_step(&mut self, trace_info: &dyn FrameTraceInfo, returned: Option) { if let Some(step) = self.steps.last_mut() { step.gas_cost = step.gas.saturating_sub(trace_info.gas_left()); step.weight_cost = trace_info.weight_consumed().saturating_sub(step.weight_cost); + if self.config.enable_syscall_details { + if let ExecutionStepKind::PVMSyscall { returned: ref mut ret, .. } = step.kind { + *ret = returned; + } + } } } diff --git a/substrate/frame/revive/src/tests/sol.rs b/substrate/frame/revive/src/tests/sol.rs index 808cc1e056063..2fb6066a048f5 100644 --- a/substrate/frame/revive/src/tests/sol.rs +++ b/substrate/frame/revive/src/tests/sol.rs @@ -618,6 +618,7 @@ fn execution_tracing_works_for_evm() { disable_stack: false, disable_storage: true, enable_return_data: true, + enable_syscall_details: false, limit: Some(5), memory_word_limit: 16, }; @@ -747,6 +748,7 @@ fn execution_tracing_works_for_pvm() { let config = ExecutionTracerConfig { enable_return_data: true, + enable_syscall_details: true, limit: Some(5), ..Default::default() }; @@ -763,6 +765,10 @@ fn execution_tracing_works_for_pvm() { step.gas = Default::default(); step.gas_cost = Default::default(); step.weight_cost = Default::default(); + // Replace args with 42 as they contain memory addresses that may vary between runs + if let ExecutionStepKind::PVMSyscall { args, .. } = &mut step.kind { + args.iter_mut().for_each(|arg| *arg = 42); + } }); let expected_trace = ExecutionTrace { @@ -781,6 +787,8 @@ fn execution_tracing_works_for_pvm() { weight_cost: Default::default(), kind: ExecutionStepKind::PVMSyscall { op: lookup_syscall_index("call_data_size").unwrap_or_default(), + args: vec![], + returned: Some(36), }, }, ExecutionStep { @@ -792,6 +800,8 @@ fn execution_tracing_works_for_pvm() { weight_cost: Default::default(), kind: ExecutionStepKind::PVMSyscall { op: lookup_syscall_index("call_data_load").unwrap_or_default(), + args: vec![42, 42], + returned: None, }, }, ExecutionStep { @@ -803,6 +813,8 @@ fn execution_tracing_works_for_pvm() { weight_cost: Default::default(), kind: ExecutionStepKind::PVMSyscall { op: lookup_syscall_index("value_transferred").unwrap_or_default(), + args: vec![42], + returned: None, }, }, ExecutionStep { @@ -814,6 +826,8 @@ fn execution_tracing_works_for_pvm() { weight_cost: Default::default(), kind: ExecutionStepKind::PVMSyscall { op: lookup_syscall_index("call_data_load").unwrap_or_default(), + args: vec![42, 42], + returned: None, }, }, ExecutionStep { @@ -825,6 +839,8 @@ fn execution_tracing_works_for_pvm() { weight_cost: Default::default(), kind: ExecutionStepKind::PVMSyscall { op: lookup_syscall_index("seal_return").unwrap_or_default(), + args: vec![42, 42, 42], + returned: None, }, }, ], diff --git a/substrate/frame/revive/src/tracing.rs b/substrate/frame/revive/src/tracing.rs index aed836bcace20..10e35fde24e67 100644 --- a/substrate/frame/revive/src/tracing.rs +++ b/substrate/frame/revive/src/tracing.rs @@ -136,14 +136,22 @@ pub trait Tracing { /// /// # Parameters /// - `ecall`: The name of the syscall being executed. + /// - `args`: The syscall arguments (register values). /// - `trace_info`: Information about the current execution frame. - fn enter_ecall(&mut self, _ecall: &'static str, _trace_info: &dyn FrameTraceInfo) {} + fn enter_ecall( + &mut self, + _ecall: &'static str, + _args: &[u64], + _trace_info: &dyn FrameTraceInfo, + ) { + } /// Called after an EVM opcode or PVM syscall is executed to record the gas cost. /// /// # Parameters /// - `trace_info`: Information about the current execution frame. - fn exit_step(&mut self, _trace_info: &dyn FrameTraceInfo) {} + /// - `returned`: The syscall return value (PVM only, `None` for EVM opcodes). + fn exit_step(&mut self, _trace_info: &dyn FrameTraceInfo, _returned: Option) {} /// Called once the transaction completes to report the gas consumed by the meter. /// diff --git a/substrate/frame/revive/src/vm/evm.rs b/substrate/frame/revive/src/vm/evm.rs index d09fba49a9dc3..643c7e2827b98 100644 --- a/substrate/frame/revive/src/vm/evm.rs +++ b/substrate/frame/revive/src/vm/evm.rs @@ -161,7 +161,7 @@ fn run_plain_with_tracing( interpreter.bytecode.relative_jump(1); let res = exec_instruction(interpreter, opcode); - tracing::if_tracing(|tracer| tracer.exit_step(interpreter)); + tracing::if_tracing(|tracer| tracer.exit_step(interpreter, None)); res?; } diff --git a/substrate/frame/revive/uapi/src/lib.rs b/substrate/frame/revive/uapi/src/lib.rs index 5e3cb0ec5e705..6537f3f0b675c 100644 --- a/substrate/frame/revive/uapi/src/lib.rs +++ b/substrate/frame/revive/uapi/src/lib.rs @@ -55,7 +55,7 @@ macro_rules! define_error_codes { )* ) => { /// Every error that can be returned to a contract when it calls any of the host functions. - #[derive(Debug, PartialEq, Eq)] + #[derive(Debug, PartialEq, Eq, Copy, Clone)] #[repr(u32)] pub enum ReturnErrorCode { /// API call successful. From 68425abb31faeb850971d64a4b70200d3668da58 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Fri, 16 Jan 2026 16:55:50 +0100 Subject: [PATCH 71/76] fixes --- prdoc/pr_9722.prdoc | 43 +++++--------- .../revive/src/evm/api/debug_rpc_types.rs | 29 +++------ .../frame/revive/src/evm/api/hex_serde.rs | 59 +++++++++++++++++++ .../src/evm/tracing/execution_tracing.rs | 4 +- substrate/frame/revive/src/tests/sol.rs | 3 +- 5 files changed, 85 insertions(+), 53 deletions(-) diff --git a/prdoc/pr_9722.prdoc b/prdoc/pr_9722.prdoc index d579d2e72e5f1..e267c3baa3157 100644 --- a/prdoc/pr_9722.prdoc +++ b/prdoc/pr_9722.prdoc @@ -78,37 +78,24 @@ doc: } ``` - For a PVM execution the response will look like this + For PVM execution, each step includes additional fields not present in Geth: + + - `args`: Array of syscall arguments (register values a0-a5) as hex strings + - `returned`: The syscall return value + + These fields are enabled by default. To disable them, use `disableSyscallDetails: true`. + + Example output with syscall details: ```json { - "gas": 97945, - "weight_consumed": { "ref_time": 2938350000, "proof_size": 97 }, - "base_call_weight": { "ref_time": 9000000000, "proof_size": 3000 }, - "failed": false, - "returnValue": "0x", - "structLogs": [ - { - "gas": 97108, - "gasCost": 131, - "weight_cost": { "ref_time": 3930000, "proof_size": 0 }, - "depth": 1, - "op": "call_data_size" - }, - { - "gas": 96977, - "gasCost": 20, - "weight_cost": { "ref_time": 600000, "proof_size": 0 }, - "depth": 1, - "op": "call_data_load" - }, - { - "gas": 96957, - "gasCost": 7, - "weight_cost": { "ref_time": 210000, "proof_size": 0 }, - "depth": 1, - "op": "value_transferred" - }] + "gas": 97108, + "gasCost": 131, + "weight_cost": { "ref_time": 3930000, "proof_size": 0 }, + "depth": 1, + "op": "call_data_load", + "args": ["0x0", "0x4"], + "returned": "0x2a" } ``` crates: diff --git a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs index 3ba6b22edf1fc..0c157af11c156 100644 --- a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs +++ b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs @@ -184,8 +184,8 @@ pub struct ExecutionTracerConfig { /// Whether to enable return data capture pub enable_return_data: bool, - /// Whether to enable syscall details capture, including arguments and return value (PVM only) - pub enable_syscall_details: bool, + /// Whether to disable syscall details capture, including arguments and return value (PVM only) + pub disable_syscall_details: bool, /// Limit number of steps captured #[serde(skip_serializing_if = "Option::is_none", deserialize_with = "zero_to_none")] @@ -202,7 +202,7 @@ impl Default for ExecutionTracerConfig { disable_stack: false, disable_storage: false, enable_return_data: false, - enable_syscall_details: false, + disable_syscall_details: false, limit: None, memory_word_limit: 16, } @@ -236,7 +236,7 @@ fn test_tracer_config_serialization() { disable_stack: false, disable_storage: false, enable_return_data: true, - enable_syscall_details: false, + disable_syscall_details: false, limit: None, memory_word_limit: 16, })), @@ -557,12 +557,12 @@ pub enum ExecutionStepKind { #[serde(serialize_with = "serialize_syscall_op")] op: u8, /// The syscall arguments (register values a0-a5). - /// Only populated when `enable_syscall_details` is true in ExecutionTracerConfig. - #[serde(skip_serializing_if = "Vec::is_empty", serialize_with = "serialize_syscall_args")] + /// Omitted when `disable_syscall_details` is true in ExecutionTracerConfig. + #[serde(skip_serializing_if = "Vec::is_empty", with = "super::hex_serde::vec")] args: Vec, /// The syscall return value. - /// Only populated when `enable_syscall_details` is true in ExecutionTracerConfig. - #[serde(skip_serializing_if = "Option::is_none")] + /// Omitted when `disable_syscall_details` is true in ExecutionTracerConfig. + #[serde(skip_serializing_if = "Option::is_none", with = "super::hex_serde::option")] returned: Option, }, } @@ -767,19 +767,6 @@ where serializer.serialize_str(name) } -/// Serialize syscall arguments as hex values -fn serialize_syscall_args(args: &Vec, serializer: S) -> Result -where - S: serde::Serializer, -{ - use serde::ser::SerializeSeq; - let mut seq = serializer.serialize_seq(Some(args.len()))?; - for arg in args { - seq.serialize_element(&alloc::format!("0x{:x}", arg))?; - } - seq.end() -} - /// Deserialize opcode from string using reverse lookup table fn deserialize_opcode<'de, D>(deserializer: D) -> Result where diff --git a/substrate/frame/revive/src/evm/api/hex_serde.rs b/substrate/frame/revive/src/evm/api/hex_serde.rs index e1de2a4cb94af..82379f36b9431 100644 --- a/substrate/frame/revive/src/evm/api/hex_serde.rs +++ b/substrate/frame/revive/src/evm/api/hex_serde.rs @@ -83,3 +83,62 @@ where let value = T::from_hex(s).map_err(|e| serde::de::Error::custom(format!("{:?}", e)))?; Ok(value) } + +pub mod option { + use super::*; + + pub fn serialize(value: &Option, serializer: S) -> Result + where + S: Serializer, + T: HexCodec, + { + match value { + Some(v) => serializer.serialize_str(&v.to_hex()), + None => serializer.serialize_none(), + } + } + + pub fn deserialize<'de, D, T>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + T: HexCodec, + ::Error: core::fmt::Debug, + { + let opt = Option::::deserialize(deserializer)?; + match opt { + Some(s) => + T::from_hex(s).map(Some).map_err(|e| serde::de::Error::custom(format!("{:?}", e))), + None => Ok(None), + } + } +} + +pub mod vec { + use super::*; + use serde::ser::SerializeSeq; + + pub fn serialize(values: &Vec, serializer: S) -> Result + where + S: Serializer, + T: HexCodec, + { + let mut seq = serializer.serialize_seq(Some(values.len()))?; + for v in values { + seq.serialize_element(&v.to_hex())?; + } + seq.end() + } + + pub fn deserialize<'de, D, T>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + T: HexCodec, + ::Error: core::fmt::Debug, + { + let strings = Vec::::deserialize(deserializer)?; + strings + .into_iter() + .map(|s| T::from_hex(s).map_err(|e| serde::de::Error::custom(format!("{:?}", e)))) + .collect() + } +} diff --git a/substrate/frame/revive/src/evm/tracing/execution_tracing.rs b/substrate/frame/revive/src/evm/tracing/execution_tracing.rs index 1173ea15c0acb..40b02f5f7c78d 100644 --- a/substrate/frame/revive/src/evm/tracing/execution_tracing.rs +++ b/substrate/frame/revive/src/evm/tracing/execution_tracing.rs @@ -171,7 +171,7 @@ impl Tracing for ExecutionTracer { // Extract syscall args if enabled let syscall_args = - if self.config.enable_syscall_details { args.to_vec() } else { Vec::new() }; + if !self.config.disable_syscall_details { args.to_vec() } else { Vec::new() }; let step = ExecutionStep { gas: trace_info.gas_left(), @@ -195,7 +195,7 @@ impl Tracing for ExecutionTracer { if let Some(step) = self.steps.last_mut() { step.gas_cost = step.gas.saturating_sub(trace_info.gas_left()); step.weight_cost = trace_info.weight_consumed().saturating_sub(step.weight_cost); - if self.config.enable_syscall_details { + if !self.config.disable_syscall_details { if let ExecutionStepKind::PVMSyscall { returned: ref mut ret, .. } = step.kind { *ret = returned; } diff --git a/substrate/frame/revive/src/tests/sol.rs b/substrate/frame/revive/src/tests/sol.rs index 2fb6066a048f5..3eeece5d4b203 100644 --- a/substrate/frame/revive/src/tests/sol.rs +++ b/substrate/frame/revive/src/tests/sol.rs @@ -618,7 +618,7 @@ fn execution_tracing_works_for_evm() { disable_stack: false, disable_storage: true, enable_return_data: true, - enable_syscall_details: false, + disable_syscall_details: true, limit: Some(5), memory_word_limit: 16, }; @@ -748,7 +748,6 @@ fn execution_tracing_works_for_pvm() { let config = ExecutionTracerConfig { enable_return_data: true, - enable_syscall_details: true, limit: Some(5), ..Default::default() }; From c29362dff03ea12e0d564d203a5f26f04c06397e Mon Sep 17 00:00:00 2001 From: pgherveou Date: Mon, 19 Jan 2026 13:55:49 +0100 Subject: [PATCH 72/76] formatting --- substrate/frame/revive/src/evm/api/hex_serde.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/substrate/frame/revive/src/evm/api/hex_serde.rs b/substrate/frame/revive/src/evm/api/hex_serde.rs index 82379f36b9431..86ca0add31943 100644 --- a/substrate/frame/revive/src/evm/api/hex_serde.rs +++ b/substrate/frame/revive/src/evm/api/hex_serde.rs @@ -106,8 +106,9 @@ pub mod option { { let opt = Option::::deserialize(deserializer)?; match opt { - Some(s) => - T::from_hex(s).map(Some).map_err(|e| serde::de::Error::custom(format!("{:?}", e))), + Some(s) => T::from_hex(s) + .map(Some) + .map_err(|e| serde::de::Error::custom(format!("{:?}", e))), None => Ok(None), } } From 5e784bacf4988ef1e659fd0d684e4b1a05a67573 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Thu, 22 Jan 2026 09:18:37 +0100 Subject: [PATCH 73/76] fixes --- substrate/frame/revive/src/debug.rs | 4 ++-- substrate/frame/revive/src/lib.rs | 10 +++++----- substrate/frame/revive/src/tests.rs | 6 +++--- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/substrate/frame/revive/src/debug.rs b/substrate/frame/revive/src/debug.rs index f0f7ffa1c19d6..95af377659aed 100644 --- a/substrate/frame/revive/src/debug.rs +++ b/substrate/frame/revive/src/debug.rs @@ -36,8 +36,6 @@ use Debug; Deserialize, )] pub struct DebugSettings { - /// Whether to disable execution tracing. - disable_execution_tracing: bool, /// Whether to allow unlimited contract size. allow_unlimited_contract_size: bool, /// Whether to allow bypassing EIP-3607 (allowing transactions coming from contract or @@ -45,6 +43,8 @@ pub struct DebugSettings { bypass_eip_3607: bool, /// Whether to enable PolkaVM logs. pvm_logs: bool, + /// Whether to disable execution tracing. + disable_execution_tracing: bool, } impl DebugSettings { diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 849200b290e0f..581b8f6928e3d 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -734,7 +734,7 @@ pub mod pallet { #[derive(Clone, PartialEq, Debug, Default, serde::Serialize, serde::Deserialize)] pub struct ContractData { /// Contract code. - pub code: Vec, + pub code: crate::evm::Bytes, /// Initial storage entries as 32-byte key/value pairs. pub storage: alloc::collections::BTreeMap, } @@ -813,12 +813,12 @@ pub mod pallet { ); }, Some(genesis::ContractData { code, storage }) => { - let blob = if code.starts_with(&polkavm_common::program::BLOB_MAGIC) { - ContractBlob::::from_pvm_code( code.clone(), owner.clone()).inspect_err(|err| { + let blob = if code.0.starts_with(&polkavm_common::program::BLOB_MAGIC) { + ContractBlob::::from_pvm_code( code.0.clone(), owner.clone()).inspect_err(|err| { log::error!(target: LOG_TARGET, "Failed to create PVM ContractBlob for {address:?}: {err:?}"); }) } else { - ContractBlob::::from_evm_runtime_code(code.clone(), account_id).inspect_err(|err| { + ContractBlob::::from_evm_runtime_code(code.0.clone(), account_id).inspect_err(|err| { log::error!(target: LOG_TARGET, "Failed to create EVM ContractBlob for {address:?}: {err:?}"); }) }; @@ -841,7 +841,7 @@ pub mod pallet { AccountInfo { account_type: info.clone().into(), dust: 0 }, ); - >::insert(blob.code_hash(), code); + >::insert(blob.code_hash(), code.0.clone()); >::insert(blob.code_hash(), blob.code_info().clone()); for (k, v) in storage { let _ = info.write(&Key::from_fixed(k.0), Some(v.0.to_vec()), None, false).inspect_err(|err| { diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index 9785e526f91a8..aa9a5497b18aa 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -575,7 +575,7 @@ fn ext_builder_with_genesis_config_works() { balance: U256::from(100_000_100), nonce: 42, contract_data: Some(ContractData { - code: compile_module("dummy").unwrap().0, + code: compile_module("dummy").unwrap().0.into(), storage: [([1u8; 32].into(), [2u8; 32].into())].into_iter().collect(), }), }; @@ -591,7 +591,7 @@ fn ext_builder_with_genesis_config_works() { revm::bytecode::opcode::PUSH1, 0x00, revm::bytecode::opcode::RETURN, - ], + ].into(), storage: [([3u8; 32].into(), [4u8; 32].into())].into_iter().collect(), }), }; @@ -627,7 +627,7 @@ fn ext_builder_with_genesis_config_works() { assert_eq!( PristineCode::::get(&contract_info.code_hash).unwrap(), - contract_data.code + contract_data.code.0 ); assert_eq!(Pallet::::evm_nonce(&contract.address), contract.nonce); assert_eq!(Pallet::::evm_balance(&contract.address), contract.balance); From 9060ba51f53f269d549091bf7efccd7759c39b20 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Thu, 22 Jan 2026 15:51:26 +0100 Subject: [PATCH 74/76] fix fmt --- substrate/frame/revive/src/tests.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index aa9a5497b18aa..614a8a25e8978 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -591,7 +591,8 @@ fn ext_builder_with_genesis_config_works() { revm::bytecode::opcode::PUSH1, 0x00, revm::bytecode::opcode::RETURN, - ].into(), + ] + .into(), storage: [([3u8; 32].into(), [4u8; 32].into())].into_iter().collect(), }), }; From 7da932c69437b1b496c07b1a1ce74e2db14b9809 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Mon, 26 Jan 2026 12:55:26 +0000 Subject: [PATCH 75/76] update rpc test to fix CI? --- substrate/frame/revive/rpc/examples/deploy.rs | 4 +- .../revive/rpc/examples/eth-rpc-tester.rs | 4 +- .../frame/revive/rpc/examples/transfer.rs | 2 +- .../frame/revive/rpc/examples/tx-types.rs | 2 +- substrate/frame/revive/rpc/src/example.rs | 4 +- substrate/frame/revive/rpc/src/tests.rs | 114 +++++++++++------- 6 files changed, 76 insertions(+), 54 deletions(-) diff --git a/substrate/frame/revive/rpc/examples/deploy.rs b/substrate/frame/revive/rpc/examples/deploy.rs index 12f539bba42ff..bcea0fc280c1e 100644 --- a/substrate/frame/revive/rpc/examples/deploy.rs +++ b/substrate/frame/revive/rpc/examples/deploy.rs @@ -39,7 +39,7 @@ async fn main() -> anyhow::Result<()> { println!("\n\n=== Deploying contract ===\n\n"); let nonce = client.get_transaction_count(account.address(), BlockTag::Latest.into()).await?; - let tx = TransactionBuilder::new(&client) + let tx = TransactionBuilder::new(client.clone()) .value(5_000_000_000_000u128.into()) .input(input) .send() @@ -65,7 +65,7 @@ async fn main() -> anyhow::Result<()> { } println!("\n\n=== Calling contract ===\n\n"); - let tx = TransactionBuilder::new(&client) + let tx = TransactionBuilder::new(client.clone()) .value(U256::from(1_000_000u32)) .to(contract_address) .send() diff --git a/substrate/frame/revive/rpc/examples/eth-rpc-tester.rs b/substrate/frame/revive/rpc/examples/eth-rpc-tester.rs index 9f3c44a3289f0..1fa03ffbe7dbb 100644 --- a/substrate/frame/revive/rpc/examples/eth-rpc-tester.rs +++ b/substrate/frame/revive/rpc/examples/eth-rpc-tester.rs @@ -141,7 +141,7 @@ async fn test_eth_rpc(rpc_url: &str) -> anyhow::Result<()> { println!("- balance: {balance:?}"); println!("\n\n=== Deploying dummy contract ===\n\n"); - let tx = TransactionBuilder::new(&client).input(input).send().await?; + let tx = TransactionBuilder::new(client.clone()).input(input).send().await?; println!("Hash: {:?}", tx.hash()); println!("Waiting for receipt..."); @@ -156,7 +156,7 @@ async fn test_eth_rpc(rpc_url: &str) -> anyhow::Result<()> { println!("- Address: {contract_address:?}"); println!("\n\n=== Calling dummy contract ===\n\n"); - let tx = TransactionBuilder::new(&client).to(contract_address).send().await?; + let tx = TransactionBuilder::new(client.clone()).to(contract_address).send().await?; println!("Hash: {:?}", tx.hash()); println!("Waiting for receipt..."); diff --git a/substrate/frame/revive/rpc/examples/transfer.rs b/substrate/frame/revive/rpc/examples/transfer.rs index e4eb27aabfb36..12823d4a34827 100644 --- a/substrate/frame/revive/rpc/examples/transfer.rs +++ b/substrate/frame/revive/rpc/examples/transfer.rs @@ -39,7 +39,7 @@ async fn main() -> anyhow::Result<()> { print_balance().await?; println!("\n\n=== Transferring ===\n\n"); - let tx = TransactionBuilder::new(&client) + let tx = TransactionBuilder::new(client.clone()) .signer(alith) .value(value) .to(ethan.address()) diff --git a/substrate/frame/revive/rpc/examples/tx-types.rs b/substrate/frame/revive/rpc/examples/tx-types.rs index a3467d4f4933c..ec6dccf401b20 100644 --- a/substrate/frame/revive/rpc/examples/tx-types.rs +++ b/substrate/frame/revive/rpc/examples/tx-types.rs @@ -35,7 +35,7 @@ async fn main() -> anyhow::Result<()> { ] { println!("\n\n=== TransactionType {tx_type:?} ===\n\n",); - let tx = TransactionBuilder::new(&client) + let tx = TransactionBuilder::new(client.clone()) .signer(alith.clone()) .value(value) .to(ethan.address()) diff --git a/substrate/frame/revive/rpc/src/example.rs b/substrate/frame/revive/rpc/src/example.rs index 4b5208c5e94b4..e4a439cf45c2e 100644 --- a/substrate/frame/revive/rpc/src/example.rs +++ b/substrate/frame/revive/rpc/src/example.rs @@ -88,9 +88,9 @@ impl SubmittedTransaction { } impl TransactionBuilder { - pub fn new(client: &Arc) -> Self { + pub fn new(client: Arc) -> Self { Self { - client: Arc::clone(client), + client, signer: Account::default(), value: U256::zero(), input: Bytes::default(), diff --git a/substrate/frame/revive/rpc/src/tests.rs b/substrate/frame/revive/rpc/src/tests.rs index 8201b434d59f7..8ced08e1979e5 100644 --- a/substrate/frame/revive/rpc/src/tests.rs +++ b/substrate/frame/revive/rpc/src/tests.rs @@ -20,7 +20,6 @@ use crate::{ cli::{self, CliCommand}, - client, example::TransactionBuilder, subxt_client::{ self, src_chain::runtime_types::pallet_revive::primitives::Code, SrcChainConfig, @@ -103,6 +102,10 @@ impl SharedResources { ws_client_with_retry("ws://localhost:45788").await } + async fn node_client() -> OnlineClient { + OnlineClient::::from_url(Self::node_rpc_url()).await.unwrap() + } + fn node_rpc_url() -> &'static str { "ws://localhost:45789" } @@ -120,7 +123,7 @@ macro_rules! unwrap_call_err( // Helper functions /// Prepare multiple EVM transfer transactions with nonce in descending order async fn prepare_evm_transactions( - client: &Arc, + client: Arc, signer: Account, recipient: pallet_revive::evm::Address, amount: U256, @@ -132,7 +135,7 @@ async fn prepare_evm_transactions( let mut transactions = Vec::new(); for i in (0..count).rev() { let nonce = start_nonce.saturating_add(U256::from(i as u64)); - let tx_builder = TransactionBuilder::new(client) + let tx_builder = TransactionBuilder::new(client.clone()) .signer(signer.clone()) .nonce(nonce) .value(amount) @@ -264,9 +267,24 @@ async fn verify_transactions_in_single_block( #[tokio::test] async fn run_all_eth_rpc_tests() -> anyhow::Result<()> { + // Set up a 2-minute timeout for the entire test + let timeout_duration = tokio::time::Duration::from_secs(120); + let result = tokio::time::timeout(timeout_duration, run_all_eth_rpc_tests_inner()).await; + + match result { + Ok(inner_result) => inner_result, + Err(_) => { + log::error!(target: LOG_TARGET, "Test timed out after 2 minutes!"); + std::process::exit(1); + }, + } +} + +async fn run_all_eth_rpc_tests_inner() -> anyhow::Result<()> { // start node and rpc server let _shared = SharedResources::start(); - let client = Arc::new(SharedResources::client().await); + // Wait for servers to be ready + let _ = SharedResources::client().await; macro_rules! run_tests { ($($test:ident),+ $(,)?) => { @@ -274,7 +292,7 @@ async fn run_all_eth_rpc_tests() -> anyhow::Result<()> { { let test_name = stringify!($test); log::debug!(target: LOG_TARGET, "Running test: {}", test_name); - match $test(client.clone()).await { + match $test().await { Ok(()) => log::debug!(target: LOG_TARGET, "Test passed: {}", test_name), Err(err) => panic!("Test {} failed: {err:?}", test_name), } @@ -303,12 +321,13 @@ async fn run_all_eth_rpc_tests() -> anyhow::Result<()> { Ok(()) } -async fn test_transfer(client: Arc) -> anyhow::Result<()> { +async fn test_transfer() -> anyhow::Result<()> { + let client = Arc::new(SharedResources::client().await); let ethan = Account::from(subxt_signer::eth::dev::ethan()); let initial_balance = client.get_balance(ethan.address(), BlockTag::Latest.into()).await?; let value = 1_000_000_000_000_000_000_000u128.into(); - let tx = TransactionBuilder::new(&client).value(value).to(ethan.address()).send().await?; + let tx = TransactionBuilder::new(client.clone()).value(value).to(ethan.address()).send().await?; let receipt = tx.wait_for_receipt().await?; assert_eq!( @@ -327,14 +346,15 @@ async fn test_transfer(client: Arc) -> anyhow::Result<()> { Ok(()) } -async fn test_deploy_and_call(client: Arc) -> anyhow::Result<()> { +async fn test_deploy_and_call() -> anyhow::Result<()> { + let client = Arc::new(SharedResources::client().await); let account = Account::default(); // Balance transfer let ethan = Account::from(subxt_signer::eth::dev::ethan()); let initial_balance = client.get_balance(ethan.address(), BlockTag::Latest.into()).await?; let value = 1_000_000_000_000_000_000_000u128.into(); - let tx = TransactionBuilder::new(&client).value(value).to(ethan.address()).send().await?; + let tx = TransactionBuilder::new(client.clone()).value(value).to(ethan.address()).send().await?; let receipt = tx.wait_for_receipt().await?; assert_eq!( @@ -357,7 +377,7 @@ async fn test_deploy_and_call(client: Arc) -> anyhow::Result<()> { let (bytes, _) = pallet_revive_fixtures::compile_module("dummy")?; let input = bytes.into_iter().chain(data.clone()).collect::>(); let nonce = client.get_transaction_count(account.address(), BlockTag::Latest.into()).await?; - let tx = TransactionBuilder::new(&client).value(value).input(input).send().await?; + let tx = TransactionBuilder::new(client.clone()).value(value).input(input).send().await?; let receipt = tx.wait_for_receipt().await?; let contract_address = create1(&account.address(), nonce.try_into().unwrap()); assert_eq!( @@ -378,7 +398,7 @@ async fn test_deploy_and_call(client: Arc) -> anyhow::Result<()> { ); // Call contract - let tx = TransactionBuilder::new(&client) + let tx = TransactionBuilder::new(client.clone()) .value(value) .to(contract_address) .send() @@ -396,7 +416,7 @@ async fn test_deploy_and_call(client: Arc) -> anyhow::Result<()> { // Balance transfer to contract let initial_balance = client.get_balance(contract_address, BlockTag::Latest.into()).await?; - let tx = TransactionBuilder::new(&client) + let tx = TransactionBuilder::new(client.clone()) .value(value) .to(contract_address) .send() @@ -414,7 +434,9 @@ async fn test_deploy_and_call(client: Arc) -> anyhow::Result<()> { Ok(()) } -async fn test_runtime_api_dry_run_addr_works(client: Arc) -> anyhow::Result<()> { +async fn test_runtime_api_dry_run_addr_works() -> anyhow::Result<()> { + let client = Arc::new(SharedResources::client().await); + let node_client = SharedResources::node_client().await; let account = Account::default(); let origin: [u8; 32] = account.substrate_account().into(); let data = b"hello world".to_vec(); @@ -437,17 +459,17 @@ async fn test_runtime_api_dry_run_addr_works(client: Arc) -> anyhow::R .await?; let contract_address = create1(&account.address(), nonce.try_into().unwrap()); - let c = OnlineClient::::from_url("ws://localhost:45789").await?; - let res = c.runtime_api().at_latest().await?.call(payload).await?.result.unwrap(); + let res = node_client.runtime_api().at_latest().await?.call(payload).await?.result.unwrap(); assert_eq!(res.addr, contract_address); Ok(()) } -async fn test_invalid_transaction(client: Arc) -> anyhow::Result<()> { +async fn test_invalid_transaction() -> anyhow::Result<()> { + let client = Arc::new(SharedResources::client().await); let ethan = Account::from(subxt_signer::eth::dev::ethan()); - let err = TransactionBuilder::new(&client) + let err = TransactionBuilder::new(client.clone()) .value(U256::from(1_000_000_000_000u128)) .to(ethan.address()) .mutate(|tx| match tx { @@ -484,14 +506,15 @@ async fn get_evm_block_from_storage( Ok(block.0) } -async fn test_evm_blocks_should_match(client: Arc) -> anyhow::Result<()> { - let (node_client, node_rpc_client, _) = - client::connect(SharedResources::node_rpc_url(), 1024, 1024).await.unwrap(); +async fn test_evm_blocks_should_match() -> anyhow::Result<()> { + let client = Arc::new(SharedResources::client().await); + let node_client = SharedResources::node_client().await; + let node_rpc_client = RpcClient::from_url(SharedResources::node_rpc_url()).await?; // Deploy a contract to have some interesting blocks let (bytes, _) = pallet_revive_fixtures::compile_module("dummy")?; let value = U256::from(5_000_000_000_000u128); - let tx = TransactionBuilder::new(&client) + let tx = TransactionBuilder::new(client.clone()) .value(value) .input(bytes.to_vec()) .send() @@ -529,13 +552,14 @@ async fn test_evm_blocks_should_match(client: Arc) -> anyhow::Result<( Ok(()) } -async fn test_evm_blocks_hydrated_should_match(client: Arc) -> anyhow::Result<()> { +async fn test_evm_blocks_hydrated_should_match() -> anyhow::Result<()> { + let client = Arc::new(SharedResources::client().await); // Deploy a contract to have some transactions in the block let (bytes, _) = pallet_revive_fixtures::compile_module("dummy")?; let value = U256::from(5_000_000_000_000u128); let signer = Account::default(); let signer_copy = Account::default(); - let tx = TransactionBuilder::new(&client) + let tx = TransactionBuilder::new(client.clone()) .value(value) .signer(signer) .input(bytes.to_vec()) @@ -582,13 +606,12 @@ async fn test_evm_blocks_hydrated_should_match(client: Arc) -> anyhow: Ok(()) } -async fn test_block_hash_for_tag_with_proper_ethereum_block_hash_works( - client: Arc, -) -> anyhow::Result<()> { +async fn test_block_hash_for_tag_with_proper_ethereum_block_hash_works() -> anyhow::Result<()> { + let client = Arc::new(SharedResources::client().await); // Deploy a transaction to create a block with transactions let (bytes, _) = pallet_revive_fixtures::compile_module("dummy")?; let value = U256::from(5_000_000_000_000u128); - let tx = TransactionBuilder::new(&client) + let tx = TransactionBuilder::new(client.clone()) .value(value) .input(bytes.to_vec()) .send() @@ -613,9 +636,8 @@ async fn test_block_hash_for_tag_with_proper_ethereum_block_hash_works( Ok(()) } -async fn test_block_hash_for_tag_with_invalid_ethereum_block_hash_fails( - client: Arc, -) -> anyhow::Result<()> { +async fn test_block_hash_for_tag_with_invalid_ethereum_block_hash_fails() -> anyhow::Result<()> { + let client = Arc::new(SharedResources::client().await); let fake_eth_hash = H256::from([0x42u8; 32]); log::trace!(target: LOG_TARGET, "Testing with fake Ethereum hash: {fake_eth_hash:?}"); @@ -628,9 +650,8 @@ async fn test_block_hash_for_tag_with_invalid_ethereum_block_hash_fails( Ok(()) } -async fn test_block_hash_for_tag_with_block_number_works( - client: Arc, -) -> anyhow::Result<()> { +async fn test_block_hash_for_tag_with_block_number_works() -> anyhow::Result<()> { + let client = Arc::new(SharedResources::client().await); let block_number = client.block_number().await?; log::trace!(target: LOG_TARGET, "Testing with block number: {block_number}"); @@ -644,9 +665,8 @@ async fn test_block_hash_for_tag_with_block_number_works( Ok(()) } -async fn test_block_hash_for_tag_with_block_tags_works( - client: Arc, -) -> anyhow::Result<()> { +async fn test_block_hash_for_tag_with_block_tags_works() -> anyhow::Result<()> { + let client = Arc::new(SharedResources::client().await); let account = Account::default(); let tags = vec![ @@ -666,7 +686,8 @@ async fn test_block_hash_for_tag_with_block_tags_works( Ok(()) } -async fn test_multiple_transactions_in_block(client: Arc) -> anyhow::Result<()> { +async fn test_multiple_transactions_in_block() -> anyhow::Result<()> { + let client = Arc::new(SharedResources::client().await); let num_transactions = 20; let alith = Account::default(); let ethan = Account::from(subxt_signer::eth::dev::ethan()); @@ -674,7 +695,7 @@ async fn test_multiple_transactions_in_block(client: Arc) -> anyhow::R // Prepare EVM transfer transactions let transactions = - prepare_evm_transactions(&client, alith, ethan.address(), amount, num_transactions).await?; + prepare_evm_transactions(client.clone(), alith, ethan.address(), amount, num_transactions).await?; // Submit all transactions let submitted_txs = submit_evm_transactions(transactions).await?; @@ -689,7 +710,9 @@ async fn test_multiple_transactions_in_block(client: Arc) -> anyhow::R Ok(()) } -async fn test_mixed_evm_substrate_transactions(client: Arc) -> anyhow::Result<()> { +async fn test_mixed_evm_substrate_transactions() -> anyhow::Result<()> { + let client = Arc::new(SharedResources::client().await); + let node_client = SharedResources::node_client().await; let num_evm_txs = 10; let num_substrate_txs = 7; @@ -700,13 +723,11 @@ async fn test_mixed_evm_substrate_transactions(client: Arc) -> anyhow: // Prepare EVM transactions log::trace!(target: LOG_TARGET, "Creating {num_evm_txs} EVM transfer transactions"); let evm_transactions = - prepare_evm_transactions(&client, alith, ethan.address(), amount, num_evm_txs).await?; + prepare_evm_transactions(client.clone(), alith, ethan.address(), amount, num_evm_txs).await?; // Prepare substrate transactions (simple remarks) log::trace!(target: LOG_TARGET, "Creating {num_substrate_txs} substrate remark transactions"); let alice_signer = subxt_signer::sr25519::dev::alice(); - let (node_client, _, _) = - client::connect(SharedResources::node_rpc_url(), 1024, 1024).await.unwrap(); let substrate_txs = prepare_substrate_transactions(&node_client, &alice_signer, num_substrate_txs).await?; @@ -735,9 +756,10 @@ async fn test_mixed_evm_substrate_transactions(client: Arc) -> anyhow: Ok(()) } -async fn test_runtime_pallets_address_upload_code(client: Arc) -> anyhow::Result<()> { - let (node_client, node_rpc_client, _) = - client::connect(SharedResources::node_rpc_url(), 1024, 1024).await?; +async fn test_runtime_pallets_address_upload_code() -> anyhow::Result<()> { + let client = Arc::new(SharedResources::client().await); + let node_client = SharedResources::node_client().await; + let node_rpc_client = RpcClient::from_url(SharedResources::node_rpc_url()).await?; let (bytecode, _) = pallet_revive_fixtures::compile_module("dummy")?; let signer = Account::default(); @@ -764,7 +786,7 @@ async fn test_runtime_pallets_address_upload_code(client: Arc) -> anyh let encoded_call = node_client.tx().call_data(&upload_call)?; // Step 2: Send the encoded call to RUNTIME_PALLETS_ADDR - let tx = TransactionBuilder::new(&client) + let tx = TransactionBuilder::new(client.clone()) .signer(signer.clone()) .to(pallet_revive::RUNTIME_PALLETS_ADDR) .input(encoded_call.clone()) From 9ecca66044d411ddd01d7416b822417ca89b38bb Mon Sep 17 00:00:00 2001 From: pgherveou Date: Mon, 26 Jan 2026 13:34:47 +0000 Subject: [PATCH 76/76] fmt --- substrate/frame/revive/rpc/src/tests.rs | 27 ++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/substrate/frame/revive/rpc/src/tests.rs b/substrate/frame/revive/rpc/src/tests.rs index 8ced08e1979e5..fbf9d7e9a75d2 100644 --- a/substrate/frame/revive/rpc/src/tests.rs +++ b/substrate/frame/revive/rpc/src/tests.rs @@ -327,7 +327,11 @@ async fn test_transfer() -> anyhow::Result<()> { let initial_balance = client.get_balance(ethan.address(), BlockTag::Latest.into()).await?; let value = 1_000_000_000_000_000_000_000u128.into(); - let tx = TransactionBuilder::new(client.clone()).value(value).to(ethan.address()).send().await?; + let tx = TransactionBuilder::new(client.clone()) + .value(value) + .to(ethan.address()) + .send() + .await?; let receipt = tx.wait_for_receipt().await?; assert_eq!( @@ -354,7 +358,11 @@ async fn test_deploy_and_call() -> anyhow::Result<()> { let ethan = Account::from(subxt_signer::eth::dev::ethan()); let initial_balance = client.get_balance(ethan.address(), BlockTag::Latest.into()).await?; let value = 1_000_000_000_000_000_000_000u128.into(); - let tx = TransactionBuilder::new(client.clone()).value(value).to(ethan.address()).send().await?; + let tx = TransactionBuilder::new(client.clone()) + .value(value) + .to(ethan.address()) + .send() + .await?; let receipt = tx.wait_for_receipt().await?; assert_eq!( @@ -459,7 +467,14 @@ async fn test_runtime_api_dry_run_addr_works() -> anyhow::Result<()> { .await?; let contract_address = create1(&account.address(), nonce.try_into().unwrap()); - let res = node_client.runtime_api().at_latest().await?.call(payload).await?.result.unwrap(); + let res = node_client + .runtime_api() + .at_latest() + .await? + .call(payload) + .await? + .result + .unwrap(); assert_eq!(res.addr, contract_address); Ok(()) @@ -695,7 +710,8 @@ async fn test_multiple_transactions_in_block() -> anyhow::Result<()> { // Prepare EVM transfer transactions let transactions = - prepare_evm_transactions(client.clone(), alith, ethan.address(), amount, num_transactions).await?; + prepare_evm_transactions(client.clone(), alith, ethan.address(), amount, num_transactions) + .await?; // Submit all transactions let submitted_txs = submit_evm_transactions(transactions).await?; @@ -723,7 +739,8 @@ async fn test_mixed_evm_substrate_transactions() -> anyhow::Result<()> { // Prepare EVM transactions log::trace!(target: LOG_TARGET, "Creating {num_evm_txs} EVM transfer transactions"); let evm_transactions = - prepare_evm_transactions(client.clone(), alith, ethan.address(), amount, num_evm_txs).await?; + prepare_evm_transactions(client.clone(), alith, ethan.address(), amount, num_evm_txs) + .await?; // Prepare substrate transactions (simple remarks) log::trace!(target: LOG_TARGET, "Creating {num_substrate_txs} substrate remark transactions");