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, BLS12_G1ADD, BLS12_G1MSM, BLS12_G2ADD, BLS12_G2MSM, BLS12_MAP_FP_TO_G1,
6    BLS12_MAP_FP2_TO_G2, BLS12_PAIRING_CHECK, EC_ADD, EC_MUL, EC_PAIRING, EC_RECOVER, IDENTITY,
7    MOD_EXP, P256_VERIFY, POINT_EVALUATION, RIPEMD_160, SHA_256,
8};
9use itertools::Itertools;
10use revm_inspectors::tracing::types::DecodedCallTrace;
11
12sol! {
13/// EVM precompiles interface. For illustration purposes only, as precompiles don't follow the
14/// Solidity ABI codec.
15///
16/// Parameter names and types are taken from [evm.codes](https://www.evm.codes/precompiled).
17interface Precompiles {
18    struct EcPairingInput {
19        uint256 x1;
20        uint256 y1;
21        uint256 x2;
22        uint256 y2;
23        uint256 x3;
24        uint256 y3;
25    }
26
27    /* 0x01 */ function ecrecover(bytes32 hash, uint8 v, uint256 r, uint256 s) returns (address publicAddress);
28    /* 0x02 */ function sha256(bytes data) returns (bytes32 hash);
29    /* 0x03 */ function ripemd(bytes data) returns (bytes20 hash);
30    /* 0x04 */ function identity(bytes data) returns (bytes data);
31    /* 0x05 */ function modexp(uint256 Bsize, uint256 Esize, uint256 Msize, bytes B, bytes E, bytes M) returns (bytes value);
32    /* 0x06 */ function ecadd(uint256 x1, uint256 y1, uint256 x2, uint256 y2) returns (uint256 x, uint256 y);
33    /* 0x07 */ function ecmul(uint256 x1, uint256 y1, uint256 s) returns (uint256 x, uint256 y);
34    /* 0x08 */ function ecpairing(EcPairingInput[] input) returns (bool success);
35    /* 0x09 */ function blake2f(uint32 rounds, uint64[8] h, uint64[16] m, uint64[2] t, bool f) returns (uint64[8] h);
36    /* 0x0a */ function pointEvaluation(bytes32 versionedHash, bytes32 z, bytes32 y, bytes1[48] commitment, bytes1[48] proof) returns (bytes value);
37
38    // Prague BLS12-381 precompiles (EIP-2537)
39    /* 0x0b */ function bls12G1Add(bytes p1, bytes p2) returns (bytes result);
40    /* 0x0c */ function bls12G1Msm(bytes[] scalarsAndPoints) returns (bytes result);
41    /* 0x0d */ function bls12G2Add(bytes p1, bytes p2) returns (bytes result);
42    /* 0x0e */ function bls12G2Msm(bytes[] scalarsAndPoints) returns (bytes result);
43    /* 0x0f */ function bls12PairingCheck(bytes[] pairs) returns (bool success);
44    /* 0x10 */ function bls12MapFpToG1(bytes fp) returns (bytes result);
45    /* 0x11 */ function bls12MapFp2ToG2(bytes fp2) returns (bytes result);
46
47    // Osaka precompiles (EIP-7212)
48    /* 0x100 */ function p256Verify(bytes32 hash, uint256 r, uint256 s, uint256 qx, uint256 qy) returns (bool success);
49}
50}
51use Precompiles::*;
52
53pub(super) fn is_known_precompile(address: Address, _chain_id: u64) -> bool {
54    address[..19].iter().all(|&x| x == 0)
55        && matches!(
56            address,
57            EC_RECOVER
58                | SHA_256
59                | RIPEMD_160
60                | IDENTITY
61                | MOD_EXP
62                | EC_ADD
63                | EC_MUL
64                | EC_PAIRING
65                | BLAKE_2F
66                | POINT_EVALUATION
67                | BLS12_G1ADD
68                | BLS12_G1MSM
69                | BLS12_G2ADD
70                | BLS12_G2MSM
71                | BLS12_PAIRING_CHECK
72                | BLS12_MAP_FP_TO_G1
73                | BLS12_MAP_FP2_TO_G2
74                | P256_VERIFY
75        )
76}
77
78/// Tries to decode a precompile call. Returns `Some` if successful.
79pub(super) fn decode(trace: &CallTrace, _chain_id: u64) -> Option<DecodedCallTrace> {
80    if !is_known_precompile(trace.address, _chain_id) {
81        return None;
82    }
83
84    for &precompile in PRECOMPILES {
85        if trace.address == precompile.address() {
86            let signature = precompile.signature(&trace.data);
87
88            let args = precompile
89                .decode_call(&trace.data)
90                .unwrap_or_else(|_| vec![trace.data.to_string()]);
91
92            let return_data = precompile
93                .decode_return(&trace.output)
94                .unwrap_or_else(|_| vec![trace.output.to_string()]);
95            let return_data = if return_data.len() == 1 {
96                return_data.into_iter().next().unwrap()
97            } else {
98                format!("({})", return_data.join(", "))
99            };
100
101            return Some(DecodedCallTrace {
102                label: Some("PRECOMPILES".to_string()),
103                call_data: Some(DecodedCallData { signature: signature.to_string(), args }),
104                return_data: Some(return_data),
105            });
106        }
107    }
108
109    None
110}
111
112pub(super) trait Precompile {
113    fn address(&self) -> Address;
114    fn signature(&self, data: &[u8]) -> &'static str;
115
116    fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
117        Ok(vec![hex::encode_prefixed(data)])
118    }
119
120    fn decode_return(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
121        Ok(vec![hex::encode_prefixed(data)])
122    }
123}
124
125// Note: we use the ABI decoder, but this is not necessarily ABI-encoded data. It's just a
126// convenient way to decode the data.
127
128const PRECOMPILES: &[&dyn Precompile] = &[
129    &Ecrecover,
130    &Sha256,
131    &Ripemd160,
132    &Identity,
133    &ModExp,
134    &EcAdd,
135    &Ecmul,
136    &Ecpairing,
137    &Blake2f,
138    &PointEvaluation,
139    &Bls12G1Add,
140    &Bls12G1Msm,
141    &Bls12G2Add,
142    &Bls12G2Msm,
143    &Bls12PairingCheck,
144    &Bls12MapFpToG1,
145    &Bls12MapFp2ToG2,
146    &P256Verify,
147];
148
149struct Ecrecover;
150impl Precompile for Ecrecover {
151    fn address(&self) -> Address {
152        EC_RECOVER
153    }
154
155    fn signature(&self, _: &[u8]) -> &'static str {
156        ecrecoverCall::SIGNATURE
157    }
158
159    fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
160        let ecrecoverCall { hash, v, r, s } = ecrecoverCall::abi_decode_raw(data)?;
161        Ok(vec![hash.to_string(), v.to_string(), r.to_string(), s.to_string()])
162    }
163
164    fn decode_return(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
165        let ret = ecrecoverCall::abi_decode_returns(data)?;
166        Ok(vec![ret.to_string()])
167    }
168}
169
170struct Sha256;
171impl Precompile for Sha256 {
172    fn address(&self) -> Address {
173        SHA_256
174    }
175
176    fn signature(&self, _: &[u8]) -> &'static str {
177        sha256Call::SIGNATURE
178    }
179
180    fn decode_return(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
181        let ret = sha256Call::abi_decode_returns(data)?;
182        Ok(vec![ret.to_string()])
183    }
184}
185
186struct Ripemd160;
187impl Precompile for Ripemd160 {
188    fn address(&self) -> Address {
189        RIPEMD_160
190    }
191
192    fn signature(&self, _: &[u8]) -> &'static str {
193        ripemdCall::SIGNATURE
194    }
195
196    fn decode_return(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
197        let ret = ripemdCall::abi_decode_returns(data)?;
198        Ok(vec![ret.to_string()])
199    }
200}
201
202struct Identity;
203impl Precompile for Identity {
204    fn address(&self) -> Address {
205        IDENTITY
206    }
207
208    fn signature(&self, _: &[u8]) -> &'static str {
209        identityCall::SIGNATURE
210    }
211}
212
213struct ModExp;
214impl Precompile for ModExp {
215    fn address(&self) -> Address {
216        MOD_EXP
217    }
218
219    fn signature(&self, _: &[u8]) -> &'static str {
220        modexpCall::SIGNATURE
221    }
222
223    fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
224        let mut decoder = abi::Decoder::new(data);
225        let b_size = decoder.take_offset()?;
226        let e_size = decoder.take_offset()?;
227        let m_size = decoder.take_offset()?;
228        let b = decoder.take_slice(b_size)?;
229        let e = decoder.take_slice(e_size)?;
230        let m = decoder.take_slice(m_size)?;
231        Ok(vec![
232            b_size.to_string(),
233            e_size.to_string(),
234            m_size.to_string(),
235            hex::encode_prefixed(b),
236            hex::encode_prefixed(e),
237            hex::encode_prefixed(m),
238        ])
239    }
240}
241
242struct EcAdd;
243impl Precompile for EcAdd {
244    fn address(&self) -> Address {
245        EC_ADD
246    }
247
248    fn signature(&self, _: &[u8]) -> &'static str {
249        ecaddCall::SIGNATURE
250    }
251
252    fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
253        let ecaddCall { x1, y1, x2, y2 } = ecaddCall::abi_decode_raw(data)?;
254        Ok(vec![x1.to_string(), y1.to_string(), x2.to_string(), y2.to_string()])
255    }
256
257    fn decode_return(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
258        let ecaddReturn { x, y } = ecaddCall::abi_decode_returns(data)?;
259        Ok(vec![x.to_string(), y.to_string()])
260    }
261}
262
263struct Ecmul;
264impl Precompile for Ecmul {
265    fn address(&self) -> Address {
266        EC_MUL
267    }
268
269    fn signature(&self, _: &[u8]) -> &'static str {
270        ecmulCall::SIGNATURE
271    }
272
273    fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
274        let ecmulCall { x1, y1, s } = ecmulCall::abi_decode_raw(data)?;
275        Ok(vec![x1.to_string(), y1.to_string(), s.to_string()])
276    }
277
278    fn decode_return(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
279        let ecmulReturn { x, y } = ecmulCall::abi_decode_returns(data)?;
280        Ok(vec![x.to_string(), y.to_string()])
281    }
282}
283
284struct Ecpairing;
285impl Precompile for Ecpairing {
286    fn address(&self) -> Address {
287        EC_PAIRING
288    }
289
290    fn signature(&self, _: &[u8]) -> &'static str {
291        ecpairingCall::SIGNATURE
292    }
293
294    fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
295        let mut decoder = abi::Decoder::new(data);
296        let mut values = Vec::new();
297        // input must be either empty or a multiple of 6 32-byte values
298        let mut tmp = <[&B256; 6]>::default();
299        while !decoder.is_empty() {
300            for tmp in &mut tmp {
301                *tmp = decoder.take_word()?;
302            }
303            values.push(iter_to_string(tmp.iter().map(|x| U256::from_be_bytes(x.0))));
304        }
305        Ok(values)
306    }
307
308    fn decode_return(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
309        let ret = ecpairingCall::abi_decode_returns(data)?;
310        Ok(vec![ret.to_string()])
311    }
312}
313
314struct Blake2f;
315impl Precompile for Blake2f {
316    fn address(&self) -> Address {
317        BLAKE_2F
318    }
319
320    fn signature(&self, _: &[u8]) -> &'static str {
321        blake2fCall::SIGNATURE
322    }
323
324    fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
325        decode_blake2f(data)
326    }
327}
328
329fn decode_blake2f<'a>(data: &'a [u8]) -> alloy_sol_types::Result<Vec<String>> {
330    let mut decoder = abi::Decoder::new(data);
331    let rounds = u32::from_be_bytes(decoder.take_slice(4)?.try_into().unwrap());
332    let u64_le_list =
333        |x: &'a [u8]| x.chunks_exact(8).map(|x| u64::from_le_bytes(x.try_into().unwrap()));
334    let h = u64_le_list(decoder.take_slice(64)?);
335    let m = u64_le_list(decoder.take_slice(128)?);
336    let t = u64_le_list(decoder.take_slice(16)?);
337    let f = decoder.take_slice(1)?[0];
338    Ok(vec![
339        rounds.to_string(),
340        iter_to_string(h),
341        iter_to_string(m),
342        iter_to_string(t),
343        f.to_string(),
344    ])
345}
346
347struct PointEvaluation;
348impl Precompile for PointEvaluation {
349    fn address(&self) -> Address {
350        POINT_EVALUATION
351    }
352
353    fn signature(&self, _: &[u8]) -> &'static str {
354        pointEvaluationCall::SIGNATURE
355    }
356
357    fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
358        let mut decoder = abi::Decoder::new(data);
359        let versioned_hash = decoder.take_word()?;
360        let z = decoder.take_word()?;
361        let y = decoder.take_word()?;
362        let commitment = decoder.take_slice(48)?;
363        let proof = decoder.take_slice(48)?;
364        Ok(vec![
365            versioned_hash.to_string(),
366            z.to_string(),
367            y.to_string(),
368            hex::encode_prefixed(commitment),
369            hex::encode_prefixed(proof),
370        ])
371    }
372}
373
374fn iter_to_string<I: Iterator<Item = T>, T: std::fmt::Display>(iter: I) -> String {
375    format!("[{}]", iter.format(", "))
376}
377
378const G1_POINT_SIZE: usize = 128;
379const G2_POINT_SIZE: usize = 256;
380const SCALAR_SIZE: usize = 32;
381const FP_SIZE: usize = 64;
382
383struct Bls12G1Add;
384impl Precompile for Bls12G1Add {
385    fn address(&self) -> Address {
386        BLS12_G1ADD
387    }
388
389    fn signature(&self, _: &[u8]) -> &'static str {
390        bls12G1AddCall::SIGNATURE
391    }
392
393    fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
394        let (p1, rest) = take_at_most(data, G1_POINT_SIZE);
395        let (p2, _) = take_at_most(rest, G1_POINT_SIZE);
396        Ok(vec![hex::encode_prefixed(p1), hex::encode_prefixed(p2)])
397    }
398}
399
400struct Bls12G1Msm;
401impl Precompile for Bls12G1Msm {
402    fn address(&self) -> Address {
403        BLS12_G1MSM
404    }
405
406    fn signature(&self, _: &[u8]) -> &'static str {
407        bls12G1MsmCall::SIGNATURE
408    }
409
410    fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
411        let pair_size = G1_POINT_SIZE + SCALAR_SIZE;
412        Ok(data.chunks(pair_size).map(hex::encode_prefixed).collect())
413    }
414}
415
416struct Bls12G2Add;
417impl Precompile for Bls12G2Add {
418    fn address(&self) -> Address {
419        BLS12_G2ADD
420    }
421
422    fn signature(&self, _: &[u8]) -> &'static str {
423        bls12G2AddCall::SIGNATURE
424    }
425
426    fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
427        let (p1, rest) = take_at_most(data, G2_POINT_SIZE);
428        let (p2, _) = take_at_most(rest, G2_POINT_SIZE);
429        Ok(vec![hex::encode_prefixed(p1), hex::encode_prefixed(p2)])
430    }
431}
432
433struct Bls12G2Msm;
434impl Precompile for Bls12G2Msm {
435    fn address(&self) -> Address {
436        BLS12_G2MSM
437    }
438
439    fn signature(&self, _: &[u8]) -> &'static str {
440        bls12G2MsmCall::SIGNATURE
441    }
442
443    fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
444        let pair_size = G2_POINT_SIZE + SCALAR_SIZE;
445        Ok(data.chunks(pair_size).map(hex::encode_prefixed).collect())
446    }
447}
448
449struct Bls12PairingCheck;
450impl Precompile for Bls12PairingCheck {
451    fn address(&self) -> Address {
452        BLS12_PAIRING_CHECK
453    }
454
455    fn signature(&self, _: &[u8]) -> &'static str {
456        bls12PairingCheckCall::SIGNATURE
457    }
458
459    fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
460        let pair_size = G1_POINT_SIZE + G2_POINT_SIZE;
461        Ok(data.chunks(pair_size).map(hex::encode_prefixed).collect())
462    }
463
464    fn decode_return(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
465        let ret = bls12PairingCheckCall::abi_decode_returns(data)?;
466        Ok(vec![ret.to_string()])
467    }
468}
469
470struct Bls12MapFpToG1;
471impl Precompile for Bls12MapFpToG1 {
472    fn address(&self) -> Address {
473        BLS12_MAP_FP_TO_G1
474    }
475
476    fn signature(&self, _: &[u8]) -> &'static str {
477        bls12MapFpToG1Call::SIGNATURE
478    }
479
480    fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
481        let (fp, _) = take_at_most(data, FP_SIZE);
482        Ok(vec![hex::encode_prefixed(fp)])
483    }
484}
485
486struct Bls12MapFp2ToG2;
487impl Precompile for Bls12MapFp2ToG2 {
488    fn address(&self) -> Address {
489        BLS12_MAP_FP2_TO_G2
490    }
491
492    fn signature(&self, _: &[u8]) -> &'static str {
493        bls12MapFp2ToG2Call::SIGNATURE
494    }
495
496    fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
497        let (fp2, _) = take_at_most(data, G1_POINT_SIZE);
498        Ok(vec![hex::encode_prefixed(fp2)])
499    }
500}
501
502struct P256Verify;
503impl Precompile for P256Verify {
504    fn address(&self) -> Address {
505        P256_VERIFY
506    }
507
508    fn signature(&self, _: &[u8]) -> &'static str {
509        p256VerifyCall::SIGNATURE
510    }
511
512    fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
513        let p256VerifyCall { hash, r, s, qx, qy } = p256VerifyCall::abi_decode_raw(data)?;
514        Ok(vec![hash.to_string(), r.to_string(), s.to_string(), qx.to_string(), qy.to_string()])
515    }
516
517    fn decode_return(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
518        let ret = p256VerifyCall::abi_decode_returns(data)?;
519        Ok(vec![ret.to_string()])
520    }
521}
522
523fn take_at_most(data: &[u8], n: usize) -> (&[u8], &[u8]) {
524    let n = n.min(data.len());
525    data.split_at(n)
526}
527
528#[cfg(test)]
529mod tests {
530    use super::*;
531    use alloy_primitives::hex;
532
533    #[test]
534    fn ecpairing() {
535        // https://github.com/foundry-rs/foundry/issues/5337#issuecomment-1627384480
536        let data = hex!(
537            "
538            26bbb723f965460ca7282cd75f0e3e7c67b15817f7cee60856b394936ed02917
539            0fbe873ac672168143a91535450bab6c412dce8dc8b66a88f2da6e245f9282df
540            13cd4f0451538ece5014fe6688b197aefcc611a5c6a7c319f834f2188ba04b08
541            126ff07e81490a1b6ae92b2d9e700c8e23e9d5c7f6ab857027213819a6c9ae7d
542            04183624c9858a56c54deb237c26cb4355bc2551312004e65fc5b299440b15a3
543            2e4b11aa549ad6c667057b18be4f4437fda92f018a59430ebb992fa3462c9ca1
544            2d4d9aa7e302d9df41749d5507949d05dbea33fbb16c643b22f599a2be6df2e2
545            14bedd503c37ceb061d8ec60209fe345ce89830a19230301f076caff004d1926
546            0967032fcbf776d1afc985f88877f182d38480a653f2decaa9794cbc3bf3060c
547            0e187847ad4c798374d0d6732bf501847dd68bc0e071241e0213bc7fc13db7ab
548            304cfbd1e08a704a99f5e847d93f8c3caafddec46b7a0d379da69a4d112346a7
549            1739c1b1a457a8c7313123d24d2f9192f896b7c63eea05a9d57f06547ad0cec8
550            001d6fedb032f70e377635238e0563f131670001f6abf439adb3a9d5d52073c6
551            1889afe91e4e367f898a7fcd6464e5ca4e822fe169bccb624f6aeb87e4d060bc
552            198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2
553            1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed
554            090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b
555            12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa
556            2dde6d7baf0bfa09329ec8d44c38282f5bf7f9ead1914edd7dcaebb498c84519
557            0c359f868a85c6e6c1ea819cfab4a867501a3688324d74df1fe76556558b1937
558            29f41c6e0e30802e2749bfb0729810876f3423e6f24829ad3e30adb1934f1c8a
559            030e7a5f70bb5daa6e18d80d6d447e772efb0bb7fb9d0ffcd54fc5a48af1286d
560            0ea726b117e48cda8bce2349405f006a84cdd3dcfba12efc990df25970a27b6d
561            30364cd4f8a293b1a04f0153548d3e01baad091c69097ca4e9f26be63e4095b5
562        "
563        );
564        let decoded = Ecpairing.decode_call(&data).unwrap();
565        // 4 arrays of 6 32-byte values
566        assert_eq!(decoded.len(), 4);
567    }
568}