From 1e1cdceb0c2246fc448fec74e2a4b1ef2ff34a6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5vard=20Anda=20Estensen?= Date: Fri, 24 Nov 2023 13:13:09 +0100 Subject: [PATCH] feat(rpc-types): implement serialization for LocalizedTransactionTrace (#5446) Co-authored-by: Matthias Seitz --- crates/rpc/rpc-types/src/eth/trace/parity.rs | 273 ++++++++++++++++++- 1 file changed, 258 insertions(+), 15 deletions(-) diff --git a/crates/rpc/rpc-types/src/eth/trace/parity.rs b/crates/rpc/rpc-types/src/eth/trace/parity.rs index 0cf5bef5cb..5a24a151d8 100644 --- a/crates/rpc/rpc-types/src/eth/trace/parity.rs +++ b/crates/rpc/rpc-types/src/eth/trace/parity.rs @@ -4,7 +4,7 @@ //! See use alloy_primitives::{Address, Bytes, B256, U256, U64}; -use serde::{Deserialize, Serialize}; +use serde::{ser::SerializeStruct, Deserialize, Serialize, Serializer}; use std::{ collections::BTreeMap, ops::{Deref, DerefMut}, @@ -171,6 +171,16 @@ impl Action { pub fn is_reward(&self) -> bool { matches!(self, Action::Reward(_)) } + + /// Returns what kind of action this is + pub fn kind(&self) -> ActionType { + match self { + Action::Call(_) => ActionType::Call, + Action::Create(_) => ActionType::Create, + Action::Selfdestruct(_) => ActionType::Selfdestruct, + Action::Reward(_) => ActionType::Reward, + } + } } /// An external action type. @@ -231,12 +241,12 @@ pub struct CallAction { pub struct CreateAction { /// The address of the creator. pub from: Address, - /// The value with which the new account is endowed. - pub value: U256, /// The gas available for the creation init code. pub gas: U64, /// The init code. pub init: Bytes, + /// The value with which the new account is endowed. + pub value: U256, } #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] @@ -251,10 +261,10 @@ pub enum RewardType { pub struct RewardAction { /// Author's address. pub author: Address, - /// Reward amount. - pub value: U256, /// Reward type. pub reward_type: RewardType, + /// Reward amount. + pub value: U256, } /// Represents a _selfdestruct_ action fka `suicide`. @@ -263,10 +273,10 @@ pub struct RewardAction { pub struct SelfdestructAction { /// destroyed/suicided address. pub address: Address, - /// destroyed contract heir. - pub refund_address: Address, /// Balance of the contract just before it was destroyed. pub balance: U256, + /// destroyed contract heir. + pub refund_address: Address, } #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] @@ -279,9 +289,9 @@ pub struct CallOutput { #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct CreateOutput { - pub gas_used: U64, - pub code: Bytes, pub address: Address, + pub code: Bytes, + pub gas_used: U64, } /// Represents the output of a trace. @@ -327,29 +337,88 @@ pub struct TransactionTrace { pub trace_address: Vec, } -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, Eq, PartialEq, Deserialize)] #[serde(rename_all = "camelCase")] pub struct LocalizedTransactionTrace { + /// Trace of the transaction and its result. #[serde(flatten)] pub trace: TransactionTrace, - /// Hash of the block, if not pending + /// Hash of the block, if not pending. /// /// Note: this deviates from which always returns a block number - #[serde(skip_serializing_if = "Option::is_none")] pub block_hash: Option, /// Block number the transaction is included in, None if pending. /// /// Note: this deviates from which always returns a block number - #[serde(skip_serializing_if = "Option::is_none")] pub block_number: Option, /// Hash of the transaction - #[serde(skip_serializing_if = "Option::is_none")] pub transaction_hash: Option, /// Transaction index within the block, None if pending. - #[serde(skip_serializing_if = "Option::is_none")] pub transaction_position: Option, } +// Implement Serialize manually to ensure consistent ordering of fields to match other client's +// format +impl Serialize for LocalizedTransactionTrace { + fn serialize(&self, serializer: S) -> Result + 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("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() + } +} + /// A record of a full VM trace for a CALL/CREATE. #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -413,6 +482,8 @@ pub struct StorageDelta { #[cfg(test)] mod tests { use super::*; + use serde_json::{json, Value}; + use std::str::FromStr; #[test] fn test_transaction_trace() { @@ -469,4 +540,176 @@ mod tests { let val = serde_json::from_str::(&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::
().unwrap(), + call_type: CallType::DelegateCall, + gas: U64::from(3148955), + input: Bytes::from_str("0x585a9fd40000000000000000000000000000000000000000000000000000000000000040a47c5ad9a4af285720eae6cc174a9c75c5bbaf973b00f1a0c191327445b6581000000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000ce16f69375520ab01377ce7b88f5ba8c48f8d666f61490331372e432315cd97447e3bc452d6c73a6e0536260a88ddab46f85c88d00000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000aab8cf0fbfb038751339cb61161fa11789b41a78f1b7b0e12cf8e467d403590b7a5f26f0000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000646616e746f6d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a3078636531364636393337353532306162303133373763653742383866354241384334384638443636360000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000045553444300000000000000000000000000000000000000000000000000000000").unwrap(), + to: "0x99b5fa03a5ea4315725c43346e55a6a6fbd94098".parse::
().unwrap(), + value: U256::from(0), + }), + error: None, + result: Some(TraceOutput::Call(CallOutput { gas_used: U64::from(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::
().unwrap(), + gas: U64::from(3438907), + init: Bytes::from_str("0x6080604052600160005534801561001557600080fd5b50610324806100256000396000f3fe608060405234801561001057600080fd5b50600436106100355760003560e01c8062f55d9d1461003a5780631cff79cd1461004f575b600080fd5b61004d6100483660046101da565b610079565b005b61006261005d3660046101fc565b6100bb565b60405161007092919061027f565b60405180910390f35b6002600054141561009d5760405163caa30f5560e01b815260040160405180910390fd5b600260005573ffffffffffffffffffffffffffffffffffffffff8116ff5b60006060600260005414156100e35760405163caa30f5560e01b815260040160405180910390fd5b600260005573ffffffffffffffffffffffffffffffffffffffff85163b610136576040517f6f7c43f100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8473ffffffffffffffffffffffffffffffffffffffff16848460405161015d9291906102de565b6000604051808303816000865af19150503d806000811461019a576040519150601f19603f3d011682016040523d82523d6000602084013e61019f565b606091505b50600160005590969095509350505050565b803573ffffffffffffffffffffffffffffffffffffffff811681146101d557600080fd5b919050565b6000602082840312156101ec57600080fd5b6101f5826101b1565b9392505050565b60008060006040848603121561021157600080fd5b61021a846101b1565b9250602084013567ffffffffffffffff8082111561023757600080fd5b818601915086601f83011261024b57600080fd5b81358181111561025a57600080fd5b87602082850101111561026c57600080fd5b6020830194508093505050509250925092565b821515815260006020604081840152835180604085015260005b818110156102b557858101830151858201606001528201610299565b818111156102c7576000606083870101525b50601f01601f191692909201606001949350505050565b818382376000910190815291905056fea264697066735822122032cb5e746816b7fac95205c068b30da37bd40119a57265be331c162cae74712464736f6c63430008090033").unwrap(), + value: U256::from(0), + }), + error: None, + result: Some(TraceOutput::Create(CreateOutput { gas_used: U64::from(183114), address: "0x7eb6c6c1db08c0b9459a68cfdcedab64f319c138".parse::
().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(); + similar_asserts::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(); + similar_asserts::assert_eq!(serialized, reference_data); + } }