Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 71 additions & 10 deletions substrate/frame/revive/src/evm/api/debug_rpc_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -482,7 +482,7 @@ where
#[derive(
Default, TypeInfo, Encode, Decode, Serialize, Deserialize, Clone, Debug, Eq, PartialEq,
)]
#[serde(rename_all = "camelCase")]
#[serde(default, rename_all = "camelCase")]
pub struct ExecutionTrace {
/// Total gas used by the transaction.
pub gas: u64,
Expand All @@ -499,8 +499,10 @@ pub struct ExecutionTrace {
}

/// 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")]
#[derive(
TypeInfo, Encode, Decode, Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Default,
)]
#[serde(default, rename_all = "camelCase")]
pub struct ExecutionStep {
/// Remaining gas before executing this step.
#[codec(compact)]
Expand All @@ -513,10 +515,10 @@ pub struct ExecutionStep {
/// Current call depth.
pub depth: u16,
/// Return data from last frame output.
#[serde(skip_serializing_if = "Bytes::is_empty")]
#[serde(default, skip_serializing_if = "Bytes::is_empty")]
pub return_data: Bytes,
/// Any error that occurred during execution.
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
/// The kind of execution step (EVM opcode or PVM syscall).
#[serde(flatten)]
Expand All @@ -536,16 +538,22 @@ pub enum ExecutionStepKind {
#[serde(serialize_with = "serialize_opcode", deserialize_with = "deserialize_opcode")]
op: u8,
/// EVM stack contents.
#[serde(serialize_with = "serialize_stack_minimal")]
#[serde(
default,
serialize_with = "serialize_stack_minimal",
deserialize_with = "deserialize_stack_minimal"
)]
stack: Vec<Bytes>,
/// EVM memory contents.
#[serde(
default,
skip_serializing_if = "Vec::is_empty",
serialize_with = "serialize_memory_no_prefix"
)]
memory: Vec<Bytes>,
/// Contract storage changes.
#[serde(
default,
skip_serializing_if = "Option::is_none",
serialize_with = "serialize_storage_no_prefix"
)]
Expand All @@ -554,19 +562,32 @@ pub enum ExecutionStepKind {
/// A PVM syscall execution.
PVMSyscall {
/// The executed syscall.
#[serde(serialize_with = "serialize_syscall_op")]
#[serde(
serialize_with = "serialize_syscall_op",
deserialize_with = "deserialize_syscall_op"
)]
op: u8,
/// The syscall arguments (register values a0-a5).
/// Omitted when `disable_syscall_details` is true in ExecutionTracerConfig.
#[serde(skip_serializing_if = "Vec::is_empty", with = "super::hex_serde::vec")]
#[serde(default, skip_serializing_if = "Vec::is_empty", with = "super::hex_serde::vec")]
args: Vec<u64>,
/// The syscall return value.
/// Omitted when `disable_syscall_details` is true in ExecutionTracerConfig.
#[serde(skip_serializing_if = "Option::is_none", with = "super::hex_serde::option")]
#[serde(
default,
skip_serializing_if = "Option::is_none",
with = "super::hex_serde::option"
)]
returned: Option<u64>,
},
}

impl Default for ExecutionStepKind {
fn default() -> Self {
Self::EVMOpcode { pc: 0, op: 0, stack: Vec::new(), memory: Vec::new(), storage: None }
}
}

macro_rules! define_opcode_functions {
($($op:ident),* $(,)?) => {
/// Get opcode name from byte value using opcode names
Expand Down Expand Up @@ -761,7 +782,7 @@ where
{
use crate::vm::pvm::env::list_syscalls;
let Some(syscall_name_bytes) = list_syscalls().get(*idx as usize) else {
return Err(serde::ser::Error::custom(alloc::format!("Unknown syscall: {idx}")))
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)
Expand All @@ -777,6 +798,21 @@ where
.ok_or_else(|| serde::de::Error::custom(alloc::format!("Unknown opcode: {}", s)))
}

/// Deserialize syscall from string name to index
fn deserialize_syscall_op<'de, D>(deserializer: D) -> Result<u8, D::Error>
where
D: serde::Deserializer<'de>,
{
use crate::vm::pvm::env::list_syscalls;
let s = String::deserialize(deserializer)?;
let syscalls = list_syscalls();
syscalls
.iter()
.position(|name| core::str::from_utf8(name).unwrap_or_default() == s)
.map(|i| i as u8)
.ok_or_else(|| serde::de::Error::custom(alloc::format!("Unknown syscall: {}", s)))
}

/// A smart contract execution call trace.
#[derive(
TypeInfo, Default, Encode, Decode, Serialize, Deserialize, Clone, Debug, Eq, PartialEq,
Expand Down Expand Up @@ -859,6 +895,31 @@ where
minimal_values.serialize(serializer)
}

/// Deserialize stack values from minimal hex format
fn deserialize_stack_minimal<'de, D>(deserializer: D) -> Result<Vec<Bytes>, D::Error>
where
D: serde::Deserializer<'de>,
{
let strings = Vec::<String>::deserialize(deserializer)?;
strings
.into_iter()
.map(|s| {
// Parse as U256 to handle minimal hex like "0x0" or "0x4"
let s = s.trim_start_matches("0x");
let value = sp_core::U256::from_str_radix(s, 16)
.map_err(|e| serde::de::Error::custom(alloc::format!("{:?}", e)))?;
// Convert to bytes, trimming leading zeros to match serialization
let bytes = value.to_big_endian();
let trimmed = bytes
.iter()
.position(|&b| b != 0)
.map(|pos| bytes[pos..].to_vec())
.unwrap_or_else(|| vec![0u8]);
Ok(Bytes::from(trimmed))
})
.collect()
}

/// Serialize memory values without "0x" prefix (like Geth)
fn serialize_memory_no_prefix<S>(memory: &Vec<Bytes>, serializer: S) -> Result<S::Ok, S::Error>
where
Expand Down
1 change: 1 addition & 0 deletions substrate/frame/revive/src/evm/tracing/call_tracing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ impl Tracing for CallTracer {
value: U256,
input: &[u8],
gas_limit: u64,
_parent_gas_left: Option<u64>,
) {
// Increment parent's child call count.
if let Some(&index) = self.current_stack.last() {
Expand Down
43 changes: 36 additions & 7 deletions substrate/frame/revive/src/evm/tracing/execution_tracing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ pub struct ExecutionTracer {
/// The collected trace steps.
steps: Vec<ExecutionStep>,

/// Stack of pending step indices awaiting their exit_step call.
/// When entering an opcode/syscall, we push the step index here.
/// When exit_step is called, we pop to find the correct step to update.
pending_steps: Vec<usize>,

/// Current call depth.
depth: u16,

Expand Down Expand Up @@ -71,6 +76,7 @@ impl ExecutionTracer {
Self {
config,
steps: Vec::new(),
pending_steps: Vec::new(),
depth: 0,
step_count: 0,
total_gas_used: 0,
Expand Down Expand Up @@ -153,7 +159,10 @@ impl Tracing for ExecutionTracer {
},
};

// Track this step's index so exit_step can find it even after nested steps are added
let step_index = self.steps.len();
self.steps.push(step);
self.pending_steps.push(step_index);
self.step_count += 1;
}

Expand Down Expand Up @@ -187,17 +196,26 @@ impl Tracing for ExecutionTracer {
},
};

let step_index = self.steps.len();
self.steps.push(step);
self.pending_steps.push(step_index);
self.step_count += 1;
}

fn exit_step(&mut self, trace_info: &dyn FrameTraceInfo, returned: Option<u64>) {
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.disable_syscall_details {
if let ExecutionStepKind::PVMSyscall { returned: ref mut ret, .. } = step.kind {
*ret = returned;
if let Some(step_index) = self.pending_steps.pop() {
if let Some(step) = self.steps.get_mut(step_index) {
// For call/instantiation opcodes, gas_cost was already set in enter_child_span
// (opcode_cost + gas_forwarded). For other opcodes, calculate it here.
if step.gas_cost == 0 {
step.gas_cost = step.gas.saturating_sub(trace_info.gas_left());
}
// weight_cost is the total weight consumed (including child calls)
step.weight_cost = trace_info.weight_consumed().saturating_sub(step.weight_cost);
if !self.config.disable_syscall_details {
if let ExecutionStepKind::PVMSyscall { returned: ref mut ret, .. } = step.kind {
*ret = returned;
}
}
}
}
Expand All @@ -211,8 +229,19 @@ impl Tracing for ExecutionTracer {
_is_read_only: bool,
_value: U256,
_input: &[u8],
_gas_limit: u64,
gas_limit: u64,
parent_gas_left: Option<u64>,
) {
// Set gas_cost of the pending call/instantiation step.
// gas_cost = opcode_gas_cost + gas_forwarded
if let Some(&step_index) = self.pending_steps.last() {
if let Some(step) = self.steps.get_mut(step_index) {
if let Some(parent_gas) = parent_gas_left {
let opcode_gas_cost = step.gas.saturating_sub(parent_gas);
step.gas_cost = opcode_gas_cost.saturating_add(gas_limit);
}
}
}
self.storages_per_call.push(Default::default());
self.depth += 1;
}
Expand Down
1 change: 1 addition & 0 deletions substrate/frame/revive/src/evm/tracing/prestate_tracing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ where
_value: U256,
_input: &[u8],
_gas_limit: u64,
_parent_gas_left: Option<u64>,
) {
if let Some(delegate_call) = delegate_call {
self.calls.push(self.current_addr());
Expand Down
8 changes: 8 additions & 0 deletions substrate/frame/revive/src/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -890,6 +890,7 @@ where
value,
&input_data,
Default::default(),
None,
);
});

Expand Down Expand Up @@ -1232,6 +1233,11 @@ where
let is_pvm = executable.is_pvm();

if_tracing(|tracer| {
let parent_gas_left = self
.frames()
.nth(1)
.and_then(|f| f.frame_meter.eth_gas_left())
.map(|g| g.try_into().unwrap_or_default());
tracer.enter_child_span(
self.caller().account_id().map(T::AddressMapper::to_address).unwrap_or_default(),
T::AddressMapper::to_address(&frame.account_id),
Expand All @@ -1245,6 +1251,7 @@ where
.unwrap_or_default()
.try_into()
.unwrap_or_default(),
parent_gas_left,
);
});
let mock_answer = self.exec_config.mock_handler.as_ref().and_then(|handler| {
Expand Down Expand Up @@ -2098,6 +2105,7 @@ where
value,
&input_data,
Default::default(),
None,
);
});
let result = if let Some(mock_answer) =
Expand Down
Loading
Loading