1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
use super::{
action_types::{Action, Call, CallResult, Create, CreateResult, Outcome},
trace_types::ExecTrace,
ExecTraceKey,
};
use cfx_executor::executive::Executed;
use cfx_types::Address;
use solidity_abi::string_revert_reason_decode;
/// An executive tracer only records errors during EVM unwind.
///
/// When the first error happens, `ErrorUnwind` tries to maintain a list for
/// error description and code address for triggering error. However, if the
/// tracer met a successful result or a sub-call/create, it will regard this
/// error as "caught" and clear the error list.
#[derive(Default)]
pub struct ErrorUnwind {
callstack: Vec<Address>,
pub errors: Vec<(Address, String)>,
}
impl ErrorUnwind {
pub fn from_executed(executed: &Executed) -> Self {
Self::from_traces(
executed.ext_result.get::<ExecTraceKey>().unwrap_or(&vec![]),
)
}
pub fn from_traces(traces: &[ExecTrace]) -> Self {
let mut errors = ErrorUnwind::default();
for trace in traces.iter() {
match &trace.action {
Action::Call(call) => errors.accept_call(call),
Action::Create(create) => errors.accept_create(create),
Action::CallResult(result) => errors.accept_call_result(result),
Action::CreateResult(result) => {
errors.accept_create_result(result)
}
Action::InternalTransferAction(_) => {}
}
}
errors
}
// If contract A calls contract B, contract B returns with an exception (vm
// error or reverted), but contract A makes another sub-call, we think
// contract A catches this error and clear the error list.
fn accept_call(&mut self, call: &Call) {
self.callstack.push(call.to);
self.errors.clear();
}
fn accept_create(&mut self, _create: &Create) { self.errors.clear(); }
fn accept_call_result(&mut self, result: &CallResult) {
let address = self
.callstack
.pop()
.expect("trace call and their results must be matched");
match Self::error_message(&result.outcome, &result.return_data) {
// If contract A calls contract B, contract B returns with an
// exception (vm error or reverted), and contract A then
// also returns with an exception, we think contract A is
// propagating out the exception.
Some(message) => self.errors.push((address, message)),
// If contract A calls contract B, contract B returns with an
// exception (vm error or reverted), but contract A
// returns with success, we think contract A catches this error and
// clear the error list.
None => self.errors.clear(),
}
}
fn accept_create_result(&mut self, result: &CreateResult) {
let address = result.addr;
match Self::error_message(&result.outcome, &result.return_data) {
// If contract A calls contract B, contract B returns with an
// exception (vm error or reverted), and contract A then
// also returns with an exception, we think contract A is
// propagating out the exception.
Some(message) => self.errors.push((address, message)),
// If contract A calls contract B, contract B returns with an
// exception (vm error or reverted), but contract A
// returns with success, we think contract A catches this error and
// clear the error list.
None => self.errors.clear(),
}
}
fn error_message(
outcome: &Outcome, return_data: &Vec<u8>,
) -> Option<String> {
match outcome {
Outcome::Success => None,
Outcome::Reverted => Some(format!(
"Vm reverted. {}",
string_revert_reason_decode(return_data)
)),
Outcome::Fail => Some(
String::from_utf8(return_data.clone())
.expect("Return data is encoded from valid utf-8 string"),
),
}
}
}