foundry_evm_traces/decoder/
precompiles.rs

1use crate::{CallTrace, DecodedCallData};
2use alloy_primitives::{Address, B256, U256, hex};
3use alloy_sol_types::{SolCall, abi, sol};
4use foundry_evm_core::precompiles::{
5    BLAKE_2F, EC_ADD, EC_MUL, EC_PAIRING, EC_RECOVER, IDENTITY, MOD_EXP, POINT_EVALUATION,
6    RIPEMD_160, SHA_256,
7};
8use itertools::Itertools;
9use revm_inspectors::tracing::types::DecodedCallTrace;
10
11sol! {
12/// EVM precompiles interface. For illustration purposes only, as precompiles don't follow the
13/// Solidity ABI codec.
14///
15/// Parameter names and types are taken from [evm.codes](https://www.evm.codes/precompiled).
16interface Precompiles {
17    struct EcPairingInput {
18        uint256 x1;
19        uint256 y1;
20        uint256 x2;
21        uint256 y2;
22        uint256 x3;
23        uint256 y3;
24    }
25
26    /* 0x01 */ function ecrecover(bytes32 hash, uint8 v, uint256 r, uint256 s) returns (address publicAddress);
27    /* 0x02 */ function sha256(bytes data) returns (bytes32 hash);
28    /* 0x03 */ function ripemd(bytes data) returns (bytes20 hash);
29    /* 0x04 */ function identity(bytes data) returns (bytes data);
30    /* 0x05 */ function modexp(uint256 Bsize, uint256 Esize, uint256 Msize, bytes B, bytes E, bytes M) returns (bytes value);
31    /* 0x06 */ function ecadd(uint256 x1, uint256 y1, uint256 x2, uint256 y2) returns (uint256 x, uint256 y);
32    /* 0x07 */ function ecmul(uint256 x1, uint256 y1, uint256 s) returns (uint256 x, uint256 y);
33    /* 0x08 */ function ecpairing(EcPairingInput[] input) returns (bool success);
34    /* 0x09 */ function blake2f(uint32 rounds, uint64[8] h, uint64[16] m, uint64[2] t, bool f) returns (uint64[8] h);
35    /* 0x0a */ function pointEvaluation(bytes32 versionedHash, bytes32 z, bytes32 y, bytes1[48] commitment, bytes1[48] proof) returns (bytes value);
36}
37}
38use Precompiles::*;
39
40pub(super) fn is_known_precompile(address: Address, _chain_id: u64) -> bool {
41    address[..19].iter().all(|&x| x == 0)
42        && matches!(
43            address,
44            EC_RECOVER
45                | SHA_256
46                | RIPEMD_160
47                | IDENTITY
48                | MOD_EXP
49                | EC_ADD
50                | EC_MUL
51                | EC_PAIRING
52                | BLAKE_2F
53                | POINT_EVALUATION
54        )
55}
56
57/// Tries to decode a precompile call. Returns `Some` if successful.
58pub(super) fn decode(trace: &CallTrace, _chain_id: u64) -> Option<DecodedCallTrace> {
59    if !is_known_precompile(trace.address, _chain_id) {
60        return None;
61    }
62
63    for &precompile in PRECOMPILES {
64        if trace.address == precompile.address() {
65            let signature = precompile.signature();
66
67            let args = precompile
68                .decode_call(&trace.data)
69                .unwrap_or_else(|_| vec![trace.data.to_string()]);
70
71            let return_data = precompile
72                .decode_return(&trace.output)
73                .unwrap_or_else(|_| vec![trace.output.to_string()]);
74            let return_data = if return_data.len() == 1 {
75                return_data.into_iter().next().unwrap()
76            } else {
77                format!("({})", return_data.join(", "))
78            };
79
80            return Some(DecodedCallTrace {
81                label: Some("PRECOMPILES".to_string()),
82                call_data: Some(DecodedCallData { signature: signature.to_string(), args }),
83                return_data: Some(return_data),
84            });
85        }
86    }
87
88    None
89}
90
91pub(super) trait Precompile {
92    fn address(&self) -> Address;
93    fn signature(&self) -> &'static str;
94
95    fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
96        Ok(vec![hex::encode_prefixed(data)])
97    }
98
99    fn decode_return(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
100        Ok(vec![hex::encode_prefixed(data)])
101    }
102}
103
104// Note: we use the ABI decoder, but this is not necessarily ABI-encoded data. It's just a
105// convenient way to decode the data.
106
107const PRECOMPILES: &[&dyn Precompile] = &[
108    &Ecrecover,
109    &Sha256,
110    &Ripemd160,
111    &Identity,
112    &ModExp,
113    &EcAdd,
114    &Ecmul,
115    &Ecpairing,
116    &Blake2f,
117    &PointEvaluation,
118];
119
120struct Ecrecover;
121impl Precompile for Ecrecover {
122    fn address(&self) -> Address {
123        EC_RECOVER
124    }
125
126    fn signature(&self) -> &'static str {
127        ecrecoverCall::SIGNATURE
128    }
129
130    fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
131        let ecrecoverCall { hash, v, r, s } = ecrecoverCall::abi_decode_raw(data)?;
132        Ok(vec![hash.to_string(), v.to_string(), r.to_string(), s.to_string()])
133    }
134
135    fn decode_return(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
136        let ret = ecrecoverCall::abi_decode_returns(data)?;
137        Ok(vec![ret.to_string()])
138    }
139}
140
141struct Sha256;
142impl Precompile for Sha256 {
143    fn address(&self) -> Address {
144        SHA_256
145    }
146
147    fn signature(&self) -> &'static str {
148        sha256Call::SIGNATURE
149    }
150
151    fn decode_return(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
152        let ret = sha256Call::abi_decode_returns(data)?;
153        Ok(vec![ret.to_string()])
154    }
155}
156
157struct Ripemd160;
158impl Precompile for Ripemd160 {
159    fn address(&self) -> Address {
160        RIPEMD_160
161    }
162
163    fn signature(&self) -> &'static str {
164        ripemdCall::SIGNATURE
165    }
166
167    fn decode_return(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
168        let ret = ripemdCall::abi_decode_returns(data)?;
169        Ok(vec![ret.to_string()])
170    }
171}
172
173struct Identity;
174impl Precompile for Identity {
175    fn address(&self) -> Address {
176        IDENTITY
177    }
178
179    fn signature(&self) -> &'static str {
180        identityCall::SIGNATURE
181    }
182}
183
184struct ModExp;
185impl Precompile for ModExp {
186    fn address(&self) -> Address {
187        MOD_EXP
188    }
189
190    fn signature(&self) -> &'static str {
191        modexpCall::SIGNATURE
192    }
193
194    fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
195        let mut decoder = abi::Decoder::new(data);
196        let b_size = decoder.take_offset()?;
197        let e_size = decoder.take_offset()?;
198        let m_size = decoder.take_offset()?;
199        let b = decoder.take_slice(b_size)?;
200        let e = decoder.take_slice(e_size)?;
201        let m = decoder.take_slice(m_size)?;
202        Ok(vec![
203            b_size.to_string(),
204            e_size.to_string(),
205            m_size.to_string(),
206            hex::encode_prefixed(b),
207            hex::encode_prefixed(e),
208            hex::encode_prefixed(m),
209        ])
210    }
211}
212
213struct EcAdd;
214impl Precompile for EcAdd {
215    fn address(&self) -> Address {
216        EC_ADD
217    }
218
219    fn signature(&self) -> &'static str {
220        ecaddCall::SIGNATURE
221    }
222
223    fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
224        let ecaddCall { x1, y1, x2, y2 } = ecaddCall::abi_decode_raw(data)?;
225        Ok(vec![x1.to_string(), y1.to_string(), x2.to_string(), y2.to_string()])
226    }
227
228    fn decode_return(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
229        let ecaddReturn { x, y } = ecaddCall::abi_decode_returns(data)?;
230        Ok(vec![x.to_string(), y.to_string()])
231    }
232}
233
234struct Ecmul;
235impl Precompile for Ecmul {
236    fn address(&self) -> Address {
237        EC_MUL
238    }
239
240    fn signature(&self) -> &'static str {
241        ecmulCall::SIGNATURE
242    }
243
244    fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
245        let ecmulCall { x1, y1, s } = ecmulCall::abi_decode_raw(data)?;
246        Ok(vec![x1.to_string(), y1.to_string(), s.to_string()])
247    }
248
249    fn decode_return(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
250        let ecmulReturn { x, y } = ecmulCall::abi_decode_returns(data)?;
251        Ok(vec![x.to_string(), y.to_string()])
252    }
253}
254
255struct Ecpairing;
256impl Precompile for Ecpairing {
257    fn address(&self) -> Address {
258        EC_PAIRING
259    }
260
261    fn signature(&self) -> &'static str {
262        ecpairingCall::SIGNATURE
263    }
264
265    fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
266        let mut decoder = abi::Decoder::new(data);
267        let mut values = Vec::new();
268        // input must be either empty or a multiple of 6 32-byte values
269        let mut tmp = <[&B256; 6]>::default();
270        while !decoder.is_empty() {
271            for tmp in &mut tmp {
272                *tmp = decoder.take_word()?;
273            }
274            values.push(iter_to_string(tmp.iter().map(|x| U256::from_be_bytes(x.0))));
275        }
276        Ok(values)
277    }
278
279    fn decode_return(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
280        let ret = ecpairingCall::abi_decode_returns(data)?;
281        Ok(vec![ret.to_string()])
282    }
283}
284
285struct Blake2f;
286impl Precompile for Blake2f {
287    fn address(&self) -> Address {
288        BLAKE_2F
289    }
290
291    fn signature(&self) -> &'static str {
292        blake2fCall::SIGNATURE
293    }
294
295    fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
296        decode_blake2f(data)
297    }
298}
299
300fn decode_blake2f<'a>(data: &'a [u8]) -> alloy_sol_types::Result<Vec<String>> {
301    let mut decoder = abi::Decoder::new(data);
302    let rounds = u32::from_be_bytes(decoder.take_slice(4)?.try_into().unwrap());
303    let u64_le_list =
304        |x: &'a [u8]| x.chunks_exact(8).map(|x| u64::from_le_bytes(x.try_into().unwrap()));
305    let h = u64_le_list(decoder.take_slice(64)?);
306    let m = u64_le_list(decoder.take_slice(128)?);
307    let t = u64_le_list(decoder.take_slice(16)?);
308    let f = decoder.take_slice(1)?[0];
309    Ok(vec![
310        rounds.to_string(),
311        iter_to_string(h),
312        iter_to_string(m),
313        iter_to_string(t),
314        f.to_string(),
315    ])
316}
317
318struct PointEvaluation;
319impl Precompile for PointEvaluation {
320    fn address(&self) -> Address {
321        POINT_EVALUATION
322    }
323
324    fn signature(&self) -> &'static str {
325        pointEvaluationCall::SIGNATURE
326    }
327
328    fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
329        let mut decoder = abi::Decoder::new(data);
330        let versioned_hash = decoder.take_word()?;
331        let z = decoder.take_word()?;
332        let y = decoder.take_word()?;
333        let commitment = decoder.take_slice(48)?;
334        let proof = decoder.take_slice(48)?;
335        Ok(vec![
336            versioned_hash.to_string(),
337            z.to_string(),
338            y.to_string(),
339            hex::encode_prefixed(commitment),
340            hex::encode_prefixed(proof),
341        ])
342    }
343}
344
345fn iter_to_string<I: Iterator<Item = T>, T: std::fmt::Display>(iter: I) -> String {
346    format!("[{}]", iter.format(", "))
347}
348
349#[cfg(test)]
350mod tests {
351    use super::*;
352    use alloy_primitives::hex;
353
354    #[test]
355    fn ecpairing() {
356        // https://github.com/foundry-rs/foundry/issues/5337#issuecomment-1627384480
357        let data = hex!(
358            "
359            26bbb723f965460ca7282cd75f0e3e7c67b15817f7cee60856b394936ed02917
360            0fbe873ac672168143a91535450bab6c412dce8dc8b66a88f2da6e245f9282df
361            13cd4f0451538ece5014fe6688b197aefcc611a5c6a7c319f834f2188ba04b08
362            126ff07e81490a1b6ae92b2d9e700c8e23e9d5c7f6ab857027213819a6c9ae7d
363            04183624c9858a56c54deb237c26cb4355bc2551312004e65fc5b299440b15a3
364            2e4b11aa549ad6c667057b18be4f4437fda92f018a59430ebb992fa3462c9ca1
365            2d4d9aa7e302d9df41749d5507949d05dbea33fbb16c643b22f599a2be6df2e2
366            14bedd503c37ceb061d8ec60209fe345ce89830a19230301f076caff004d1926
367            0967032fcbf776d1afc985f88877f182d38480a653f2decaa9794cbc3bf3060c
368            0e187847ad4c798374d0d6732bf501847dd68bc0e071241e0213bc7fc13db7ab
369            304cfbd1e08a704a99f5e847d93f8c3caafddec46b7a0d379da69a4d112346a7
370            1739c1b1a457a8c7313123d24d2f9192f896b7c63eea05a9d57f06547ad0cec8
371            001d6fedb032f70e377635238e0563f131670001f6abf439adb3a9d5d52073c6
372            1889afe91e4e367f898a7fcd6464e5ca4e822fe169bccb624f6aeb87e4d060bc
373            198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2
374            1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed
375            090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b
376            12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa
377            2dde6d7baf0bfa09329ec8d44c38282f5bf7f9ead1914edd7dcaebb498c84519
378            0c359f868a85c6e6c1ea819cfab4a867501a3688324d74df1fe76556558b1937
379            29f41c6e0e30802e2749bfb0729810876f3423e6f24829ad3e30adb1934f1c8a
380            030e7a5f70bb5daa6e18d80d6d447e772efb0bb7fb9d0ffcd54fc5a48af1286d
381            0ea726b117e48cda8bce2349405f006a84cdd3dcfba12efc990df25970a27b6d
382            30364cd4f8a293b1a04f0153548d3e01baad091c69097ca4e9f26be63e4095b5
383        "
384        );
385        let decoded = Ecpairing.decode_call(&data).unwrap();
386        // 4 arrays of 6 32-byte values
387        assert_eq!(decoded.len(), 4);
388    }
389}