cfx_execute_helper/observer/exec_tracer/error_unwind.rs
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 108 109
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(_) => {}
                Action::SetAuth(_) => {}
                Action::SelfDestruct(_) => {}
            }
        }
        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"),
            ),
        }
    }
}