use alloy_primitives::{Address, BlockHash, Bytes, TxHash, B256, U256, U64};
use serde::{ser::SerializeStruct, Deserialize, Serialize, Serializer};
use std::{
collections::BTreeMap,
ops::{Deref, DerefMut},
};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum TraceType {
#[default]
Trace,
VmTrace,
StateDiff,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TraceResults {
#[serde(deserialize_with = "alloy_serde::null_as_default")]
pub output: Bytes,
pub state_diff: Option<StateDiff>,
#[serde(default)]
pub trace: Vec<TransactionTrace>,
pub vm_trace: Option<VmTrace>,
}
impl TraceResults {
pub fn set_root_trace_gas_used(&mut self, gas_used: u64) {
if let Some(r) = self.trace.first_mut().and_then(|t| t.result.as_mut()) {
r.set_gas_used(gas_used)
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TraceResultsWithTransactionHash {
#[serde(flatten)]
pub full_trace: TraceResults,
#[doc(alias = "tx_hash")]
pub transaction_hash: B256,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct ChangedType<T> {
pub from: T,
pub to: T,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub enum Delta<T> {
#[default]
#[serde(rename = "=")]
Unchanged,
#[serde(rename = "+")]
Added(T),
#[serde(rename = "-")]
Removed(T),
#[serde(rename = "*")]
Changed(ChangedType<T>),
}
impl<T> Delta<T> {
pub const fn changed(from: T, to: T) -> Self {
Self::Changed(ChangedType { from, to })
}
pub const fn is_unchanged(&self) -> bool {
matches!(self, Self::Unchanged)
}
pub const fn is_added(&self) -> bool {
matches!(self, Self::Added(_))
}
pub const fn is_removed(&self) -> bool {
matches!(self, Self::Removed(_))
}
pub const fn is_changed(&self) -> bool {
matches!(self, Self::Changed(_))
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AccountDiff {
pub balance: Delta<U256>,
pub code: Delta<Bytes>,
pub nonce: Delta<U64>,
pub storage: BTreeMap<B256, Delta<B256>>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(transparent)]
pub struct StateDiff(pub BTreeMap<Address, AccountDiff>);
impl Deref for StateDiff {
type Target = BTreeMap<Address, AccountDiff>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for StateDiff {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", tag = "type", content = "action")]
pub enum Action {
Call(CallAction),
Create(CreateAction),
#[serde(rename = "suicide", alias = "selfdestruct")]
Selfdestruct(SelfdestructAction),
Reward(RewardAction),
}
impl Default for Action {
fn default() -> Self {
Self::Call(CallAction::default())
}
}
impl Action {
pub const fn is_call(&self) -> bool {
matches!(self, Self::Call(_))
}
pub const fn is_create(&self) -> bool {
matches!(self, Self::Create(_))
}
pub const fn is_selfdestruct(&self) -> bool {
matches!(self, Self::Selfdestruct(_))
}
pub const fn is_reward(&self) -> bool {
matches!(self, Self::Reward(_))
}
pub const fn as_call(&self) -> Option<&CallAction> {
match self {
Self::Call(action) => Some(action),
_ => None,
}
}
pub const fn as_create(&self) -> Option<&CreateAction> {
match self {
Self::Create(action) => Some(action),
_ => None,
}
}
pub const fn as_selfdestruct(&self) -> Option<&SelfdestructAction> {
match self {
Self::Selfdestruct(action) => Some(action),
_ => None,
}
}
pub const fn as_reward(&self) -> Option<&RewardAction> {
match self {
Self::Reward(action) => Some(action),
_ => None,
}
}
pub const fn kind(&self) -> ActionType {
match self {
Self::Call(_) => ActionType::Call,
Self::Create(_) => ActionType::Create,
Self::Selfdestruct(_) => ActionType::Selfdestruct,
Self::Reward(_) => ActionType::Reward,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ActionType {
Call,
Create,
#[serde(rename = "suicide", alias = "selfdestruct")]
Selfdestruct,
Reward,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum CallType {
#[default]
None,
Call,
CallCode,
DelegateCall,
StaticCall,
AuthCall,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct CallAction {
pub from: Address,
pub call_type: CallType,
#[serde(with = "alloy_serde::quantity")]
pub gas: u64,
pub input: Bytes,
pub to: Address,
pub value: U256,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CreateAction {
pub from: Address,
#[serde(with = "alloy_serde::quantity")]
pub gas: u64,
pub init: Bytes,
pub value: U256,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum RewardType {
Block,
Uncle,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RewardAction {
pub author: Address,
pub reward_type: RewardType,
pub value: U256,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SelfdestructAction {
pub address: Address,
pub balance: U256,
pub refund_address: Address,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CallOutput {
#[serde(with = "alloy_serde::quantity")]
pub gas_used: u64,
pub output: Bytes,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CreateOutput {
pub address: Address,
pub code: Bytes,
#[serde(with = "alloy_serde::quantity")]
pub gas_used: u64,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum TraceOutput {
Call(CallOutput),
Create(CreateOutput),
}
impl TraceOutput {
pub const fn output(&self) -> &Bytes {
match self {
Self::Call(call) => &call.output,
Self::Create(create) => &create.code,
}
}
pub fn into_output(self) -> Bytes {
match self {
Self::Call(call) => call.output,
Self::Create(create) => create.code,
}
}
pub const fn gas_used(&self) -> u64 {
match self {
Self::Call(call) => call.gas_used,
Self::Create(create) => create.gas_used,
}
}
pub fn set_gas_used(&mut self, gas_used: u64) {
match self {
Self::Call(call) => call.gas_used = gas_used,
Self::Create(create) => create.gas_used = gas_used,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
#[doc(alias = "TxTrace")]
pub struct TransactionTrace {
#[serde(flatten)]
pub action: Action,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
#[serde(default)]
pub result: Option<TraceOutput>,
pub subtraces: usize,
pub trace_address: Vec<usize>,
}
#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
#[doc(alias = "LocalizedTxTrace")]
pub struct LocalizedTransactionTrace {
#[serde(flatten)]
pub trace: TransactionTrace,
pub block_hash: Option<BlockHash>,
pub block_number: Option<u64>,
#[doc(alias = "tx_hash")]
pub transaction_hash: Option<TxHash>,
#[doc(alias = "tx_position", alias = "transaction_index", alias = "tx_index")]
pub transaction_position: Option<u64>,
}
impl Serialize for LocalizedTransactionTrace {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut s = serializer.serialize_struct("LocalizedTransactionTrace", 9)?;
let TransactionTrace { action, error, result, subtraces, trace_address } = &self.trace;
match action {
Action::Call(call_action) => {
s.serialize_field("action", call_action)?;
}
Action::Create(create_action) => {
s.serialize_field("action", create_action)?;
}
Action::Selfdestruct(selfdestruct_action) => {
s.serialize_field("action", selfdestruct_action)?;
}
Action::Reward(reward_action) => {
s.serialize_field("action", reward_action)?;
}
}
if let Some(block_hash) = self.block_hash {
s.serialize_field("blockHash", &block_hash)?;
}
if let Some(block_number) = self.block_number {
s.serialize_field("blockNumber", &block_number)?;
}
if let Some(error) = error {
s.serialize_field("error", error)?;
}
match result {
Some(TraceOutput::Call(call)) => s.serialize_field("result", call)?,
Some(TraceOutput::Create(create)) => s.serialize_field("result", create)?,
None => s.serialize_field("result", &None::<()>)?,
}
s.serialize_field("subtraces", &subtraces)?;
s.serialize_field("traceAddress", &trace_address)?;
if let Some(transaction_hash) = &self.transaction_hash {
s.serialize_field("transactionHash", transaction_hash)?;
}
if let Some(transaction_position) = &self.transaction_position {
s.serialize_field("transactionPosition", transaction_position)?;
}
s.serialize_field("type", &action.kind())?;
s.end()
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct VmTrace {
pub code: Bytes,
pub ops: Vec<VmInstruction>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct VmInstruction {
pub cost: u64,
pub ex: Option<VmExecutedOperation>,
pub pc: usize,
pub sub: Option<VmTrace>,
#[serde(skip_serializing_if = "Option::is_none")]
pub op: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub idx: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct VmExecutedOperation {
pub used: u64,
pub push: Vec<U256>,
pub mem: Option<MemoryDelta>,
pub store: Option<StorageDelta>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MemoryDelta {
pub off: usize,
pub data: Bytes,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct StorageDelta {
pub key: U256,
pub val: U256,
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::{json, Value};
use similar_asserts::assert_eq;
use std::str::FromStr;
#[test]
fn test_transaction_trace() {
let s = r#"{
"action": {
"from": "0x66e29f0b6b1b07071f2fde4345d512386cb66f5f",
"callType": "call",
"gas": "0x10bfc",
"input": "0xf6cd1e8d0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ec6952892271c8ee13f12e118484e03149281c9600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000010480862479000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000160f5f00288e9e1cc8655b327e081566e580a71d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000011c37937e080000fffffffffffffffffffffffffffffffffffffffffffffffffee3c86c81f8000000000000000000000000000000000000000000000000000000000000",
"to": "0x160f5f00288e9e1cc8655b327e081566e580a71d",
"value": "0x244b"
},
"error": "Reverted",
"result": {
"gasUsed": "0x9daf",
"output": "0x000000000000000000000000000000000000000000000000011c37937e080000"
},
"subtraces": 3,
"traceAddress": [],
"type": "call"
}"#;
let val = serde_json::from_str::<TransactionTrace>(s).unwrap();
serde_json::to_value(val).unwrap();
}
#[test]
fn test_selfdestruct_suicide() {
let input = r#"{
"action": {
"address": "0x66e29f0b6b1b07071f2fde4345d512386cb66f5f",
"refundAddress": "0x66e29f0b6b1b07071f2fde4345d512386cb66f5f",
"balance": "0x244b"
},
"error": "Reverted",
"result": {
"gasUsed": "0x9daf",
"output": "0x000000000000000000000000000000000000000000000000011c37937e080000"
},
"subtraces": 3,
"traceAddress": [],
"type": "suicide"
}"#;
let val = serde_json::from_str::<TransactionTrace>(input).unwrap();
assert!(val.action.is_selfdestruct());
let json = serde_json::to_value(val.clone()).unwrap();
let expect = serde_json::from_str::<serde_json::Value>(input).unwrap();
assert_eq!(json, expect);
let s = serde_json::to_string(&val).unwrap();
let json = serde_json::from_str::<serde_json::Value>(&s).unwrap();
assert_eq!(json, expect);
let input = input.replace("suicide", "selfdestruct");
let val = serde_json::from_str::<TransactionTrace>(&input).unwrap();
assert!(val.action.is_selfdestruct());
}
#[derive(Debug)]
struct TraceTestCase {
trace: LocalizedTransactionTrace,
expected_json: Value,
}
#[test]
fn test_serialization_order() {
let test_cases = vec![
TraceTestCase {
trace: LocalizedTransactionTrace {
trace: TransactionTrace {
action: Action::Call(CallAction {
from: "0x4f4495243837681061c4743b74b3eedf548d56a5".parse::<Address>().unwrap(),
call_type: CallType::DelegateCall,
gas: 3148955,
input: Bytes::from_str("0x585a9fd40000000000000000000000000000000000000000000000000000000000000040a47c5ad9a4af285720eae6cc174a9c75c5bbaf973b00f1a0c191327445b6581000000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000ce16f69375520ab01377ce7b88f5ba8c48f8d666f61490331372e432315cd97447e3bc452d6c73a6e0536260a88ddab46f85c88d00000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000aab8cf0fbfb038751339cb61161fa11789b41a78f1b7b0e12cf8e467d403590b7a5f26f0000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000646616e746f6d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a3078636531364636393337353532306162303133373763653742383866354241384334384638443636360000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000045553444300000000000000000000000000000000000000000000000000000000").unwrap(),
to: "0x99b5fa03a5ea4315725c43346e55a6a6fbd94098".parse::<Address>().unwrap(),
value: U256::from(0),
}),
error: None,
result: Some(TraceOutput::Call(CallOutput { gas_used: 32364, output: Bytes::new() })),
subtraces: 0,
trace_address: vec![0, 10, 0],
},
block_hash: Some(B256::ZERO),
block_number: Some(18557272),
transaction_hash: Some(B256::from_str("0x54160ddcdbfaf98a43a43c328ebd44aa99faa765e0daa93e61145b06815a4071").unwrap()),
transaction_position: Some(102),
},
expected_json: json!({
"action": {
"from": "0x4f4495243837681061c4743b74b3eedf548d56a5",
"callType": "delegatecall",
"gas": "0x300c9b",
"input": "0x585a9fd40000000000000000000000000000000000000000000000000000000000000040a47c5ad9a4af285720eae6cc174a9c75c5bbaf973b00f1a0c191327445b6581000000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000ce16f69375520ab01377ce7b88f5ba8c48f8d666f61490331372e432315cd97447e3bc452d6c73a6e0536260a88ddab46f85c88d00000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000aab8cf0fbfb038751339cb61161fa11789b41a78f1b7b0e12cf8e467d403590b7a5f26f0000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000646616e746f6d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a3078636531364636393337353532306162303133373763653742383866354241384334384638443636360000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000045553444300000000000000000000000000000000000000000000000000000000",
"to": "0x99b5fa03a5ea4315725c43346e55a6a6fbd94098",
"value": "0x0"
},
"blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"blockNumber": 18557272,
"result": {
"gasUsed": "0x7e6c",
"output": "0x"
},
"subtraces": 0,
"traceAddress": [
0,
10,
0
],
"transactionHash": "0x54160ddcdbfaf98a43a43c328ebd44aa99faa765e0daa93e61145b06815a4071",
"transactionPosition": 102,
"type": "call"
}),
},
TraceTestCase {
trace: LocalizedTransactionTrace {
trace: TransactionTrace {
action: Action::Create(CreateAction{
from: "0x4f4495243837681061c4743b74b3eedf548d56a5".parse::<Address>().unwrap(),
gas: 3438907,
init: Bytes::from_str("0x6080604052600160005534801561001557600080fd5b50610324806100256000396000f3fe608060405234801561001057600080fd5b50600436106100355760003560e01c8062f55d9d1461003a5780631cff79cd1461004f575b600080fd5b61004d6100483660046101da565b610079565b005b61006261005d3660046101fc565b6100bb565b60405161007092919061027f565b60405180910390f35b6002600054141561009d5760405163caa30f5560e01b815260040160405180910390fd5b600260005573ffffffffffffffffffffffffffffffffffffffff8116ff5b60006060600260005414156100e35760405163caa30f5560e01b815260040160405180910390fd5b600260005573ffffffffffffffffffffffffffffffffffffffff85163b610136576040517f6f7c43f100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8473ffffffffffffffffffffffffffffffffffffffff16848460405161015d9291906102de565b6000604051808303816000865af19150503d806000811461019a576040519150601f19603f3d011682016040523d82523d6000602084013e61019f565b606091505b50600160005590969095509350505050565b803573ffffffffffffffffffffffffffffffffffffffff811681146101d557600080fd5b919050565b6000602082840312156101ec57600080fd5b6101f5826101b1565b9392505050565b60008060006040848603121561021157600080fd5b61021a846101b1565b9250602084013567ffffffffffffffff8082111561023757600080fd5b818601915086601f83011261024b57600080fd5b81358181111561025a57600080fd5b87602082850101111561026c57600080fd5b6020830194508093505050509250925092565b821515815260006020604081840152835180604085015260005b818110156102b557858101830151858201606001528201610299565b818111156102c7576000606083870101525b50601f01601f191692909201606001949350505050565b818382376000910190815291905056fea264697066735822122032cb5e746816b7fac95205c068b30da37bd40119a57265be331c162cae74712464736f6c63430008090033").unwrap(),
value: U256::from(0),
}),
error: None,
result: Some(TraceOutput::Create(CreateOutput { gas_used: 183114, address: "0x7eb6c6c1db08c0b9459a68cfdcedab64f319c138".parse::<Address>().unwrap(), code: Bytes::from_str("0x608060405234801561001057600080fd5b50600436106100355760003560e01c8062f55d9d1461003a5780631cff79cd1461004f575b600080fd5b61004d6100483660046101da565b610079565b005b61006261005d3660046101fc565b6100bb565b60405161007092919061027f565b60405180910390f35b6002600054141561009d5760405163caa30f5560e01b815260040160405180910390fd5b600260005573ffffffffffffffffffffffffffffffffffffffff8116ff5b60006060600260005414156100e35760405163caa30f5560e01b815260040160405180910390fd5b600260005573ffffffffffffffffffffffffffffffffffffffff85163b610136576040517f6f7c43f100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8473ffffffffffffffffffffffffffffffffffffffff16848460405161015d9291906102de565b6000604051808303816000865af19150503d806000811461019a576040519150601f19603f3d011682016040523d82523d6000602084013e61019f565b606091505b50600160005590969095509350505050565b803573ffffffffffffffffffffffffffffffffffffffff811681146101d557600080fd5b919050565b6000602082840312156101ec57600080fd5b6101f5826101b1565b9392505050565b60008060006040848603121561021157600080fd5b61021a846101b1565b9250602084013567ffffffffffffffff8082111561023757600080fd5b818601915086601f83011261024b57600080fd5b81358181111561025a57600080fd5b87602082850101111561026c57600080fd5b6020830194508093505050509250925092565b821515815260006020604081840152835180604085015260005b818110156102b557858101830151858201606001528201610299565b818111156102c7576000606083870101525b50601f01601f191692909201606001949350505050565b818382376000910190815291905056fea264697066735822122032cb5e746816b7fac95205c068b30da37bd40119a57265be331c162cae74712464736f6c63430008090033").unwrap() })),
subtraces: 0,
trace_address: vec![0, 7, 0, 0],
},
block_hash: Some(B256::from_str("0xd5ac5043011d4f16dba7841fa760c4659644b78f663b901af4673b679605ed0d").unwrap()),
block_number: Some(18557272),
transaction_hash: Some(B256::from_str("0x54160ddcdbfaf98a43a43c328ebd44aa99faa765e0daa93e61145b06815a4071").unwrap()),
transaction_position: Some(102),
},
expected_json: json!({
"action": {
"from": "0x4f4495243837681061c4743b74b3eedf548d56a5",
"gas": "0x34793b",
"init": "0x6080604052600160005534801561001557600080fd5b50610324806100256000396000f3fe608060405234801561001057600080fd5b50600436106100355760003560e01c8062f55d9d1461003a5780631cff79cd1461004f575b600080fd5b61004d6100483660046101da565b610079565b005b61006261005d3660046101fc565b6100bb565b60405161007092919061027f565b60405180910390f35b6002600054141561009d5760405163caa30f5560e01b815260040160405180910390fd5b600260005573ffffffffffffffffffffffffffffffffffffffff8116ff5b60006060600260005414156100e35760405163caa30f5560e01b815260040160405180910390fd5b600260005573ffffffffffffffffffffffffffffffffffffffff85163b610136576040517f6f7c43f100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8473ffffffffffffffffffffffffffffffffffffffff16848460405161015d9291906102de565b6000604051808303816000865af19150503d806000811461019a576040519150601f19603f3d011682016040523d82523d6000602084013e61019f565b606091505b50600160005590969095509350505050565b803573ffffffffffffffffffffffffffffffffffffffff811681146101d557600080fd5b919050565b6000602082840312156101ec57600080fd5b6101f5826101b1565b9392505050565b60008060006040848603121561021157600080fd5b61021a846101b1565b9250602084013567ffffffffffffffff8082111561023757600080fd5b818601915086601f83011261024b57600080fd5b81358181111561025a57600080fd5b87602082850101111561026c57600080fd5b6020830194508093505050509250925092565b821515815260006020604081840152835180604085015260005b818110156102b557858101830151858201606001528201610299565b818111156102c7576000606083870101525b50601f01601f191692909201606001949350505050565b818382376000910190815291905056fea264697066735822122032cb5e746816b7fac95205c068b30da37bd40119a57265be331c162cae74712464736f6c63430008090033",
"value": "0x0"
},
"blockHash": "0xd5ac5043011d4f16dba7841fa760c4659644b78f663b901af4673b679605ed0d",
"blockNumber": 18557272,
"result": {
"address": "0x7eb6c6c1db08c0b9459a68cfdcedab64f319c138",
"code": "0x608060405234801561001057600080fd5b50600436106100355760003560e01c8062f55d9d1461003a5780631cff79cd1461004f575b600080fd5b61004d6100483660046101da565b610079565b005b61006261005d3660046101fc565b6100bb565b60405161007092919061027f565b60405180910390f35b6002600054141561009d5760405163caa30f5560e01b815260040160405180910390fd5b600260005573ffffffffffffffffffffffffffffffffffffffff8116ff5b60006060600260005414156100e35760405163caa30f5560e01b815260040160405180910390fd5b600260005573ffffffffffffffffffffffffffffffffffffffff85163b610136576040517f6f7c43f100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8473ffffffffffffffffffffffffffffffffffffffff16848460405161015d9291906102de565b6000604051808303816000865af19150503d806000811461019a576040519150601f19603f3d011682016040523d82523d6000602084013e61019f565b606091505b50600160005590969095509350505050565b803573ffffffffffffffffffffffffffffffffffffffff811681146101d557600080fd5b919050565b6000602082840312156101ec57600080fd5b6101f5826101b1565b9392505050565b60008060006040848603121561021157600080fd5b61021a846101b1565b9250602084013567ffffffffffffffff8082111561023757600080fd5b818601915086601f83011261024b57600080fd5b81358181111561025a57600080fd5b87602082850101111561026c57600080fd5b6020830194508093505050509250925092565b821515815260006020604081840152835180604085015260005b818110156102b557858101830151858201606001528201610299565b818111156102c7576000606083870101525b50601f01601f191692909201606001949350505050565b818382376000910190815291905056fea264697066735822122032cb5e746816b7fac95205c068b30da37bd40119a57265be331c162cae74712464736f6c63430008090033",
"gasUsed": "0x2cb4a"
},
"subtraces": 0,
"traceAddress": [
0,
7,
0,
0
],
"transactionHash": "0x54160ddcdbfaf98a43a43c328ebd44aa99faa765e0daa93e61145b06815a4071",
"transactionPosition": 102,
"type": "create"
}),
}
];
for (i, test_case) in test_cases.iter().enumerate() {
let serialized = serde_json::to_string(&test_case.trace).unwrap();
let actual_json: Value = serde_json::from_str(&serialized).unwrap();
assert_eq!(
actual_json, test_case.expected_json,
"Test case {} failed; Trace: {:?}",
i, test_case.trace
);
}
}
#[test]
fn test_deserialize_serialize() {
let reference_data = r#"{
"action": {
"from": "0xc77820eef59629fc8d88154977bc8de8a1b2f4ae",
"callType": "call",
"gas": "0x4a0d00",
"input": "0x12",
"to": "0x4f4495243837681061c4743b74b3eedf548d56a5",
"value": "0x0"
},
"blockHash": "0xd5ac5043011d4f16dba7841fa760c4659644b78f663b901af4673b679605ed0d",
"blockNumber": 18557272,
"result": {
"gasUsed": "0x17d337",
"output": "0x"
},
"subtraces": 1,
"traceAddress": [],
"transactionHash": "0x54160ddcdbfaf98a43a43c328ebd44aa99faa765e0daa93e61145b06815a4071",
"transactionPosition": 102,
"type": "call"
}"#;
let trace: LocalizedTransactionTrace = serde_json::from_str(reference_data).unwrap();
assert!(trace.trace.action.is_call());
let serialized = serde_json::to_string_pretty(&trace).unwrap();
assert_eq!(serialized, reference_data);
}
#[test]
fn test_deserialize_serialize_selfdestruct() {
let reference_data = r#"{
"action": {
"address": "0xc77820eef59629fc8d88154977bc8de8a1b2f4ae",
"balance": "0x0",
"refundAddress": "0x4f4495243837681061c4743b74b3eedf548d56a5"
},
"blockHash": "0xd5ac5043011d4f16dba7841fa760c4659644b78f663b901af4673b679605ed0d",
"blockNumber": 18557272,
"result": {
"gasUsed": "0x17d337",
"output": "0x"
},
"subtraces": 1,
"traceAddress": [],
"transactionHash": "0x54160ddcdbfaf98a43a43c328ebd44aa99faa765e0daa93e61145b06815a4071",
"transactionPosition": 102,
"type": "suicide"
}"#;
let trace: LocalizedTransactionTrace = serde_json::from_str(reference_data).unwrap();
assert!(trace.trace.action.is_selfdestruct());
let serialized = serde_json::to_string_pretty(&trace).unwrap();
assert_eq!(serialized, reference_data);
}
#[test]
fn test_transaction_trace_null_result() {
let trace = TransactionTrace {
action: Action::Call(CallAction {
from: Address::from_str("0x1234567890123456789012345678901234567890").unwrap(),
call_type: CallType::Call,
gas: 100000,
input: Bytes::from_str("0x1234").unwrap(),
to: Address::from_str("0x0987654321098765432109876543210987654321").unwrap(),
value: U256::from(0),
}),
..Default::default()
};
let serialized = serde_json::to_string(&trace).unwrap();
let deserialized: serde_json::Value = serde_json::from_str(&serialized).unwrap();
assert_eq!(deserialized["result"], serde_json::Value::Null);
assert!(deserialized.as_object().unwrap().contains_key("result"));
assert!(!deserialized.as_object().unwrap().contains_key("error"));
let deserialized_trace: TransactionTrace = serde_json::from_str(&serialized).unwrap();
assert_eq!(deserialized_trace.result, None);
}
#[test]
fn test_transaction_trace_error_result() {
let trace = TransactionTrace { error: Some("Reverted".to_string()), ..Default::default() };
let serialized = serde_json::to_string(&trace).unwrap();
let deserialized: serde_json::Value = serde_json::from_str(&serialized).unwrap();
assert_eq!(deserialized["result"], serde_json::Value::Null);
assert!(deserialized.as_object().unwrap().contains_key("result"));
assert!(deserialized.as_object().unwrap().contains_key("error"));
let deserialized_trace: TransactionTrace = serde_json::from_str(&serialized).unwrap();
assert_eq!(deserialized_trace.result, None);
}
#[test]
fn test_nethermind_trace_result_null_output_value() {
let reference_data = r#"{
"output": null,
"stateDiff": {
"0x5e1d1eb61e1164d5a50b28c575da73a29595dff7": {
"balance": "=",
"code": "=",
"nonce": "=",
"storage": {
"0x0000000000000000000000000000000000000000000000000000000000000005": {
"*": {
"from": "0x0000000000000000000000000000000000000000000000000000000000042f66",
"to": "0x0000000000000000000000000000000000000000000000000000000000042f67"
}
}
}
}
},
"trace": [],
"vmTrace": null,
"transactionHash": "0xe56a5e7455c45b1842b35dbcab9d024b21870ee59820525091e183b573b4f9eb"
}"#;
let trace =
serde_json::from_str::<TraceResultsWithTransactionHash>(reference_data).unwrap();
assert_eq!(trace.full_trace.output, Bytes::default());
}
}