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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
//! Connection to external EVM tracer.

use crate::go;
use core::fmt::{Display, Formatter, Result as FmtResult};
use std::ffi::{CStr, CString};

/// Creates the trace
pub fn trace(config: &str) -> Result<String, Error> {
    // Create a string we can pass into Go
    let c_config = CString::new(config).expect("invalid config");

    // Generate the trace externally
    let result = unsafe { go::CreateTrace(c_config.as_ptr()) };

    // Convert the returned string to something we can use in Rust again.
    // Also make sure the returned data is copied to rust managed memory.
    let c_result = unsafe { CStr::from_ptr(result) };
    let result = c_result
        .to_str()
        .expect("Error translating EVM trace from library")
        .to_string();

    // We can now free the returned string (memory managed by Go)
    unsafe { go::FreeString(c_result.as_ptr()) };

    // Return the trace
    match result.is_empty() || result.starts_with("Failed") {
        true => Ok(format!(
            "[\n{{\n\"gas\": 0,\n\"failed\": true,\n\"invalid\": true,\n\"returnValue\": \"{result}\",\n\"structLogs\": []\n}}\n]"
        )),
        false => Ok(result),
    }
}

/// Error type for any geth-utils related failure.
#[derive(Debug, Clone)]
pub enum Error {
    /// Error while tracing.
    TracingError(String),
}

impl Display for Error {
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
        write!(f, "{:?}", self)
    }
}

#[cfg(test)]
mod test {
    use eth_types::GethExecTrace;

    use crate::trace;

    #[test]
    fn valid_tx() {
        for config in [
            // Minimal call tx with gas_limit = 21000
            r#"{
                "block_constants": {
                    "gas_limit": "0x52080"
                },
                "transactions": [
                    {
                        "from": "0x00000000000000000000000000000000000000fe",
                        "to": "0x00000000000000000000000000000000000000ff",
                        "gas_limit": "0x5208"
                    }
                ]
            }"#,
            // Minimal creation tx with gas_limit = 53000
            r#"{
                "block_constants": {
                    "gas_limit": "0xcf080"
                },
                "transactions": [
                    {
                        "from": "0x00000000000000000000000000000000000000fe",
                        "gas_limit": "0xcf08"
                    }
                ]
            }"#,
            // Normal call tx with gas_limit = 21000 and gas_price = 2 Gwei
            r#"{
                "block_constants": {
                    "gas_limit": "0x52080"
                },
                "accounts": {
                    "0x00000000000000000000000000000000000000fe": {
                        "balance": "0x2632e314a000"
                    }
                },
                "transactions": [
                    {
                        "from": "0x00000000000000000000000000000000000000fe",
                        "to": "0x00000000000000000000000000000000000000ff",
                        "gas_limit": "0x5208",
                        "gas_price": "0x77359400"
                    }
                ]
            }"#,
        ] {
            let trace_result = trace(config);
            assert!(trace_result.is_ok());
            // Run over the traces
            let trace: Vec<GethExecTrace> = serde_json::from_str(&trace_result.unwrap()).unwrap();
            for trace in trace.iter() {
                assert!(!trace.invalid);
            }
        }
    }

    #[test]
    fn invalid_tx() {
        for config in [
            // Insufficient gas for intrinsic usage
            r#"{
                "block_constants": {
                    "gas_limit": "0xcf080"
                },
                "transactions": [
                    {
                        "from": "0x00000000000000000000000000000000000000fe",
                        "to": "0x00000000000000000000000000000000000000ff"
                    }
                ]
            }"#,
            // Insufficient balance to buy gas
            r#"{
                "block_constants": {
                    "gas_limit": "0x52080"
                },
                "transactions": [
                    {
                        "from": "0x00000000000000000000000000000000000000fe",
                        "to": "0x00000000000000000000000000000000000000ff",
                        "gas_limit": "0x5208",
                        "gas_price": "0x1111"
                    }
                ]
            }"#,
            // Insufficient balance to do the first transfer
            r#"{
                "block_constants": {
                    "gas_limit": "0x52080"
                },
                "transactions": [
                    {
                        "from": "0x00000000000000000000000000000000000000fe",
                        "to": "0x00000000000000000000000000000000000000ff",
                        "value": "0x100",
                        "gas_limit": "0x5208"
                    }
                ]
            }"#,
        ] {
            let trace_result = trace(config);
            assert!(trace_result.is_ok());
            // Run over the traces
            let trace: Vec<GethExecTrace> = serde_json::from_str(&trace_result.unwrap()).unwrap();
            for trace in trace.iter() {
                assert!(trace.invalid);
            }
        }
    }
}