use super::blockscout::BlockscoutCallInner;
use crate::types::{
single::{Call, Log, TransactionTrace},
CallResult, CallType, CreateResult,
};
use crate::listeners::call_list::Listener;
use crate::types::serialization::*;
use serde::Serialize;
use crate::types::block::BlockTransactionTrace;
use ethereum_types::{H160, U256};
use parity_scale_codec::{Decode, Encode};
use sp_std::{cmp::Ordering, vec::Vec};
pub struct Formatter;
impl super::ResponseFormatter for Formatter {
type Listener = Listener;
type Response = Vec<BlockTransactionTrace>;
fn format(listener: Listener) -> Option<Vec<BlockTransactionTrace>> {
let mut traces = Vec::new();
for (eth_tx_index, entry) in listener.entries.iter().enumerate() {
if entry.is_empty() {
log::debug!(
target: "tracing",
"Empty trace entry with transaction index {}, skipping...", eth_tx_index
);
continue;
}
let mut result: Vec<Call> = entry
.into_iter()
.map(|(_, it)| {
let from = it.from;
let trace_address = it.trace_address.clone();
let value = it.value;
let gas = it.gas;
let gas_used = it.gas_used;
let inner = it.inner.clone();
Call::CallTracer(CallTracerCall {
from: from,
gas: gas,
gas_used: gas_used,
trace_address: Some(trace_address.clone()),
inner: match inner.clone() {
BlockscoutCallInner::Call {
input,
to,
res,
call_type,
} => CallTracerInner::Call {
call_type: match call_type {
CallType::Call => "CALL".as_bytes().to_vec(),
CallType::CallCode => "CALLCODE".as_bytes().to_vec(),
CallType::DelegateCall => "DELEGATECALL".as_bytes().to_vec(),
CallType::StaticCall => "STATICCALL".as_bytes().to_vec(),
},
to,
input,
res: res.clone(),
value: Some(value),
logs: match res {
CallResult::Output { .. } => it.logs.clone(),
CallResult::Error { .. } => Vec::new(),
},
},
BlockscoutCallInner::Create { init, res } => CallTracerInner::Create {
input: init,
error: match res {
CreateResult::Success { .. } => None,
CreateResult::Error { ref error } => Some(error.clone()),
},
to: match res {
CreateResult::Success {
created_contract_address_hash,
..
} => Some(created_contract_address_hash),
CreateResult::Error { .. } => None,
},
output: match res {
CreateResult::Success {
created_contract_code,
..
} => Some(created_contract_code),
CreateResult::Error { .. } => None,
},
value: value,
call_type: "CREATE".as_bytes().to_vec(),
},
BlockscoutCallInner::SelfDestruct { balance, to } => {
CallTracerInner::SelfDestruct {
value: balance,
to,
call_type: "SELFDESTRUCT".as_bytes().to_vec(),
}
}
},
calls: Vec::new(),
})
})
.collect();
if result.len() > 1 {
result.sort_by(|a, b| match (a, b) {
(
Call::CallTracer(CallTracerCall {
trace_address: Some(a),
..
}),
Call::CallTracer(CallTracerCall {
trace_address: Some(b),
..
}),
) => {
let a_len = a.len();
let b_len = b.len();
let sibling_greater_than = |a: &Vec<u32>, b: &Vec<u32>| -> bool {
for (i, a_value) in a.iter().enumerate() {
if a_value > &b[i] {
return true;
} else if a_value < &b[i] {
return false;
} else {
continue;
}
}
return false;
};
if b_len > a_len || (a_len == b_len && sibling_greater_than(&a, &b)) {
Ordering::Less
} else {
Ordering::Greater
}
}
_ => unreachable!(),
});
while result.len() > 1 {
let mut last = result
.pop()
.expect("result.len() > 1, so pop() necessarily returns an element");
if let Some(index) =
result
.iter()
.position(|current| match (last.clone(), current) {
(
Call::CallTracer(CallTracerCall {
trace_address: Some(a),
..
}),
Call::CallTracer(CallTracerCall {
trace_address: Some(b),
..
}),
) => {
&b[..]
== a.get(0..a.len() - 1).expect(
"non-root element while traversing trace result",
)
}
_ => unreachable!(),
}) {
if let Call::CallTracer(CallTracerCall {
ref mut trace_address,
..
}) = last
{
*trace_address = None;
}
if let Some(Call::CallTracer(CallTracerCall { calls, .. })) =
result.get_mut(index)
{
calls.push(last);
}
}
}
}
if let Some(Call::CallTracer(CallTracerCall { trace_address, .. })) = result.get_mut(0)
{
*trace_address = None;
}
if result.len() == 1 {
traces.push(BlockTransactionTrace {
tx_position: eth_tx_index as u32,
tx_hash: Default::default(),
result: TransactionTrace::CallListNested(
result
.pop()
.expect("result.len() == 1, so pop() necessarily returns this element"),
),
});
}
}
if traces.is_empty() {
return None;
}
return Some(traces);
}
}
#[derive(Clone, Eq, PartialEq, Debug, Encode, Decode, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CallTracerCall {
pub from: H160,
#[serde(skip_serializing_if = "Option::is_none")]
pub trace_address: Option<Vec<u32>>,
pub gas: U256,
pub gas_used: U256,
#[serde(flatten)]
pub inner: CallTracerInner,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub calls: Vec<Call>,
}
#[derive(Clone, Eq, PartialEq, Debug, Encode, Decode, Serialize)]
#[serde(untagged)]
pub enum CallTracerInner {
Call {
#[serde(rename = "type", serialize_with = "opcode_serialize")]
call_type: Vec<u8>,
to: H160,
#[serde(serialize_with = "bytes_0x_serialize")]
input: Vec<u8>,
#[serde(flatten)]
res: CallResult,
#[serde(skip_serializing_if = "Option::is_none")]
value: Option<U256>,
#[serde(skip_serializing_if = "Vec::is_empty")]
logs: Vec<Log>,
},
Create {
#[serde(rename = "type", serialize_with = "opcode_serialize")]
call_type: Vec<u8>,
#[serde(serialize_with = "bytes_0x_serialize")]
input: Vec<u8>,
#[serde(skip_serializing_if = "Option::is_none")]
to: Option<H160>,
#[serde(
skip_serializing_if = "Option::is_none",
serialize_with = "option_bytes_0x_serialize"
)]
output: Option<Vec<u8>>,
#[serde(
skip_serializing_if = "Option::is_none",
serialize_with = "option_string_serialize"
)]
error: Option<Vec<u8>>,
value: U256,
},
SelfDestruct {
#[serde(rename = "type", serialize_with = "opcode_serialize")]
call_type: Vec<u8>,
to: H160,
value: U256,
},
}