cfx_execute_helper/observer/exec_tracer/
error_unwind.rs

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