diff --git a/Cargo.lock b/Cargo.lock index dca98bc2f4..f3198e9a2f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7084,11 +7084,18 @@ version = "0.1.0" dependencies = [ "ethereum-types", "evm-tracing-events", + "fp-rpc", "hex", + "log", "moonbeam-rpc-primitives-debug", "parity-scale-codec", + "rlp", "serde", "serde_json", + "sha3", + "sp-api", + "sp-block-builder", + "sp-runtime", "sp-std", ] @@ -7192,6 +7199,7 @@ dependencies = [ "moonbeam-rpc-primitives-debug", "sc-client-api", "sc-utils", + "sha3", "sp-api", "sp-block-builder", "sp-blockchain", diff --git a/runtime/astar/src/lib.rs b/runtime/astar/src/lib.rs index f666aa252c..f4c1e94dc8 100644 --- a/runtime/astar/src/lib.rs +++ b/runtime/astar/src/lib.rs @@ -1413,6 +1413,18 @@ impl_runtime_apis! { pallet_evm::AccountStorages::::get(address, H256::from_slice(&tmp[..])) } + fn storage_range_at(address: H160, start_key: H256, limit: u64) -> (Vec<(H256, H256)>, Option) { + let iter = pallet_evm::AccountStorages::::iter_prefix_from(address, start_key.as_bytes().to_vec()); + let mut res: Vec<(H256, H256)> = vec![]; + for (key, value) in iter { + if res.len() == limit as usize { + return (res, Some(key)); + } + res.push((key, value)); + } + return (res, None); + } + fn call( from: H160, to: H160, diff --git a/runtime/local/src/lib.rs b/runtime/local/src/lib.rs index 741cd43830..a965987ca2 100644 --- a/runtime/local/src/lib.rs +++ b/runtime/local/src/lib.rs @@ -1451,6 +1451,18 @@ impl_runtime_apis! { pallet_evm::AccountStorages::::get(address, H256::from_slice(&tmp[..])) } + fn storage_range_at(address: H160, start_key: H256, limit: u64) -> (Vec<(H256, H256)>, Option) { + let iter = pallet_evm::AccountStorages::::iter_prefix_from(address, start_key.as_bytes().to_vec()); + let mut res: Vec<(H256, H256)> = vec![]; + for (key, value) in iter { + if res.len() == limit as usize { + return (res, Some(key)); + } + res.push((key, value)); + } + return (res, None); + } + fn call( from: H160, to: H160, diff --git a/runtime/shibuya/src/lib.rs b/runtime/shibuya/src/lib.rs index d6f5ce0577..2f9dc6c8a8 100644 --- a/runtime/shibuya/src/lib.rs +++ b/runtime/shibuya/src/lib.rs @@ -1629,6 +1629,18 @@ impl_runtime_apis! { pallet_evm::AccountStorages::::get(address, H256::from_slice(&tmp[..])) } + fn storage_range_at(address: H160, start_key: H256, limit: u64) -> (Vec<(H256, H256)>, Option) { + let iter = pallet_evm::AccountStorages::::iter_prefix_from(address, start_key.as_bytes().to_vec()); + let mut res: Vec<(H256, H256)> = vec![]; + for (key, value) in iter { + if res.len() == limit as usize { + return (res, Some(key)); + } + res.push((key, value)); + } + return (res, None); + } + fn call( from: H160, to: H160, diff --git a/runtime/shiden/src/lib.rs b/runtime/shiden/src/lib.rs index 560afa2eb7..881ff8620b 100644 --- a/runtime/shiden/src/lib.rs +++ b/runtime/shiden/src/lib.rs @@ -1391,6 +1391,18 @@ impl_runtime_apis! { pallet_evm::AccountStorages::::get(address, H256::from_slice(&tmp[..])) } + fn storage_range_at(address: H160, start_key: H256, limit: u64) -> (Vec<(H256, H256)>, Option) { + let iter = pallet_evm::AccountStorages::::iter_prefix_from(address, start_key.as_bytes().to_vec()); + let mut res: Vec<(H256, H256)> = vec![]; + for (key, value) in iter { + if res.len() == limit as usize { + return (res, Some(key)); + } + res.push((key, value)); + } + return (res, None); + } + fn call( from: H160, to: H160, diff --git a/vendor/evm-tracing/Cargo.toml b/vendor/evm-tracing/Cargo.toml index 557126f6f2..d64d661261 100644 --- a/vendor/evm-tracing/Cargo.toml +++ b/vendor/evm-tracing/Cargo.toml @@ -9,9 +9,12 @@ version = "0.1.0" [dependencies] ethereum-types = { workspace = true, features = ["std"] } +log = { workspace = true } hex = { workspace = true, features = ["serde"] } serde = { workspace = true } serde_json = { workspace = true } +sha3 = { workspace = true } +rlp = { workspace = true } # Moonbeam evm-tracing-events = { workspace = true, features = ["std"] } @@ -20,3 +23,9 @@ moonbeam-rpc-primitives-debug = { workspace = true } # Substrate parity-scale-codec = { workspace = true } sp-std = { workspace = true, features = ["std"] } +sp-api = { workspace = true, features = [ "std" ] } +sp-runtime = { workspace = true, features = [ "std" ] } +sp-block-builder = { workspace = true, features = [ "std" ] } + +# Frontier +fp-rpc = { workspace = true, features = [ "std" ] } diff --git a/vendor/evm-tracing/src/formatters/mod.rs b/vendor/evm-tracing/src/formatters/mod.rs index 3d991ecea4..12d7f3b9da 100644 --- a/vendor/evm-tracing/src/formatters/mod.rs +++ b/vendor/evm-tracing/src/formatters/mod.rs @@ -19,10 +19,15 @@ pub mod call_tracer; pub mod raw; pub mod trace_filter; +pub mod sentio_call_tracer; +pub mod sentio_prestate_tracer; + pub use blockscout::Formatter as Blockscout; pub use call_tracer::Formatter as CallTracer; pub use raw::Formatter as Raw; pub use trace_filter::Formatter as TraceFilter; +pub use sentio_call_tracer::Formatter as SentioTracer; +pub use sentio_prestate_tracer::Formatter as SentioPrestateTracer; use evm_tracing_events::Listener; use serde::Serialize; diff --git a/vendor/evm-tracing/src/formatters/sentio_call_tracer.rs b/vendor/evm-tracing/src/formatters/sentio_call_tracer.rs new file mode 100644 index 0000000000..15f2138e9b --- /dev/null +++ b/vendor/evm-tracing/src/formatters/sentio_call_tracer.rs @@ -0,0 +1,34 @@ +// Copyright 2019-2022 PureStake Inc. +// This file is part of Moonbeam. + +// Moonbeam is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Moonbeam is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Moonbeam. If not, see . + +use crate::types::single::TransactionTrace; + +use crate::listeners::sentio_call_list::Listener; + +pub struct Formatter; + +impl super::ResponseFormatter for Formatter { + type Listener = Listener; + type Response = Vec; + + fn format(listener: Listener) -> Option> { + if listener.results.is_empty() { + None + } else { + Some(listener.results.into_iter().map(|call| TransactionTrace::SentioCallTrace(call)).collect()) + } + } +} diff --git a/vendor/evm-tracing/src/formatters/sentio_prestate_tracer.rs b/vendor/evm-tracing/src/formatters/sentio_prestate_tracer.rs new file mode 100644 index 0000000000..c1e78358dc --- /dev/null +++ b/vendor/evm-tracing/src/formatters/sentio_prestate_tracer.rs @@ -0,0 +1,52 @@ +// Copyright 2019-2022 PureStake Inc. +// This file is part of Moonbeam. + +// Moonbeam is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Moonbeam is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Moonbeam. If not, see . + +use std::marker::PhantomData; +use fp_rpc::EthereumRuntimeRPCApi; +use sp_api::{BlockT, ProvideRuntimeApi}; +use sp_block_builder::BlockBuilder; +use crate::types::single::TransactionTrace; + +use crate::listeners::sentio_prestate::Listener; + +pub struct Formatter + where + B: BlockT, + C:ProvideRuntimeApi, + C::Api: EthereumRuntimeRPCApi, + C::Api: BlockBuilder { + _b: PhantomData, + _c: PhantomData +} + +impl super::ResponseFormatter for Formatter + where + B: BlockT, + C:ProvideRuntimeApi + 'static, + C::Api: EthereumRuntimeRPCApi, + C::Api: BlockBuilder +{ + type Listener = Listener; + type Response = Vec; + + fn format(listener: Listener) -> Option> { + if listener.results.is_empty() { + None + } else { + Some(listener.results.into_iter().map(|trace| TransactionTrace::SentioPrestateTrace(trace)).collect()) + } + } +} diff --git a/vendor/evm-tracing/src/listeners/mod.rs b/vendor/evm-tracing/src/listeners/mod.rs index 5049d5f583..032aea85cf 100644 --- a/vendor/evm-tracing/src/listeners/mod.rs +++ b/vendor/evm-tracing/src/listeners/mod.rs @@ -17,5 +17,11 @@ pub mod call_list; pub mod raw; +pub mod sentio_call_list; +pub mod sentio_prestate; +mod sentio_util; + pub use call_list::Listener as CallList; pub use raw::Listener as Raw; +pub use sentio_call_list::Listener as SentioCallList; +pub use sentio_prestate::Listener as SentioPrestate; \ No newline at end of file diff --git a/vendor/evm-tracing/src/listeners/sentio_call_list.rs b/vendor/evm-tracing/src/listeners/sentio_call_list.rs new file mode 100644 index 0000000000..f6ec269244 --- /dev/null +++ b/vendor/evm-tracing/src/listeners/sentio_call_list.rs @@ -0,0 +1,855 @@ +// Copyright 2019-2022 PureStake Inc. +// This file is part of Moonbeam. + +// Moonbeam is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Moonbeam is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Moonbeam. If not, see . + +// Early transact mode: +// +// GasometerEvent::RecordTransaction +// EvmEvent::TransactCall +// GasometerEvent::RecordTransaction +// +// EvmEvent::Call // GasometerEvent::RecordTransaction +// StepResult::StaticCall +// EvmEvent::StaticCall +// EvmEvent::Exit +// StepResult::Exit +// EvmEvent::Exit +// +// Finish +// +// Precompile call will not trigger StepResult::Exit + +use ethereum_types::{H160, H256, U256}; +use evm_tracing_events::{ + runtime::{Capture, ExitError, ExitReason, Memory, Opcode, Stack}, + Event, EvmEvent, GasometerEvent, Listener as ListenerT, RuntimeEvent, StepEventFilter, +}; +use std::{collections::HashMap, str::FromStr, vec, vec::Vec}; + +use crate::listeners::sentio_util::{ + copy_memory, copy_stack, format_memory, stack_back, to_opcode, unpack_revert, +}; +use crate::types::sentio; +use crate::types::sentio::{ + FunctionInfo, SentioBaseTrace, SentioCallTrace, SentioEventTrace, SentioTrace, +}; + +/// Enum of the different "modes" of tracer for multiple runtime versions and +/// the kind of EVM events that are emitted. +enum TracingVersion { + /// The first event of the transaction is `EvmEvent::TransactX`. It goes along other events + /// such as `EvmEvent::Exit`. All contexts should have clear start/end boundaries. + EarlyTransact, + /// Older version in which the events above didn't existed. + /// It means that we cannot rely on those events to perform any task, and must rely only + /// on other events. + Legacy, +} + +#[derive(Debug)] +struct Context { + // storage_cache: BTreeMap, + address: H160, + code_address: Option, + current_step: Option, + // Only for debug + current_opcode: Option, + gas: u64, + start_gas: u64, + // global_storage_changes: BTreeMap>, +} + +impl Context { + fn used_gas(&self) -> u64 { + self.start_gas - self.gas + } +} + +#[derive(Debug, Clone)] +struct Step { + /// Current opcode. + opcode: Opcode, + /// Gas cost of the following opcode. + gas_cost: u64, + /// Program counter position. + pc: u64, + + memory: Memory, + stack: Stack, +} + +pub struct Listener { + pub results: Vec, + + tracer_config: sentio::SentioTracerConfig, + function_map: HashMap>, + call_map: HashMap>, + entry_pc: HashMap, + + previous_jump: Option, + index: i32, + + // Each external/internal call has element + call_stack: Vec, + // Each external call frame has element + context_stack: Vec, + + call_list_first_transaction: bool, + + // precompile_address: HashSet, + /// Version of the tracing. Not sure if we need in our tracer + /// Defaults to legacy, and switch to a more modern version if recently added events are + /// received. + version: TracingVersion, +} + +impl Listener { + pub fn new(config: sentio::SentioTracerConfig) -> Self { + let mut function_map: HashMap> = Default::default(); + let mut call_map: HashMap> = Default::default(); + + for (address_string, functions) in &config.functions { + let address = H160::from_str(&address_string).unwrap(); + let mut m: HashMap = Default::default(); + + for function in functions { + let mut new_func = function.clone(); + new_func.address = address; + m.insert(function.pc, new_func); + } + function_map.insert(address, m); + } + + for (address_string, calls) in &config.calls { + let address = H160::from_str(&address_string).unwrap(); + let mut m: HashMap = Default::default(); + for call in calls { + m.insert(*call, true); + } + call_map.insert(address, m); + } + log::info!( + "create sentioTracer with {} functions and {} calls", + function_map.len(), + call_map.len() + ); + + Self { + results: vec![], + tracer_config: config, + function_map, + call_map, + previous_jump: None, + index: 0, + entry_pc: Default::default(), + call_stack: vec![], + context_stack: vec![], + call_list_first_transaction: false, + version: TracingVersion::Legacy, + // https://docs.moonbeam.network/builders/pallets-precompiles/precompiles/overview/ + // precompile_address: (1..=4095).into_iter().map(H160::from_low_u64_be).collect() + } + } +} + +impl Listener { + pub fn using R>(&mut self, f: F) -> R { + evm_tracing_events::using(self, f) + } + + pub fn finish_transaction(&mut self) { + self.context_stack = vec![]; + + match self.version { + TracingVersion::Legacy => { + log::error!("legacy mode is not handle well"); + } + _ => {} + } + + if self.call_stack.len() != 1 { + panic!("call stack size is not 1, {}", self.call_stack.len()); + } + if self.context_stack.len() != 0 { + panic!("context stack size is not 0 {}", self.context_stack.len()); + } + + let mut root = self.call_stack.remove(0); + + if root.base.start_index == -1 { + root.base.start_index = 0; + } + + if self.tracer_config.debug { + root.base.tracer_config = + Some(serde_json::to_vec(&self.tracer_config).unwrap_or_default()); + } + self.results.push(root); + + self.index = 0; + self.previous_jump = None; + } + + // almost identical to raw + pub fn gasometer_event(&mut self, event: GasometerEvent) { + match event { + GasometerEvent::RecordCost { cost, snapshot } + | GasometerEvent::RecordDynamicCost { + gas_cost: cost, + snapshot, + .. + } => { + if let Some(context) = self.context_stack.last_mut() { + // Register opcode cost. (ignore costs not between Step and StepResult) + if let Some(step) = &mut context.current_step { + step.gas_cost = cost; + + if context.start_gas == 0 { + context.start_gas = snapshot.gas(); + if let Some(call) = self.call_stack.last_mut() { + call.base.gas = context.start_gas + } + } + } + context.gas = snapshot.gas(); + } + } + // GasometerEvent::RecordStipend { stipend, snapshot } => { + // if let Some(context) = self.context_stack.last_mut() { + // let gas = snapshot.gas(); + // log::warn!("stipend {}, {} found, not battle tested", stipend, gas); + // // TODO check why this work + // context.gas += gas; + // } + // } + // GasometerEvent::RecordTransaction { cost, snapshot } => { + // if let Some(call) = self.call_stack.last_mut() { + // call.base.gas_used = cost; + // } + // } + // We ignore other kinds of message if any (new ones may be added in the future). + #[allow(unreachable_patterns)] + _ => (), + } + } + + pub fn runtime_event(&mut self, event: RuntimeEvent) { + match event { + RuntimeEvent::Step { + context: _, + opcode, + position, + stack, + memory, + } => { + let op = to_opcode(&opcode); + + // Ignore steps outside of any context (shouldn't even be possible). + if let Some(context) = self.context_stack.last_mut() { + if self.tracer_config.debug { + let op_string = std::str::from_utf8(&opcode).unwrap(); + context.current_opcode = Some(op_string.to_string()); + } + context.current_step = Some(Step { + opcode: op, + gas_cost: 0, // 0 for now, will add with gas events (for all) + pc: *position.as_ref().unwrap_or(&0), + // TODO check if this safe or cost too much? + memory: memory.expect("memory data to not be filtered out"), + stack: stack.expect("stack data to not be filtered out"), + }); + } + } + RuntimeEvent::StepResult { + result, + return_value: _, + } => { + // StepResult is expected to be emited after a step (in a context). + // Only case StepResult will occur without a Step before is in a transfer + // transaction to a non-contract address. However it will not contain any + // steps and return an empty trace, so we can ignore this edge case. + 'outer: loop { + if let Some(context) = self.context_stack.last_mut() { + let code_address = context.code_address.unwrap_or(context.address); + + if let Some(current_step) = context.current_step.take() { + let Step { + opcode, + gas_cost, + pc, + memory, + stack, + } = current_step; + + self.index = self.index + 1; + + let mut base_trace = SentioBaseTrace { + tracer_config: None, + pc, + start_index: self.index - 1, + end_index: self.index, + op: opcode, + gas: context.gas, + gas_used: gas_cost, + error: None, + revert_reason: None, + }; + + base_trace.error = match &result { + Err(Capture::Exit(reason)) => { + let res = match &reason { + ExitReason::Error(error) => Some(error_message(error)), + ExitReason::Revert(_) => { + Some(b"execution reverted".to_vec()) + } + ExitReason::Fatal(_) => Some(vec![]), + _ => None, + }; + res + } + _ => None, + }; + + match opcode { + Opcode::CREATE + | Opcode::CREATE2 + | Opcode::CALL + | Opcode::CALLCODE + | Opcode::DELEGATECALL + | Opcode::STATICCALL + | Opcode::SUICIDE => { + let call_trace: SentioCallTrace = + SentioCallTrace::new(base_trace); + self.call_stack.push(call_trace) + } + Opcode::LOG0 + | Opcode::LOG1 + | Opcode::LOG2 + | Opcode::LOG3 + | Opcode::LOG4 => { + let topic_count = + (opcode.as_u8() - Opcode::LOG0.as_u8()) as u64; + let log_offset = stack_back(&stack, 0); + let log_size = stack_back(&stack, 1); + let data = copy_memory( + &memory, + log_offset.to_low_u64_be() as usize, + log_size.to_low_u64_be() as usize, + ); + let mut topics: Vec = Vec::new(); + for i in 0..topic_count { + topics.push(*stack_back(&stack, 2 + i)) + } + + let log_trace = SentioEventTrace { + base: base_trace, + log: sentio::Log { + address: context.address, + code_address, + topics, + data, + }, + }; + let last = self + .call_stack + .last_mut() + .expect("call stack should not be empty"); + last.traces.push(SentioTrace::EventTrace(log_trace)) + } + Opcode::JUMP if self.tracer_config.with_internal_calls => { + let mut jump = SentioCallTrace::new(base_trace); + jump.from = code_address; + + if self.previous_jump.is_some() { + log::error!("Unexpected previous jump {}", self.index) + } + self.previous_jump = Some(jump); + } + Opcode::JUMPDEST if self.tracer_config.with_internal_calls => { + // vm.JumpDest and match with a previous jump (otherwise it's a jumpi) + if let Some(mut previous_jump) = self.previous_jump.take() { + let root = &mut self.call_stack[0]; + if root.base.start_index == -1 + && *self.entry_pc.get(&pc).unwrap_or(&false) + { + root.base.pc = pc; + root.base.start_index = self.index - 1; + break 'outer; + } + + let stack_size = self.call_stack.len(); + + // Part 1: try process the trace as function call exit + for i in (0..stack_size).rev() { + // process internal call within the same contract + // no function info means another external call + let function_info = &self.call_stack[i].function; + let function_info = match function_info { + None => break, + Some(f) => f, + }; + + if function_info.address != code_address { + break; + } + + // find a match + if self.call_stack[i].exit_pc == pc { + // find a match, pop the stack, copy memory if needed + if stack_size - i > 1 { + log::info!( + "tail call optimization size {}", + stack_size - 1 + ) + } + + for j in (i..stack_size).rev() { + let mut element = self + .call_stack + .pop() + .expect("stack should have element"); + + let function_j = element + .function + .as_ref() + .expect("function should existed"); + + element.base.end_index = self.index - 1; + element.base.gas_used = + element.base.gas - context.gas; + if function_j.output_size as usize + > stack.data.len() + { + log::error!("stack size not enough ({} vs {}) for function {} {}. pc: {}", + stack.data.len(), function_j.output_size, function_j.address, function_j.name, pc) + } else { + element.output_stack = copy_stack( + &stack, + function_j.output_size as usize, + ); + } + if function_j.output_memory { + element.output_memory = + Some(format_memory(&memory.data)); + } + + self.call_stack[j - 1] + .traces + .push(SentioTrace::CallTrace(element)); + } + // self.previous_jump = None; + break 'outer; + } + } + + // Part 2: try process the trace as function call entry + // filter those jump are not call site + if let Some(function_info) = + self.get_function_info(code_address, pc) + { + if !self + .is_call(previous_jump.from, previous_jump.base.pc) + { + break 'outer; + } + + if function_info.input_size as usize > stack.data.len() + { + log::error!("Unexpected stack size for function: {:?}\nPrevious Jump: {:?}", function_info, previous_jump); + break 'outer; + } + + previous_jump.exit_pc = + stack_back(&stack, function_info.input_size) + .to_low_u64_be(); + previous_jump.function = Some(function_info.clone()); + previous_jump.function_pc = pc; + previous_jump.input_stack = copy_stack( + &stack, + function_info.input_size as usize, + ); + if self.tracer_config.debug { + previous_jump.name = + Some(function_info.name.clone()); + } + if function_info.input_memory { + previous_jump.input_memory = + Some(format_memory(&memory.data)) + } + self.call_stack.push(previous_jump) + } + } + } + Opcode::REVERT if self.tracer_config.with_internal_calls => { + let log_offset = stack_back(&stack, 0).to_low_u64_be() as usize; + let log_size = stack_back(&stack, 1).to_low_u64_be() as usize; + let output = &memory.data[log_offset..(log_offset + log_size)]; + + base_trace.error = Some(b"execution reverted".to_vec()); + base_trace.revert_reason = unpack_revert(&output); + let last = self + .call_stack + .last_mut() + .expect("call stack should not be empty"); + last.traces.push(SentioTrace::OtherTrace(base_trace)) + } + _ if self.tracer_config.with_internal_calls => { + if base_trace.error.is_some() { + let last = self + .call_stack + .last_mut() + .expect("call stack should not be empty"); + last.traces.push(SentioTrace::OtherTrace(base_trace)) + } + } + _ => {} + } + } + } + break; + } // outer loop + + // TODO update storage if needed + // // We match on the capture to handle traps/exits. + // match result { + // Err(Capture::Exit(reason)) => { // OP could be return & STOP & CALL (immediate revert) & any (oos) + // if let Some(context) = self.context_stack.pop() { + // let stack_size = self.call_stack.len(); + // for i in (0..stack_size).rev() { + // if self.call_stack[i].function.is_some() { + // continue; + // } + // + // if stack_size - i > 1 { + // log::info!("tail call optimization [external] size {}", stack_size - i); + // } + // + // let mut call = self.call_stack.get_mut(i).expect("call should exist"); + // call.process_error(&return_value, &reason); + // // let gas = call.base.gas - context.gas_used; + // self.pop_stack(i, &return_value, context.gas); + // return; + // } + // } + // } + // _ => (), + // } // match result + } + _ => {} + } + } + + fn create_root_trace(&mut self, from: H160, to: H160, op: Opcode, value: U256, data: Vec) { + if (op == Opcode::CALL || op == Opcode::CALLCODE) && data.len() >= 4 { + // also not precompile + if let Some(m) = self.function_map.get(&to) { + let sig_hash = format!("0x{}", hex::encode(&data[0..4])); + + for (pc, func) in m { + if func.signature_hash == sig_hash { + self.entry_pc.insert(*pc, true); + } + } + log::info!( + "entry pc match {} ({} times)", + sig_hash, + self.entry_pc.len() + ); + } + } + + let base_trace = SentioBaseTrace { + tracer_config: None, + op, + start_index: -1, + gas: 0, + pc: 0, + end_index: 0, + gas_used: 0, + error: None, + revert_reason: None, + }; + let call = SentioCallTrace { + base: base_trace, + from, + to: Some(to), + input: data, + value, + + name: None, + output: vec![], + traces: vec![], + input_stack: vec![], + input_memory: None, + output_stack: vec![], + output_memory: None, + function_pc: 0, + exit_pc: 0, + function: None, + }; + self.call_stack.push(call); + + // no need to push context stack since it's record in gas step + self.push_context_stack(to, None); + } + + fn push_context_stack(&mut self, address: H160, code_address: Option) { + self.context_stack.push(Context { + address, + code_address, + current_step: None, + current_opcode: None, + gas: 0, + start_gas: 0, + }); + } + + fn patch_call_trace( + &mut self, + code_address: H160, + transfer: Option, + input: Vec, + context: evm_tracing_events::Context, + ) { + let value = transfer.map(|t| t.value).unwrap_or_default(); + if self.call_stack.is_empty() { + // Legacy mode + self.create_root_trace(context.caller, context.address, Opcode::CALL, value, input); + } else if self.call_stack.len() > 1 { + // the first Call will happen after TransactCall and it's + let mut call = self.call_stack.last_mut().expect("not none"); + if call.function != None { + panic!("find internal call when setting external call trace") + } + call.from = context.caller; + call.to = Some(context.address); + call.input = input; + call.value = value; + + self.push_context_stack(context.address, Some(code_address)); + } + } + + pub fn evm_event(&mut self, event: EvmEvent) { + match event { + EvmEvent::TransactCall { + caller, + address, + value, + data, + .. + } => { + self.version = TracingVersion::EarlyTransact; + self.create_root_trace(caller, address, Opcode::CALL, value, data); + } + EvmEvent::TransactCreate { + caller, + value, + init_code, + address, + .. + } + | EvmEvent::TransactCreate2 { + caller, + value, + init_code, + address, + .. + } => { + self.version = TracingVersion::EarlyTransact; + self.create_root_trace(caller, address, Opcode::CREATE, value, init_code); + } + EvmEvent::Call { + code_address, + transfer, + input, + context, + .. + } => { + self.patch_call_trace(code_address, transfer, input, context); + } + EvmEvent::PrecompileSubcall { + code_address, + transfer, + input, + context, + .. + } => { + self.patch_call_trace(code_address, transfer, input, context); + log::warn!("precompiled call found") + } + EvmEvent::Create { + caller, + address, + value, + init_code, + .. + } => { + if self.call_stack.is_empty() { + // Legacy mode + self.create_root_trace(caller, address, Opcode::CREATE, value, init_code); + } else if self.call_stack.len() > 1 { + let mut call = self.call_stack.last_mut().expect("not none"); + + if call.function != None { + panic!("find internal call when setting external call trace") + } + call.from = caller; + call.to = Some(address); + call.input = init_code; + call.value = value; + + self.push_context_stack(address, None); + } + } + EvmEvent::Suicide { address, .. } => { + self.push_context_stack(address, None); + } + EvmEvent::Exit { + reason, + return_value, + } => { + // We match on the capture to handle traps/exits. + // match result { + // Err(Capture::Exit(reason)) => { // OP could be return & STOP & CALL (immediate revert) & any (oos) + if let Some(context) = self.context_stack.pop() { + let used_gas = context.used_gas(); + if let Some(previous_context) = self.context_stack.last_mut() { + // For early exit like OOS there no chance to update previous context + previous_context.gas -= used_gas; + } + + let stack_size = self.call_stack.len(); + for i in (0..stack_size).rev() { + if self.call_stack[i].function.is_some() { + continue; + } + + if stack_size - i > 1 { + log::info!("tail call optimization [external] size {}", stack_size - i); + } + + let call = self.call_stack.get_mut(i).expect("call should exist"); + call.base.gas = context.start_gas; // exclude the op cost itself + call.process_error(&return_value, &reason); + // let gas = call.base.gas - context.gas_used; + self.pop_call_stack(i, &return_value, context.start_gas - used_gas); + return; + } + } + } + } + } + + fn pop_call_stack(&mut self, to: usize, output: &Vec, gas_left: u64) { + let stack_size = self.call_stack.len(); + for _ in to..stack_size { + let mut call = self.call_stack.pop().expect("not null"); + call.output = output.clone(); + call.base.end_index = self.index; + call.base.gas_used = call.base.gas - gas_left; + + match self.call_stack.last_mut() { + Some(peek) => { + peek.traces.push(SentioTrace::CallTrace(call)); + } + None => { + // keep the root to process in finish transaction + self.call_stack.push(call) + } + } + } + } + + fn get_function_info(&self, address: H160, pc: u64) -> Option<&FunctionInfo> { + match self.function_map.get(&address) { + Some(m) => m.get(&pc), + None => None, + } + } + + fn is_call(&self, address: H160, pc: u64) -> bool { + match self.call_map.get(&address) { + Some(m) => *m.get(&pc).unwrap_or(&false), + None => false, + } + } +} + +fn error_message(error: &ExitError) -> Vec { + match error { + ExitError::StackUnderflow => "stack underflow", + ExitError::StackOverflow => "stack overflow", + ExitError::InvalidJump => "invalid jump", + ExitError::InvalidRange => "invalid range", + ExitError::DesignatedInvalid => "designated invalid", + ExitError::CallTooDeep => "call too deep", + ExitError::CreateCollision => "create collision", + ExitError::CreateContractLimit => "create contract limit", + ExitError::OutOfOffset => "out of offset", + ExitError::OutOfGas => "out of gas", + ExitError::OutOfFund => "out of funds", + ExitError::Other(err) => err, + _ => "unexpected error", + } + .as_bytes() + .to_vec() +} + +impl ListenerT for Listener { + fn event(&mut self, event: Event) { + match event { + Event::Gasometer(gasometer_event) => self.gasometer_event(gasometer_event), + Event::Runtime(runtime_event) => self.runtime_event(runtime_event), + Event::Evm(evm_event) => self.evm_event(evm_event), + Event::CallListNew() => { + if !self.call_list_first_transaction { + self.finish_transaction(); + } else { + self.call_list_first_transaction = false; + } + } + }; + } + + fn step_event_filter(&self) -> StepEventFilter { + StepEventFilter { + enable_memory: true, + enable_stack: true, + } + } +} + +impl SentioCallTrace { + fn process_error(&mut self, output: &Vec, reason: &ExitReason) { + let (error, revert_reason) = match reason { + ExitReason::Revert(_) => (Some(b"execution reverted".to_vec()), unpack_revert(output)), + ExitReason::Fatal(_) => { + log::error!("unexpected fatal"); + (Some(vec![]), None) + } + ExitReason::Error(error) => (Some(error_message(error)), None), + ExitReason::Succeed(_) => (None, None), + }; + if error.is_none() { + return; + } + if self.base.op == Opcode::CREATE || self.base.op == Opcode::CREATE2 { + self.to = None; + } + self.base.error = error; + self.base.revert_reason = revert_reason; + } +} diff --git a/vendor/evm-tracing/src/listeners/sentio_prestate.rs b/vendor/evm-tracing/src/listeners/sentio_prestate.rs new file mode 100644 index 0000000000..f3b37c38a9 --- /dev/null +++ b/vendor/evm-tracing/src/listeners/sentio_prestate.rs @@ -0,0 +1,456 @@ +// Copyright 2019-2022 PureStake Inc. +// This file is part of Moonbeam. + +// Moonbeam is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Moonbeam is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Moonbeam. If not, see . + +use std::collections::BTreeSet; +use ethereum_types::{BigEndianHash, H160, H256, U256}; +use evm_tracing_events::{Event, EvmEvent, Listener as ListenerT, RuntimeEvent, StepEventFilter}; +use evm_tracing_events::runtime::{Memory, Opcode, Stack}; +use std::ops::Deref; +use fp_rpc::EthereumRuntimeRPCApi; +use sha3::{Digest, Keccak256}; +use sp_api::{ApiRef, ProvideRuntimeApi}; +use sp_block_builder::BlockBuilder; +use sp_runtime::traits::Block as BlockT; +use rlp::RlpStream; + +use crate::listeners::sentio_util::{copy_memory, stack_back, to_opcode}; +use crate::types::sentio::{State, SentioPrestateTrace, SentioPrestateTracerConfig, Account}; + +#[derive(Debug, Clone)] +struct Step { + /// Current opcode. + opcode: Opcode, + memory: Memory, + stack: Stack, +} + +#[derive(Debug)] +struct Context { + address: H160, + code_address: Option, + current_step: Option, + current_opcode: Option, +} + +pub struct Listener + where + B: BlockT, + C: ProvideRuntimeApi + 'static, + C::Api: EthereumRuntimeRPCApi, + C::Api: BlockBuilder, +{ + pub results: Vec, + + tracer_config: SentioPrestateTracerConfig, + gas_limit: u64, + pre: State, + post: State, + create: bool, + created: BTreeSet, + deleted: BTreeSet, + + context_stack: Vec, + + api: *const C::Api, + beneficiary: H160, + extrinsics: Vec, + parent_block: B::Hash, + + call_list_first_transaction: bool, +} + +impl Listener + where + B: BlockT, + C: ProvideRuntimeApi, + C::Api: EthereumRuntimeRPCApi, + C::Api: BlockBuilder, +{ + pub fn new(config: SentioPrestateTracerConfig, parent_block: B::Hash, extrinsics: Vec, beneficiary: H160, api: &ApiRef) -> Self { + Self { + results: vec![], + tracer_config: config, + gas_limit: 0, + pre: Default::default(), + post: Default::default(), + create: false, + created: Default::default(), + deleted: Default::default(), + call_list_first_transaction: false, + parent_block, + extrinsics, + api: api.deref(), + beneficiary, + context_stack: vec![], + } + } + + pub fn using R>(&mut self, f: F) -> R { + evm_tracing_events::using(self, f) + } + + pub fn finish_transaction(&mut self) { + let last_extrinsic = self.extrinsics.pop().expect("has last"); + + if self.tracer_config.diff_mode { + let api = unsafe { &*self.api }; + let _ = api.apply_extrinsic(self.parent_block, last_extrinsic); + + let mut pre_to_be_deleted: Vec = vec![]; + // if t.create { // probably don't need in this impl + // // Keep existing account prior to contract creation at that address + // if s := t.pre[t.to]; s != nil && !s.exists() { + // // Exclude newly created contract. + // delete(t.pre, t.to) + // } + // } + for (addr, pre_account) in self.pre.iter_mut() { + let addr = *addr; + // The deleted account's state is pruned from `post` but kept in `pre` + if self.deleted.contains(&addr) { + continue; + } + let mut modified = false; + let basic = api.account_basic(self.parent_block, addr).ok(); + + let mut post_account = Account { + balance: None, + code: vec![], + nonce: None, + storage: Default::default(), + code_address: None, + mapping_keys: Default::default(), + }; + let new_balance = basic.clone().map(|x| x.balance); + let new_nonce = basic.map(|x| x.nonce); + let new_code = api.account_code_at(self.parent_block, addr).unwrap_or_default(); + + if new_balance != pre_account.balance { + modified = true; + post_account.balance = new_balance; + } + + if new_nonce != pre_account.nonce { + modified = true; + post_account.nonce = new_nonce; + } + + if new_code != pre_account.code { + modified = true; + post_account.code = new_code; + } + + let mut to_be_removed: Vec = vec![]; + for (key, val) in &pre_account.storage { + // don't include the empty slot + if *val == H256::default() { + to_be_removed.push(*key); + } + + let new_val = api.storage_at(self.parent_block, addr, key.into_uint()).expect("has storage value"); + if *val == new_val { + to_be_removed.push(*key); + } else { + modified = true; + if new_val != H256::zero() { + post_account.storage.insert(*key, new_val); + } + } + } + for key in to_be_removed { + pre_account.storage.remove(&key); + } + + if modified { + self.post.insert(addr, post_account); + } else { + // if state is not modified, then no need to include into the pre state + pre_to_be_deleted.push(addr) + } + } + + // the new created contracts' prestate were empty, so delete them + for a in &self.created { + // the created contract maybe exists in statedb before the creating tx + let s = self.pre.get(&a); + if let Some(s) = s { + if !account_existed(s) { + pre_to_be_deleted.push(*a) + } + } + } + + for addr in pre_to_be_deleted { + self.pre.remove(&addr); + } + } + + let mut res = SentioPrestateTrace { + pre: Default::default(), + post: Default::default(), + }; + + core::mem::swap(&mut res.pre, &mut self.pre); + core::mem::swap(&mut res.post, &mut self.post); + self.created.clear(); + self.deleted.clear(); + self.gas_limit = 0; + self.create = false; + self.results.push(res); + } + + fn push_stack(&mut self, address: H160, code_address: Option) { + self.context_stack.push(Context { + address: address, + code_address: code_address, + current_step: None, + current_opcode: None, + }); + } + + pub fn evm_event(&mut self, event: EvmEvent) { + match &event { + EvmEvent::TransactCreate { gas_limit, caller, address, .. } | + EvmEvent::TransactCreate2 { gas_limit, caller, address, .. } | + EvmEvent::TransactCall { gas_limit, caller, address, .. } => { + let mut exts: Vec = vec![]; + core::mem::swap(&mut exts, &mut self.extrinsics); + exts.pop().map(|ext| self.extrinsics.push(ext)); + let api = unsafe { &*self.api }; + for e in exts { + let _ = api.apply_extrinsic(self.parent_block, e); + } + + self.gas_limit = *gas_limit; + self.lookup_account(*caller); + self.lookup_account(*address); + self.lookup_account(self.beneficiary); + } + _ => {} + } + + match &event { + EvmEvent::TransactCreate { address, .. } + | EvmEvent::TransactCreate2 { address, .. } => { + self.create = true; + if self.tracer_config.diff_mode { + self.created.insert(*address); + } + } + _ => {} + } + + match &event { + EvmEvent::TransactCreate { address, .. } | + EvmEvent::TransactCreate2 { address, .. } | + EvmEvent::TransactCall { address, .. } | + EvmEvent::Create { address, .. } | + EvmEvent::Suicide { address, .. } => { + self.push_stack(*address, None); + } + EvmEvent::Call { code_address, context, .. } | + EvmEvent::PrecompileSubcall { code_address, context, .. } => { + self.push_stack(context.address, Some(*code_address)); + } + EvmEvent::Exit { .. } => { + self.context_stack.pop(); + } + } + } + + pub fn runtime_event(&mut self, event: RuntimeEvent) { + match event { + RuntimeEvent::Step { opcode, stack, memory, .. } => { + let op = to_opcode(&opcode); + if let Some(context) = self.context_stack.last_mut() { + if self.tracer_config.debug { + let op_string = std::str::from_utf8(&opcode).unwrap(); + context.current_opcode = Some(op_string.to_string()); + } + context.current_step = Some(Step { + opcode: op, + memory: memory.expect("memory data to not be filtered out"), + stack: stack.expect("stack data to not be filtered out"), + }); + } + } + RuntimeEvent::StepResult { .. } => { + if let Some(context) = self.context_stack.last_mut() { + if let Some(current_step) = context.current_step.take() { + let Step { + opcode, + memory, + stack, + } = current_step; + + let stack_size = stack.data.len(); + + match opcode { + Opcode::SHA3 if stack_size >= 2 => { + let size = stack_back(&stack, 1).to_low_u64_be(); + if size == 64 { + let offset = stack_back(&stack, 0).to_low_u64_be(); + let raw_key = copy_memory(&memory, offset as usize, size as usize); + let digest = Keccak256::digest(&raw_key.as_slice()); + let hash_of_key = H256::from_slice(&digest); + // let key = H512::from_slice(raw_key.as_slice()); + let key= format!("{}", hex::encode(raw_key)); + let account = self.pre.get_mut(&context.address).expect("account should existed"); + account.mapping_keys.insert(key, hash_of_key); + } + } + Opcode::SLOAD | Opcode::SSTORE if stack_size >= 1 => { + // panic!("duplicate sload / sstore"); + } + Opcode::EXTCODECOPY | Opcode::EXTCODEHASH | Opcode::EXTCODESIZE | Opcode::BALANCE if stack_size >= 1 => { + let addr_raw = stack_back(&stack, 0); + let addr = H160::from(*addr_raw); + self.lookup_account(addr); + } + Opcode::SUICIDE if stack_size >= 1 => { + let caller = context.address; // not in new context yet + let addr_raw = stack_back(&stack, 0); + let addr = H160::from(*addr_raw); + self.lookup_account(addr); + self.deleted.insert(caller); + } + Opcode::DELEGATECALL | Opcode::CALL | Opcode::STATICCALL | Opcode::CALLCODE if stack_size >= 5 => { + let addr_raw = stack_back(&stack, 1); + let addr = H160::from(*addr_raw); + self.lookup_account(addr); + } + Opcode::CREATE => { + let caller = context.address.as_bytes().to_vec(); // not in new context yet + let api = unsafe { &*self.api }; + let nonce = api.account_basic(self.parent_block, context.address) + .map(|x| x.nonce) + .unwrap_or_default(); + let mut stream = RlpStream::new(); + stream.begin_list(2); + stream.append(&caller); + stream.append(&nonce); + let data = H256::from_slice(Keccak256::digest(&stream.out()).as_slice()); + let addr = H160::from(data); + self.lookup_account(addr); + self.created.insert(addr); + } + Opcode::CREATE2 if stack_size >= 4 => { + let caller = context.address.as_bytes().to_vec(); // not in new context yet + let offset = stack_back(&stack, 1).to_low_u64_be(); + let size = stack_back(&stack, 2).to_low_u64_be(); + let init = copy_memory(&memory, offset as usize, size as usize); + let init_hash = Keccak256::digest(init).to_vec(); + let salt = stack_back(&stack, 3).as_bytes().to_vec(); + let mut stream = RlpStream::new(); + stream.begin_list(4); + stream.append(&vec![0xff]); + stream.append(&caller); + stream.append(&salt); + stream.append(&init_hash); + let data = H256::from_slice(Keccak256::digest(&stream.out()).as_slice()); + let addr = H160::from(data); + self.lookup_account(addr); + self.created.insert(addr); + } + _ => {} + } + } + } + } + RuntimeEvent::SLoad { address, index, value: _ } | + RuntimeEvent::SStore { address, index, value: _ } => { + if let Some(context) = self.context_stack.last_mut() { + if let Some(account) = self.pre.get_mut(&address) { + account.code_address = context.code_address.or(Some(context.address)); + } + self.lookup_storage(address, index); + } + } + } + } + + // lookupAccount fetches details of an account and adds it to the prestate + // if it doesn't exist there. + fn lookup_account(&mut self, address: H160) { + if self.pre.contains_key(&address) { + return; + } + + let api = unsafe { &*self.api }; + let basic = api.account_basic(self.parent_block, address).ok(); + let code = api.account_code_at(self.parent_block, address).unwrap_or_default(); + let account: Account = Account { + balance: basic.clone().map(|b| b.balance), + nonce: basic.map(|b| b.nonce), + code, + storage: Default::default(), + code_address: None, + mapping_keys: Default::default(), + }; + self.pre.insert(address, account); + } + + // lookupStorage fetches the requested storage slot and adds + // it to the prestate of the given contract. It assumes `lookupAccount` + // has been performed on the contract before. + fn lookup_storage(&mut self, address: H160, key: H256) { + let account = self.pre.get_mut(&address).expect("account should already be looked at"); + if account.storage.contains_key(&key) { + return; + } + let api = unsafe { &*self.api }; + let value = api + .storage_at(self.parent_block, address, key.into_uint()) + .unwrap(); + account.storage.insert(key, value); + } +} + +impl ListenerT for Listener + where + B: BlockT, + C: ProvideRuntimeApi + 'static, + C::Api: EthereumRuntimeRPCApi, + C::Api: BlockBuilder, +{ + fn event(&mut self, event: Event) { + match event { + Event::Runtime(runtime_event) => self.runtime_event(runtime_event), + Event::Evm(evm_event) => self.evm_event(evm_event), + Event::CallListNew() => { + if !self.call_list_first_transaction { + self.finish_transaction(); + } else { + self.call_list_first_transaction = false; + } + } + _ => {} + }; + } + + fn step_event_filter(&self) -> StepEventFilter { + StepEventFilter { + enable_memory: true, + enable_stack: true, + } + } +} + +fn account_existed(a: &Account) -> bool { + return a.nonce.unwrap_or_default() > U256::zero() || + a.storage.len() > 0 || + a.balance != Some(U256::zero()); +} diff --git a/vendor/evm-tracing/src/listeners/sentio_util.rs b/vendor/evm-tracing/src/listeners/sentio_util.rs new file mode 100644 index 0000000000..97ff24d970 --- /dev/null +++ b/vendor/evm-tracing/src/listeners/sentio_util.rs @@ -0,0 +1,274 @@ +use ethereum_types::{H256, U256}; +use sha3::{Digest, Keccak256}; +use evm_tracing_events::runtime::{Memory, Opcode, Stack}; + +pub fn stack_back(stack: &Stack, n: u64) -> &H256 { + return stack.data.get(stack.data.len() - (n as usize) - 1).expect("stack shouldn't be empty"); +} + +pub fn copy_stack(stack: &Stack, copy_size: usize) -> Vec { + let stack_size = stack.data.len(); + let mut res: Vec = vec![U256::zero(); stack_size - copy_size]; + + for i in (stack_size - copy_size)..stack_size { + res.push(U256::from(stack.data[i].as_bytes())); + } + return res; +} + +pub fn copy_memory(memory: &Memory, offset: usize, size: usize) -> Vec { + if memory.data.len() > offset { + let mut end = offset + size; + if end > memory.data.len() { + end = memory.data.len() + } + return Vec::from_iter(memory.data[offset..end].iter().cloned()); + } + return Vec::default(); +} + +// copied from type but change memory to reference +pub fn format_memory(memory: &Vec) -> Vec { + let size = 32; + memory + .chunks(size) + .map(|c| { + let mut msg = [0u8; 32]; + let chunk = c.len(); + if chunk < size { + let left = size - chunk; + let remainder = vec![0; left]; + msg[0..left].copy_from_slice(&remainder[..]); + msg[left..size].copy_from_slice(c); + } else { + msg[0..size].copy_from_slice(c) + } + H256::from_slice(&msg[..]) + }) + .collect() +} + +pub fn to_opcode(opcode: &Vec) -> Opcode { + let op_string = std::str::from_utf8(&opcode).unwrap(); + let out = match op_string.as_ref() { + "Stop" => Opcode(0), + "Add" => Opcode(1), + "Mul" => Opcode(2), + "Sub" => Opcode(3), + "Div" => Opcode(4), + "SDiv" => Opcode(5), + "Mod" => Opcode(6), + "SMod" => Opcode(7), + "AddMod" => Opcode(8), + "MulMod" => Opcode(9), + "Exp" => Opcode(10), + "SignExtend" => Opcode(11), + "Lt" => Opcode(16), + "Gt" => Opcode(17), + "Slt" => Opcode(18), + "Sgt" => Opcode(19), + "Eq" => Opcode(20), + "IsZero" => Opcode(21), + "And" => Opcode(22), + "Or" => Opcode(23), + "Xor" => Opcode(24), + "Not" => Opcode(25), + "Byte" => Opcode(26), + "Shl" => Opcode(27), + "Shr" => Opcode(28), + "Sar" => Opcode(29), + "Keccak256" => Opcode(32), + "Address" => Opcode(48), + "Balance" => Opcode(49), + "Origin" => Opcode(50), + "Caller" => Opcode(51), + "CallValue" => Opcode(52), + "CallDataLoad" => Opcode(53), + "CallDataSize" => Opcode(54), + "CallDataCopy" => Opcode(55), + "CodeSize" => Opcode(56), + "CodeCopy" => Opcode(57), + "GasPrice" => Opcode(58), + "ExtCodeSize" => Opcode(59), + "ExtCodeCopy" => Opcode(60), + "ReturnDataSize" => Opcode(61), + "ReturnDataCopy" => Opcode(62), + "ExtCodeHash" => Opcode(63), + "BlockHash" => Opcode(64), + "Coinbase" => Opcode(65), + "Timestamp" => Opcode(66), + "Number" => Opcode(67), + "Difficulty" => Opcode(68), + "GasLimit" => Opcode(69), + "ChainId" => Opcode(70), + "Pop" => Opcode(80), + "MLoad" => Opcode(81), + "MStore" => Opcode(82), + "MStore8" => Opcode(83), + "SLoad" => Opcode(84), + "SStore" => Opcode(85), + "Jump" => Opcode(86), + "JumpI" => Opcode(87), + "GetPc" => Opcode(88), + "MSize" => Opcode(89), + "Gas" => Opcode(90), + "JumpDest" => Opcode(91), + "Push1" => Opcode(96), + "Push2" => Opcode(97), + "Push3" => Opcode(98), + "Push4" => Opcode(99), + "Push5" => Opcode(100), + "Push6" => Opcode(101), + "Push7" => Opcode(102), + "Push8" => Opcode(103), + "Push9" => Opcode(104), + "Push10" => Opcode(105), + "Push11" => Opcode(106), + "Push12" => Opcode(107), + "Push13" => Opcode(108), + "Push14" => Opcode(109), + "Push15" => Opcode(110), + "Push16" => Opcode(111), + "Push17" => Opcode(112), + "Push18" => Opcode(113), + "Push19" => Opcode(114), + "Push20" => Opcode(115), + "Push21" => Opcode(116), + "Push22" => Opcode(117), + "Push23" => Opcode(118), + "Push24" => Opcode(119), + "Push25" => Opcode(120), + "Push26" => Opcode(121), + "Push27" => Opcode(122), + "Push28" => Opcode(123), + "Push29" => Opcode(124), + "Push30" => Opcode(125), + "Push31" => Opcode(126), + "Push32" => Opcode(127), + "Dup1" => Opcode(128), + "Dup2" => Opcode(129), + "Dup3" => Opcode(130), + "Dup4" => Opcode(131), + "Dup5" => Opcode(132), + "Dup6" => Opcode(133), + "Dup7" => Opcode(134), + "Dup8" => Opcode(135), + "Dup9" => Opcode(136), + "Dup10" => Opcode(137), + "Dup11" => Opcode(138), + "Dup12" => Opcode(139), + "Dup13" => Opcode(140), + "Dup14" => Opcode(141), + "Dup15" => Opcode(142), + "Dup16" => Opcode(143), + "Swap1" => Opcode(144), + "Swap2" => Opcode(145), + "Swap3" => Opcode(146), + "Swap4" => Opcode(147), + "Swap5" => Opcode(148), + "Swap6" => Opcode(149), + "Swap7" => Opcode(150), + "Swap8" => Opcode(151), + "Swap9" => Opcode(152), + "Swap10" => Opcode(153), + "Swap11" => Opcode(154), + "Swap12" => Opcode(155), + "Swap13" => Opcode(156), + "Swap14" => Opcode(157), + "Swap15" => Opcode(158), + "Swap16" => Opcode(159), + "Log0" => Opcode(160), + "Log1" => Opcode(161), + "Log2" => Opcode(162), + "Log3" => Opcode(163), + "Log4" => Opcode(164), + "JumpTo" => Opcode(176), + "JumpIf" => Opcode(177), + "JumpSub" => Opcode(178), + "JumpSubv" => Opcode(180), + "BeginSub" => Opcode(181), + "BeginData" => Opcode(182), + "ReturnSub" => Opcode(184), + "PutLocal" => Opcode(185), + "GetLocal" => Opcode(186), + "SLoadBytes" => Opcode(225), + "SStoreBytes" => Opcode(226), + "SSize" => Opcode(227), + "Create" => Opcode(240), + "Call" => Opcode(241), + "CallCode" => Opcode(242), + "Return" => Opcode(243), + "DelegateCall" => Opcode(244), + "Create2" => Opcode(245), + "StaticCall" => Opcode(250), + "TxExecGas" => Opcode(252), + "Revert" => Opcode(253), + "Invalid" => Opcode(254), + "SelfDestruct" => Opcode(255), + _ => Opcode(0) + }; + return out; +} + +// UnpackRevert resolves the abi-encoded revert reason. According to the solidity +// spec https://solidity.readthedocs.io/en/latest/control-structures.html#revert, +// the provided revert reason is abi-encoded as if it were a call to a function +// `Error(string)`. So it's a special tool for it. +pub fn unpack_revert(output: &[u8]) -> Option> { + if output.len() < 4 { + return None; + } + let revert_selector = &Keccak256::digest("Error(string)")[0..4]; + if output[0..4] != *revert_selector { + return None; + } + let data = &output[4..]; + if let Some((start, length)) = length_prefix_points_to(0, data) { + let bytes = data[start..start + length].to_vec(); + // let reason = std::str::from_utf8(&bytes).unwrap(); + return Some(bytes); + } + return None; +} + +fn length_prefix_points_to(index: usize, output: &[u8]) -> Option<(usize, usize)> { + let mut big_offset_end = U256::from(&output[index..index + 32]); + big_offset_end = big_offset_end + U256::from(32); + let output_length = U256::from(output.len()); + + if big_offset_end > output_length { + log::error!("abi: cannot marshal in to go slice: offset {} would go over slice boundary (len={})", big_offset_end, output_length); + return None; + } + + if big_offset_end.bits() > 63 { + log::error!("abi offset larger than int64: {}", big_offset_end); + return None; + } + + let offset_end = big_offset_end.as_u64() as usize; + let length_big = U256::from(&output[offset_end - 32..offset_end]); + + let total_size = big_offset_end + length_big; + if total_size.bits() > 63 { + log::error!("abi: length larger than int64: {}", total_size); + return None; + } + + if total_size > output_length { + log::error!("abi: cannot marshal in to go type: length insufficient {} require {}", output_length, total_size); + return None; + } + + let start = big_offset_end.as_u64() as usize; + let length = length_big.as_u64() as usize; + return Some((start, length)); +} + +#[test] +fn test_unpack_revert() { + let output_hex = "08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002f416e7973776170563345524332303a207472616e7366657220616d6f756e7420657863656564732062616c616e63650000000000000000000000000000000000"; + assert_eq!(unpack_revert(&hex::decode(output_hex).unwrap()), Some(b"AnyswapV3ERC20: transfer amount exceeds balance".to_vec())); + assert_eq!(unpack_revert(&hex::decode("08c379a1").unwrap()), None); + assert_eq!(unpack_revert(&hex::decode("").unwrap()), None); +} diff --git a/vendor/evm-tracing/src/types/mod.rs b/vendor/evm-tracing/src/types/mod.rs index f2099cbb55..c574010b75 100644 --- a/vendor/evm-tracing/src/types/mod.rs +++ b/vendor/evm-tracing/src/types/mod.rs @@ -25,6 +25,7 @@ use sp_std::vec::Vec; pub mod block; pub mod serialization; pub mod single; +pub mod sentio; use serde::Serialize; use serialization::*; diff --git a/vendor/evm-tracing/src/types/sentio.rs b/vendor/evm-tracing/src/types/sentio.rs new file mode 100644 index 0000000000..a00068eac3 --- /dev/null +++ b/vendor/evm-tracing/src/types/sentio.rs @@ -0,0 +1,271 @@ +use std::collections::BTreeMap; +use crate::types::serialization::*; + +use ethereum_types::{H160, H256, U256}; +use parity_scale_codec::{Decode, Encode}; +use serde::{Serialize, Deserialize, Serializer}; +use serde::ser::Error; +use serde_json::Value; +use evm_tracing_events::runtime::{Opcode, opcodes_string}; + +#[derive(Clone, Eq, PartialEq, Debug, Default, Encode, Decode, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SentioTracerConfig { + #[serde(default)] + pub functions: BTreeMap>, + + #[serde(default)] + pub calls: BTreeMap>, + + #[serde(default)] + pub debug: bool, + + #[serde(default)] + pub with_internal_calls: bool, +} + +#[derive(Clone, Eq, PartialEq, Debug, Default, Encode, Decode, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct FunctionInfo { + #[serde(default)] + pub address: H160, + pub name: String, + pub signature_hash: String, + pub pc: u64, + pub input_size: u64, + pub input_memory: bool, + pub output_size: u64, + pub output_memory: bool, +} + +#[derive(Clone, Eq, PartialEq, Debug, Encode, Decode, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SentioBaseTrace { + // Only in debug mode, TODO p3, make it vec and have it serialize to json + #[serde( + skip_serializing_if = "Option::is_none", + serialize_with = "json_serialize" + )] + pub tracer_config: Option>, + + #[serde(rename = "type", serialize_with = "original_opcode_serialize")] + pub op: Opcode, + pub pc: u64, + pub start_index: i32, + pub end_index: i32, + + #[serde(serialize_with = "u64_serialize")] + pub gas: u64, + #[serde(serialize_with = "u64_serialize")] + pub gas_used: u64, + + #[serde( + skip_serializing_if = "Option::is_none", + serialize_with = "option_string_serialize" + )] + pub error: Option>, + + #[serde( + skip_serializing_if = "Option::is_none", + serialize_with = "option_string_serialize" + )] + pub revert_reason: Option>, +} + +#[derive(Clone, Eq, PartialEq, Debug, Encode, Decode, Serialize)] +#[serde(rename_all = "camelCase", untagged)] +pub enum SentioTrace { + EventTrace(SentioEventTrace), + CallTrace(SentioCallTrace), + OtherTrace(SentioBaseTrace), +} + +#[derive(Clone, Eq, PartialEq, Debug, Encode, Decode, Serialize)] +pub struct Log { + pub address: H160, + pub code_address: H160, + pub topics: Vec, + #[serde(serialize_with = "bytes_0x_serialize")] + pub data: Vec, +} + +#[derive(Clone, Eq, PartialEq, Debug, Encode, Decode, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SentioEventTrace { + #[serde(flatten)] + pub base: SentioBaseTrace, + + #[serde(flatten)] + pub log: Log, +} + +#[derive(Clone, Eq, PartialEq, Debug, Encode, Decode, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SentioCallTrace { + #[serde(flatten)] + pub base: SentioBaseTrace, + pub traces: Vec, + pub from: H160, + #[serde(serialize_with = "bytes_0x_serialize")] + pub output: Vec, + + // for external call + #[serde(skip_serializing_if = "Option::is_none")] + pub to: Option, + #[serde(serialize_with = "bytes_0x_serialize")] + pub input: Vec, + pub value: U256,// TODO use some + + // for internal trace + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + // TODO remove trailing zero + #[serde(skip_serializing_if = "Vec::is_empty")] + pub input_stack: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pub input_memory: Option>, + #[serde(skip_serializing_if = "Vec::is_empty")] + pub output_stack: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pub output_memory: Option>, + #[serde(skip_serializing_if = "is_zero")] + pub function_pc: u64, + #[serde(skip)] + #[codec(skip)] + pub exit_pc: u64, + #[codec(skip)] + #[serde(skip)] + pub function: Option, +} + +impl SentioCallTrace { + pub fn new(base_trace: SentioBaseTrace) -> Self { + return SentioCallTrace { + base: base_trace, + traces: vec![], + from: Default::default(), + output: vec![], + to: None, + input: vec![], + value: Default::default(), + name: None, + input_stack: vec![], + input_memory: None, + output_stack: vec![], + output_memory: None, + function_pc: 0, + exit_pc: 0, + function: None, + }; + } +} + +#[derive(Clone, Eq, PartialEq, Default, Debug, Encode, Decode, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SentioPrestateTracerConfig { + #[serde(default)] + pub diff_mode: bool, + + #[serde(default)] + pub debug: bool, +} + +#[derive(Clone, Eq, PartialEq, Debug, Encode, Decode, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Account { + #[serde(skip_serializing_if = "Option::is_none")] + pub balance: Option, + + #[serde( + skip_serializing_if = "Vec::is_empty", + serialize_with = "bytes_0x_serialize" + )] + pub code: Vec, + + #[serde( + skip_serializing_if = "Option::is_none", + serialize_with = "option_u256_serialize" + )] + pub nonce: Option, + + #[serde(skip_serializing_if = "BTreeMap::is_empty")] + pub storage: BTreeMap, + + #[serde(skip_serializing_if = "Option::is_none")] + pub code_address: Option, + + #[serde(skip_serializing_if = "BTreeMap::is_empty")] + pub mapping_keys: BTreeMap, +} + +pub type State = BTreeMap; + +#[derive(Clone, Eq, PartialEq, Debug, Encode, Decode, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SentioPrestateTrace { + #[serde(skip_serializing_if = "BTreeMap::is_empty")] + pub pre: State, + #[serde(skip_serializing_if = "BTreeMap::is_empty")] + pub post: State, +} + +pub fn option_u256_serialize(data: &Option, serializer: S) -> Result + where + S: Serializer, +{ + serializer.serialize_u64(data.unwrap_or_default().low_u64()) +} + + +fn u64_serialize(data: &u64, serializer: S) -> Result + where + S: Serializer, +{ + serializer.serialize_str(&format!("0x{:x}", *data)) +} + +fn original_opcode_serialize(data: &Opcode, serializer: S) -> Result + where + S: Serializer, +{ + let bytes = opcodes_string(*data); + return opcode_serialize(&bytes, serializer); +} + +fn json_serialize(data: &Option>, serializer: S) -> Result + where + S: Serializer, +{ + if let Some(d) = data { + let v: Value = serde_json::from_slice(d).unwrap(); + return v.serialize(serializer); + } + return Err(S::Error::custom("serialize error.")); +} + +fn is_zero(n: &u64) -> bool { + return *n == 0; +} + +#[test] +fn test_tracer_config_parse() { + let config_string = "{\n \"calls\": {\n \"0x18dd7bca62deee6f633221de26096fdd0c734daa\": [\n 79\n ],\n \"0x3773e1e9deb273fcdf9f80bc88bb387b1e6ce34d\": [\n 2959\n ]\n },\n \"debug\": true,\n \"functions\": {\n \"0x18dd7bca62deee6f633221de26096fdd0c734daa\": [\n {\n \"inputMemory\": false,\n \"inputSize\": 1,\n \"name\": \"_setImplementation\",\n \"outputMemory\": false,\n \"outputSize\": 0,\n \"pc\": 1593,\n \"signatureHash\": \"0x\"\n }\n ]\n },\n \"noInternalCalls\": false,\n \"withInternalCalls\": true\n}"; + + let v: SentioTracerConfig = serde_json::from_str(&config_string).unwrap(); + assert_eq!(v.debug, true); + assert_eq!(v.calls.len(), 2); + assert_eq!(v.functions.len(), 1); +} + +#[test] +fn test_h256_to_u256() { + let string = "0f02ba4d7f83e59eaa32eae9c3c4d99b68ce76decade21cdab7ecce8f4aef81a"; + let bytes = hex::decode(string).unwrap(); + let h256 = H256::from_slice(&bytes); + let h256_bytes = h256.as_bytes(); + assert_eq!(h256_bytes, bytes); + let u256 = U256::from(h256_bytes); + let u256_string = serde_json::to_string(&u256).unwrap(); + let u256_sub = "0".to_string() + &u256_string[3..u256_string.len() - 1].to_string(); + assert_eq!(string, u256_sub); +} diff --git a/vendor/evm-tracing/src/types/single.rs b/vendor/evm-tracing/src/types/single.rs index 3c3bb4e155..bff4f6e7e2 100644 --- a/vendor/evm-tracing/src/types/single.rs +++ b/vendor/evm-tracing/src/types/single.rs @@ -22,6 +22,9 @@ use super::serialization::*; use serde::Serialize; +use crate::types::sentio::{ + SentioCallTrace, SentioPrestateTrace, SentioPrestateTracerConfig, SentioTracerConfig, +}; use ethereum_types::{H256, U256}; use parity_scale_codec::{Decode, Encode}; use sp_std::{collections::btree_map::BTreeMap, vec::Vec}; @@ -33,7 +36,7 @@ pub enum Call { CallTracer(crate::formatters::call_tracer::CallTracerCall), } -#[derive(Clone, Copy, Eq, PartialEq, Debug, Encode, Decode)] +#[derive(Clone, Eq, PartialEq, Debug, Encode, Decode)] pub enum TraceType { /// Classic geth with no javascript based tracing. Raw { @@ -45,6 +48,12 @@ pub enum TraceType { CallList, /// A single block trace. Use in `debug_traceTransactionByNumber` / `traceTransactionByHash`. Block, + SentioCallList { + tracer_config: Option, + }, + SentioPrestate { + tracer_config: Option, + }, } /// Single transaction trace. @@ -64,6 +73,8 @@ pub enum TransactionTrace { CallList(Vec), /// Used by Geth's callTracer. CallListNested(Call), + SentioCallTrace(SentioCallTrace), + SentioPrestateTrace(SentioPrestateTrace), } #[derive(Clone, Eq, PartialEq, Debug, Encode, Decode, Serialize)] diff --git a/vendor/rpc-core/debug/src/lib.rs b/vendor/rpc-core/debug/src/lib.rs index 2f9bd2416b..b031d4a339 100644 --- a/vendor/rpc-core/debug/src/lib.rs +++ b/vendor/rpc-core/debug/src/lib.rs @@ -13,11 +13,14 @@ // You should have received a copy of the GNU General Public License // along with Moonbeam. If not, see . -use ethereum_types::H256; + +use std::collections::BTreeMap; +use ethereum_types::{H160, H256}; use jsonrpsee::{core::RpcResult, proc_macros::rpc}; use moonbeam_client_evm_tracing::types::single; use moonbeam_rpc_core_types::RequestBlockId; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; +use serde_json::Value; #[derive(Clone, Eq, PartialEq, Debug, Deserialize)] #[serde(rename_all = "camelCase")] @@ -28,6 +31,21 @@ pub struct TraceParams { /// Javascript tracer (we just check if it's Blockscout tracer string) pub tracer: Option, pub timeout: Option, + pub tracer_config: Option +} + +#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct StorageEntry { + pub key: H256, + pub value: H256, +} + +#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct StorageRangeResult { + pub storage: BTreeMap, + pub next_key: Option } #[rpc(server)] @@ -45,4 +63,13 @@ pub trait Debug { id: RequestBlockId, params: Option, ) -> RpcResult>; + #[method(name = "debug_storageRangeAt")] + async fn storage_range_at( + &self, + block_hash: H256, + tx_index: u64, + address: H160, + start_key: H256, + limit: u64, + ) -> RpcResult; } diff --git a/vendor/rpc/debug/Cargo.toml b/vendor/rpc/debug/Cargo.toml index ba3e0ee12b..f971e8a9c0 100644 --- a/vendor/rpc/debug/Cargo.toml +++ b/vendor/rpc/debug/Cargo.toml @@ -12,6 +12,7 @@ futures = { workspace = true, features = ["compat"] } hex-literal = { workspace = true } jsonrpsee = { workspace = true, features = ["macros", "server"] } tokio = { workspace = true, features = ["sync", "time"] } +sha3 = { workspace = true } # Moonbeam moonbeam-client-evm-tracing = { workspace = true } diff --git a/vendor/rpc/debug/src/lib.rs b/vendor/rpc/debug/src/lib.rs index de67bb0398..3528193cd9 100644 --- a/vendor/rpc/debug/src/lib.rs +++ b/vendor/rpc/debug/src/lib.rs @@ -22,14 +22,17 @@ use tokio::{ sync::{oneshot, Semaphore}, }; -use ethereum_types::H256; +use ethereum_types::{H160, H256}; use fc_rpc::{frontier_backend_client, internal_err, OverrideHandle}; use fp_rpc::EthereumRuntimeRPCApi; +use jsonrpsee::core::__reexports::serde_json; use moonbeam_client_evm_tracing::{formatters::ResponseFormatter, types::single}; +use moonbeam_rpc_core_debug::{StorageEntry, StorageRangeResult}; use moonbeam_rpc_core_types::{RequestBlockId, RequestBlockTag}; use moonbeam_rpc_primitives_debug::{DebugRuntimeApi, TracerInput}; use sc_client_api::backend::{Backend, StateBackend, StorageProvider}; use sc_utils::mpsc::TracingUnboundedSender; +use sha3::{Digest, Keccak256}; use sp_api::{ApiExt, BlockId, Core, HeaderT, ProvideRuntimeApi}; use sp_block_builder::BlockBuilder; use sp_blockchain::{ @@ -38,14 +41,24 @@ use sp_blockchain::{ use sp_runtime::traits::{BlakeTwo256, Block as BlockT, UniqueSaturatedInto}; use std::{future::Future, marker::PhantomData, sync::Arc}; +pub struct StorageRangeParam { + block_hash: H256, + tx_index: u64, + address: H160, + start_key: H256, + limit: u64, +} + pub enum RequesterInput { Transaction(H256), Block(RequestBlockId), + StorageRange(StorageRangeParam), } pub enum Response { Single(single::TransactionTrace), Block(Vec), + StorageRange(StorageRangeResult), } pub type Responder = oneshot::Sender>; @@ -119,6 +132,48 @@ impl DebugServer for Debug { _ => unreachable!(), }) } + + async fn storage_range_at( + &self, + block_hash: H256, + tx_index: u64, + address: H160, + start_key: H256, + limit: u64, + ) -> RpcResult { + let requester = self.requester.clone(); + + let (tx, rx) = oneshot::channel(); + // Send a message from the rpc handler to the service level task. + requester + .unbounded_send(( + ( + RequesterInput::StorageRange(StorageRangeParam { + block_hash, + tx_index, + address, + start_key, + limit, + }), + None, + ), + tx, + )) + .map_err(|err| { + internal_err(format!( + "failed to send request to debug service : {:?}", + err + )) + })?; + + // Receive a message from the service level task and send the rpc response. + rx.await + .map_err(|err| internal_err(format!("debug service dropped the channel : {:?}", err)))? + .map(|res| match res { + Response::StorageRange(res) => res, + _ => unreachable!(), + }) + } } pub struct DebugHandler(PhantomData<(B, C, BE)>); @@ -224,6 +279,39 @@ where ); }); } + Some(((RequesterInput::StorageRange(storage_range), ..), response_tx)) => { + let client = client.clone(); + let backend = backend.clone(); + let frontier_backend = frontier_backend.clone(); + let permit_pool = permit_pool.clone(); + let overrides = overrides.clone(); + + tokio::task::spawn(async move { + let _ = response_tx.send( + async { + let _permit = permit_pool.acquire().await; + + tokio::task::spawn_blocking(move || { + Self::handle_storage_range_request( + client.clone(), + backend.clone(), + frontier_backend.clone(), + storage_range, + overrides.clone(), + ) + }) + .await + .map_err(|e| { + internal_err(format!( + "Internal error on spawned task : {:?}", + e + )) + })? + } + .await, + ); + }); + } _ => {} } } @@ -236,6 +324,7 @@ where match params { Some(TraceParams { tracer: Some(tracer), + tracer_config, .. }) => { const BLOCKSCOUT_JS_CODE_HASH: [u8; 16] = @@ -248,6 +337,22 @@ where Some(TracerInput::Blockscout) } else if tracer == "callTracer" { Some(TracerInput::CallTracer) + } else if tracer == "sentioTracer" { + return Ok(( + TracerInput::None, + single::TraceType::SentioCallList { + tracer_config: tracer_config + .map(|x| serde_json::from_value(x).unwrap_or_default()), + }, + )); + } else if tracer == "sentioPrestateTracer" { + return Ok(( + TracerInput::None, + single::TraceType::SentioPrestate { + tracer_config: tracer_config + .map(|x| serde_json::from_value(x).unwrap_or_default()), + }, + )); } else { None }; @@ -497,6 +602,22 @@ where // Get the actual ethereum transaction. if let Some(block) = reference_block { let transactions = block.transactions; + + let is_prestate = matches!(&trace_type, single::TraceType::SentioPrestate { .. }); + // TODO check if there is a better way to get those extrinsics + let mut try_ext: Vec = vec![]; + if is_prestate { + for ext in &exts { + try_ext.push(ext.clone()); + let filtered_tx = api + .extrinsic_filter(parent_block_hash, try_ext.clone()) + .unwrap(); + if filtered_tx.len() == index + 1 { + break; + } + } + } + if let Some(transaction) = transactions.get(index) { let f = || -> RpcResult<_> { api.initialize_block(parent_block_hash, &header) @@ -561,6 +682,50 @@ where )?, )) } + single::TraceType::SentioCallList { tracer_config } => { + let mut proxy = moonbeam_client_evm_tracing::listeners::SentioCallList::new( + tracer_config.unwrap_or_default(), + ); + proxy.using(f)?; + proxy.finish_transaction(); + + let mut res = + moonbeam_client_evm_tracing::formatters::SentioTracer::format(proxy) + .ok_or("Trace result is empty.") + .map_err(|e| internal_err(format!("{:?}", e)))?; + + Ok(Response::Single(res.pop().expect("Trace result is empty."))) + } + single::TraceType::SentioPrestate { tracer_config } => { + let new_api = client.runtime_api(); + new_api + .initialize_block(parent_block_hash, &header) + .map_err(|e| { + internal_err(format!("Runtime api access error: {:?}", e)) + })?; + + let mut proxy: moonbeam_client_evm_tracing::listeners::SentioPrestate< + B, + C, + > = moonbeam_client_evm_tracing::listeners::SentioPrestate::new( + tracer_config.unwrap_or_default(), + header.parent_hash().clone(), + try_ext, + block.header.beneficiary.clone(), + &new_api, + ); + proxy.using(f)?; + proxy.finish_transaction(); + + let mut res = + moonbeam_client_evm_tracing::formatters::SentioPrestateTracer::format( + proxy, + ) + .ok_or("Trace result is empty.") + .map_err(|e| internal_err(format!("{:?}", e)))?; + + Ok(Response::Single(res.pop().expect("Trace result is empty."))) + } single::TraceType::CallList => { let mut proxy = moonbeam_client_evm_tracing::listeners::CallList::default(); proxy.using(f)?; @@ -595,4 +760,109 @@ where } Err(internal_err("Runtime block call failed".to_string())) } + + fn handle_storage_range_request( + client: Arc, + backend: Arc, + frontier_backend: Arc + Send + Sync>, + storage_range: StorageRangeParam, + overrides: Arc>, + ) -> RpcResult { + let reference_id = + match futures::executor::block_on(frontier_backend_client::load_hash::( + client.as_ref(), + frontier_backend.as_ref(), + storage_range.block_hash, + )) { + Ok(Some(hash)) => BlockId::Hash(hash), + Ok(_) => return Err(internal_err("Block hash not found".to_string())), + Err(e) => return Err(e), + }; + + // Get ApiRef. This handle allow to keep changes between txs in an internal buffer. + let api = client.runtime_api(); + // Get Blockchain backend + let blockchain = backend.blockchain(); + // Get the header I want to work with. + let Ok(hash) = client.expect_block_hash_from_id(&reference_id) else { + return Err(internal_err("Block header not found")) + }; + let header = match client.header(hash) { + Ok(Some(h)) => h, + _ => return Err(internal_err("Block header not found")), + }; + + // Get parent blockid. + let parent_block_hash = *header.parent_hash(); + + let schema = fc_storage::onchain_storage_schema::(client.as_ref(), hash); + + // Using storage overrides we align with `:ethereum_schema` which will result in proper + // SCALE decoding in case of migration. + let statuses = match overrides.schemas.get(&schema) { + Some(schema) => schema + .current_transaction_statuses(hash) + .unwrap_or_default(), + _ => { + return Err(internal_err(format!( + "No storage override at {:?}", + reference_id + ))) + } + }; + + // // Known ethereum transaction hashes. + // let eth_tx_hashes: Vec<_> = statuses.iter().map(|t| t.transaction_hash).collect(); + + // Get block extrinsics. + let exts = blockchain + .body(hash) + .map_err(|e| internal_err(format!("Fail to read blockchain db: {:?}", e)))? + .unwrap_or_default(); + + api.initialize_block(parent_block_hash, &header) + .map_err(|e| internal_err(format!("Runtime api access error: {:?}", e)))?; + if storage_range.tx_index as usize >= statuses.len() { + panic!("tx index is too large"); + } + let to = exts.len() - (statuses.len() - storage_range.tx_index as usize); + let mut i = 0; + for ext in exts { + if i >= to { + break; + } + i = i + 1; + let _ = api.apply_extrinsic(parent_block_hash, ext); + } + + let version = api.version(parent_block_hash).expect("has version"); + let res = api.storage_range_at( + parent_block_hash, + storage_range.address, + storage_range.start_key, + storage_range.limit, + ); + match res { + Ok((storages, next_key)) => { + let mut result = StorageRangeResult { + storage: Default::default(), + next_key: next_key, + }; + + for (key, value) in storages { + let key_hash = H256::from_slice(Keccak256::digest(key.as_bytes()).as_slice()); + result.storage.insert(key_hash, StorageEntry { key, value }); + } + + return Ok(Response::StorageRange(result)); + } + Err(e) => { + return Err(internal_err(format!( + "{} for version {}", + e.to_string(), + version.spec_version + ))); + } + } + } }