Skip to main content

foundry_evm_traces/decoder/
precompiles.rs

1use super::MonadHardfork;
2use crate::{CallTrace, DecodedCallData};
3use alloy_primitives::{Address, B256, U256, hex};
4use alloy_sol_types::{SolCall, abi, sol};
5use foundry_config::{Chain, NamedChain};
6use foundry_evm_core::{
7    precompiles::{
8        BLAKE_2F, BLS12_G1ADD, BLS12_G1MSM, BLS12_G2ADD, BLS12_G2MSM, BLS12_MAP_FP_TO_G1,
9        BLS12_MAP_FP2_TO_G2, BLS12_PAIRING_CHECK, CELO_TRANSFER, EC_ADD, EC_MUL, EC_PAIRING,
10        EC_RECOVER, IDENTITY, MOD_EXP, P256_VERIFY, POINT_EVALUATION, RIPEMD_160, SHA_256,
11    },
12    tempo::{TEMPO_PRECOMPILE_ADDRESSES, TEMPO_TIP20_TOKENS, active_tempo_precompile_addresses},
13};
14use foundry_evm_hardforks::TempoHardfork;
15use foundry_evm_networks::NetworkConfigs;
16use itertools::Itertools;
17use revm_inspectors::tracing::types::DecodedCallTrace;
18
19sol! {
20/// EVM precompiles interface. For illustration purposes only, as precompiles don't follow the
21/// Solidity ABI codec.
22///
23/// Parameter names and types are taken from [evm.codes](https://www.evm.codes/precompiled).
24interface Precompiles {
25    struct EcPairingInput {
26        uint256 x1;
27        uint256 y1;
28        uint256 x2;
29        uint256 y2;
30        uint256 x3;
31        uint256 y3;
32    }
33
34    /* 0x01 */ function ecrecover(bytes32 hash, uint8 v, uint256 r, uint256 s) returns (address publicAddress);
35    /* 0x02 */ function sha256(bytes data) returns (bytes32 hash);
36    /* 0x03 */ function ripemd(bytes data) returns (bytes20 hash);
37    /* 0x04 */ function identity(bytes data) returns (bytes data);
38    /* 0x05 */ function modexp(uint256 Bsize, uint256 Esize, uint256 Msize, bytes B, bytes E, bytes M) returns (bytes value);
39    /* 0x06 */ function ecadd(uint256 x1, uint256 y1, uint256 x2, uint256 y2) returns (uint256 x, uint256 y);
40    /* 0x07 */ function ecmul(uint256 x1, uint256 y1, uint256 s) returns (uint256 x, uint256 y);
41    /* 0x08 */ function ecpairing(EcPairingInput[] input) returns (bool success);
42    /* 0x09 */ function blake2f(uint32 rounds, uint64[8] h, uint64[16] m, uint64[2] t, bool f) returns (uint64[8] h);
43    /* 0x0a */ function pointEvaluation(bytes32 versionedHash, bytes32 z, bytes32 y, bytes1[48] commitment, bytes1[48] proof) returns (bytes value);
44
45    // Prague BLS12-381 precompiles (EIP-2537)
46    /* 0x0b */ function bls12G1Add(bytes p1, bytes p2) returns (bytes result);
47    /* 0x0c */ function bls12G1Msm(bytes[] scalarsAndPoints) returns (bytes result);
48    /* 0x0d */ function bls12G2Add(bytes p1, bytes p2) returns (bytes result);
49    /* 0x0e */ function bls12G2Msm(bytes[] scalarsAndPoints) returns (bytes result);
50    /* 0x0f */ function bls12PairingCheck(bytes[] pairs) returns (bool success);
51    /* 0x10 */ function bls12MapFpToG1(bytes fp) returns (bytes result);
52    /* 0x11 */ function bls12MapFp2ToG2(bytes fp2) returns (bytes result);
53
54    // Osaka precompiles (EIP-7212)
55    /* 0x100 */ function p256Verify(bytes32 hash, uint256 r, uint256 s, uint256 qx, uint256 qy) returns (bool success);
56}
57}
58use Precompiles::*;
59
60pub(super) trait Precompile {
61    fn address(&self) -> Address;
62    fn signature(&self, data: &[u8]) -> &'static str;
63
64    fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
65        Ok(vec![hex::encode_prefixed(data)])
66    }
67
68    fn decode_return(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
69        Ok(vec![hex::encode_prefixed(data)])
70    }
71}
72
73// Note: we use the ABI decoder, but this is not necessarily ABI-encoded data. It's just a
74// convenient way to decode the data.
75
76const PRECOMPILES: &[&dyn Precompile] = &[
77    &Ecrecover,
78    &Sha256,
79    &Ripemd160,
80    &Identity,
81    &ModExp,
82    &EcAdd,
83    &Ecmul,
84    &Ecpairing,
85    &Blake2f,
86    &PointEvaluation,
87    &Bls12G1Add,
88    &Bls12G1Msm,
89    &Bls12G2Add,
90    &Bls12G2Msm,
91    &Bls12PairingCheck,
92    &Bls12MapFpToG1,
93    &Bls12MapFp2ToG2,
94    &P256Verify,
95];
96
97struct Ecrecover;
98impl Precompile for Ecrecover {
99    fn address(&self) -> Address {
100        EC_RECOVER
101    }
102
103    fn signature(&self, _: &[u8]) -> &'static str {
104        ecrecoverCall::SIGNATURE
105    }
106
107    fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
108        let ecrecoverCall { hash, v, r, s } = ecrecoverCall::abi_decode_raw(data)?;
109        Ok(vec![hash.to_string(), v.to_string(), r.to_string(), s.to_string()])
110    }
111
112    fn decode_return(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
113        let ret = ecrecoverCall::abi_decode_returns(data)?;
114        Ok(vec![ret.to_string()])
115    }
116}
117
118struct Sha256;
119impl Precompile for Sha256 {
120    fn address(&self) -> Address {
121        SHA_256
122    }
123
124    fn signature(&self, _: &[u8]) -> &'static str {
125        sha256Call::SIGNATURE
126    }
127
128    fn decode_return(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
129        let ret = sha256Call::abi_decode_returns(data)?;
130        Ok(vec![ret.to_string()])
131    }
132}
133
134struct Ripemd160;
135impl Precompile for Ripemd160 {
136    fn address(&self) -> Address {
137        RIPEMD_160
138    }
139
140    fn signature(&self, _: &[u8]) -> &'static str {
141        ripemdCall::SIGNATURE
142    }
143
144    fn decode_return(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
145        let ret = ripemdCall::abi_decode_returns(data)?;
146        Ok(vec![ret.to_string()])
147    }
148}
149
150struct Identity;
151impl Precompile for Identity {
152    fn address(&self) -> Address {
153        IDENTITY
154    }
155
156    fn signature(&self, _: &[u8]) -> &'static str {
157        identityCall::SIGNATURE
158    }
159}
160
161struct ModExp;
162impl Precompile for ModExp {
163    fn address(&self) -> Address {
164        MOD_EXP
165    }
166
167    fn signature(&self, _: &[u8]) -> &'static str {
168        modexpCall::SIGNATURE
169    }
170
171    fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
172        let mut decoder = abi::Decoder::new(data);
173        let b_size = decoder.take_offset()?;
174        let e_size = decoder.take_offset()?;
175        let m_size = decoder.take_offset()?;
176        let b = decoder.take_slice(b_size)?;
177        let e = decoder.take_slice(e_size)?;
178        let m = decoder.take_slice(m_size)?;
179        Ok(vec![
180            b_size.to_string(),
181            e_size.to_string(),
182            m_size.to_string(),
183            hex::encode_prefixed(b),
184            hex::encode_prefixed(e),
185            hex::encode_prefixed(m),
186        ])
187    }
188}
189
190struct EcAdd;
191impl Precompile for EcAdd {
192    fn address(&self) -> Address {
193        EC_ADD
194    }
195
196    fn signature(&self, _: &[u8]) -> &'static str {
197        ecaddCall::SIGNATURE
198    }
199
200    fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
201        let ecaddCall { x1, y1, x2, y2 } = ecaddCall::abi_decode_raw(data)?;
202        Ok(vec![x1.to_string(), y1.to_string(), x2.to_string(), y2.to_string()])
203    }
204
205    fn decode_return(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
206        let ecaddReturn { x, y } = ecaddCall::abi_decode_returns(data)?;
207        Ok(vec![x.to_string(), y.to_string()])
208    }
209}
210
211struct Ecmul;
212impl Precompile for Ecmul {
213    fn address(&self) -> Address {
214        EC_MUL
215    }
216
217    fn signature(&self, _: &[u8]) -> &'static str {
218        ecmulCall::SIGNATURE
219    }
220
221    fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
222        let ecmulCall { x1, y1, s } = ecmulCall::abi_decode_raw(data)?;
223        Ok(vec![x1.to_string(), y1.to_string(), s.to_string()])
224    }
225
226    fn decode_return(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
227        let ecmulReturn { x, y } = ecmulCall::abi_decode_returns(data)?;
228        Ok(vec![x.to_string(), y.to_string()])
229    }
230}
231
232struct Ecpairing;
233impl Precompile for Ecpairing {
234    fn address(&self) -> Address {
235        EC_PAIRING
236    }
237
238    fn signature(&self, _: &[u8]) -> &'static str {
239        ecpairingCall::SIGNATURE
240    }
241
242    fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
243        let mut decoder = abi::Decoder::new(data);
244        let mut values = Vec::new();
245        // input must be either empty or a multiple of 6 32-byte values
246        let mut tmp = <[&B256; 6]>::default();
247        while !decoder.is_empty() {
248            for tmp in &mut tmp {
249                *tmp = decoder.take_word()?;
250            }
251            values.push(iter_to_string(tmp.iter().map(|x| U256::from_be_bytes(x.0))));
252        }
253        Ok(values)
254    }
255
256    fn decode_return(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
257        let ret = ecpairingCall::abi_decode_returns(data)?;
258        Ok(vec![ret.to_string()])
259    }
260}
261
262struct Blake2f;
263impl Precompile for Blake2f {
264    fn address(&self) -> Address {
265        BLAKE_2F
266    }
267
268    fn signature(&self, _: &[u8]) -> &'static str {
269        blake2fCall::SIGNATURE
270    }
271
272    fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
273        decode_blake2f(data)
274    }
275}
276
277fn decode_blake2f<'a>(data: &'a [u8]) -> alloy_sol_types::Result<Vec<String>> {
278    let mut decoder = abi::Decoder::new(data);
279    let rounds = u32::from_be_bytes(decoder.take_slice(4)?.try_into().unwrap());
280    let u64_le_list = |x: &'a [u8]| x.as_chunks::<8>().0.iter().map(|x| u64::from_le_bytes(*x));
281    let h = u64_le_list(decoder.take_slice(64)?);
282    let m = u64_le_list(decoder.take_slice(128)?);
283    let t = u64_le_list(decoder.take_slice(16)?);
284    let f = decoder.take_slice(1)?[0];
285    Ok(vec![
286        rounds.to_string(),
287        iter_to_string(h),
288        iter_to_string(m),
289        iter_to_string(t),
290        f.to_string(),
291    ])
292}
293
294struct PointEvaluation;
295impl Precompile for PointEvaluation {
296    fn address(&self) -> Address {
297        POINT_EVALUATION
298    }
299
300    fn signature(&self, _: &[u8]) -> &'static str {
301        pointEvaluationCall::SIGNATURE
302    }
303
304    fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
305        let mut decoder = abi::Decoder::new(data);
306        let versioned_hash = decoder.take_word()?;
307        let z = decoder.take_word()?;
308        let y = decoder.take_word()?;
309        let commitment = decoder.take_slice(48)?;
310        let proof = decoder.take_slice(48)?;
311        Ok(vec![
312            versioned_hash.to_string(),
313            z.to_string(),
314            y.to_string(),
315            hex::encode_prefixed(commitment),
316            hex::encode_prefixed(proof),
317        ])
318    }
319}
320
321fn iter_to_string<I: Iterator<Item = T>, T: std::fmt::Display>(iter: I) -> String {
322    format!("[{}]", iter.format(", "))
323}
324
325const G1_POINT_SIZE: usize = 128;
326const G2_POINT_SIZE: usize = 256;
327const SCALAR_SIZE: usize = 32;
328const FP_SIZE: usize = 64;
329
330struct Bls12G1Add;
331impl Precompile for Bls12G1Add {
332    fn address(&self) -> Address {
333        BLS12_G1ADD
334    }
335
336    fn signature(&self, _: &[u8]) -> &'static str {
337        bls12G1AddCall::SIGNATURE
338    }
339
340    fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
341        let (p1, rest) = take_at_most(data, G1_POINT_SIZE);
342        let (p2, _) = take_at_most(rest, G1_POINT_SIZE);
343        Ok(vec![hex::encode_prefixed(p1), hex::encode_prefixed(p2)])
344    }
345}
346
347struct Bls12G1Msm;
348impl Precompile for Bls12G1Msm {
349    fn address(&self) -> Address {
350        BLS12_G1MSM
351    }
352
353    fn signature(&self, _: &[u8]) -> &'static str {
354        bls12G1MsmCall::SIGNATURE
355    }
356
357    fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
358        let pair_size = G1_POINT_SIZE + SCALAR_SIZE;
359        Ok(data.chunks(pair_size).map(hex::encode_prefixed).collect())
360    }
361}
362
363struct Bls12G2Add;
364impl Precompile for Bls12G2Add {
365    fn address(&self) -> Address {
366        BLS12_G2ADD
367    }
368
369    fn signature(&self, _: &[u8]) -> &'static str {
370        bls12G2AddCall::SIGNATURE
371    }
372
373    fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
374        let (p1, rest) = take_at_most(data, G2_POINT_SIZE);
375        let (p2, _) = take_at_most(rest, G2_POINT_SIZE);
376        Ok(vec![hex::encode_prefixed(p1), hex::encode_prefixed(p2)])
377    }
378}
379
380struct Bls12G2Msm;
381impl Precompile for Bls12G2Msm {
382    fn address(&self) -> Address {
383        BLS12_G2MSM
384    }
385
386    fn signature(&self, _: &[u8]) -> &'static str {
387        bls12G2MsmCall::SIGNATURE
388    }
389
390    fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
391        let pair_size = G2_POINT_SIZE + SCALAR_SIZE;
392        Ok(data.chunks(pair_size).map(hex::encode_prefixed).collect())
393    }
394}
395
396struct Bls12PairingCheck;
397impl Precompile for Bls12PairingCheck {
398    fn address(&self) -> Address {
399        BLS12_PAIRING_CHECK
400    }
401
402    fn signature(&self, _: &[u8]) -> &'static str {
403        bls12PairingCheckCall::SIGNATURE
404    }
405
406    fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
407        let pair_size = G1_POINT_SIZE + G2_POINT_SIZE;
408        Ok(data.chunks(pair_size).map(hex::encode_prefixed).collect())
409    }
410
411    fn decode_return(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
412        let ret = bls12PairingCheckCall::abi_decode_returns(data)?;
413        Ok(vec![ret.to_string()])
414    }
415}
416
417struct Bls12MapFpToG1;
418impl Precompile for Bls12MapFpToG1 {
419    fn address(&self) -> Address {
420        BLS12_MAP_FP_TO_G1
421    }
422
423    fn signature(&self, _: &[u8]) -> &'static str {
424        bls12MapFpToG1Call::SIGNATURE
425    }
426
427    fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
428        let (fp, _) = take_at_most(data, FP_SIZE);
429        Ok(vec![hex::encode_prefixed(fp)])
430    }
431}
432
433struct Bls12MapFp2ToG2;
434impl Precompile for Bls12MapFp2ToG2 {
435    fn address(&self) -> Address {
436        BLS12_MAP_FP2_TO_G2
437    }
438
439    fn signature(&self, _: &[u8]) -> &'static str {
440        bls12MapFp2ToG2Call::SIGNATURE
441    }
442
443    fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
444        let (fp2, _) = take_at_most(data, G1_POINT_SIZE);
445        Ok(vec![hex::encode_prefixed(fp2)])
446    }
447}
448
449struct P256Verify;
450impl Precompile for P256Verify {
451    fn address(&self) -> Address {
452        P256_VERIFY
453    }
454
455    fn signature(&self, _: &[u8]) -> &'static str {
456        p256VerifyCall::SIGNATURE
457    }
458
459    fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
460        let p256VerifyCall { hash, r, s, qx, qy } = p256VerifyCall::abi_decode_raw(data)?;
461        Ok(vec![hash.to_string(), r.to_string(), s.to_string(), qx.to_string(), qy.to_string()])
462    }
463
464    fn decode_return(&self, data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
465        let ret = p256VerifyCall::abi_decode_returns(data)?;
466        Ok(vec![ret.to_string()])
467    }
468}
469
470fn take_at_most(data: &[u8], n: usize) -> (&[u8], &[u8]) {
471    let n = n.min(data.len());
472    data.split_at(n)
473}
474
475pub(crate) fn is_known_precompile(
476    address: Address,
477    networks: Option<NetworkConfigs>,
478    chain_id: Option<u64>,
479    tempo_hardfork: Option<TempoHardfork>,
480    monad_hardfork: Option<MonadHardfork>,
481) -> bool {
482    #[cfg(not(feature = "monad"))]
483    let _ = monad_hardfork;
484
485    // Standard EVM precompiles (all chains).
486    // An 18-byte zero prefix, as `P256_VERIFY` (0x..0100) occupies the two lowest bytes.
487    let is_standard = address[..18].iter().all(|&x| x == 0)
488        && matches!(
489            address,
490            EC_RECOVER
491                | SHA_256
492                | RIPEMD_160
493                | IDENTITY
494                | MOD_EXP
495                | EC_ADD
496                | EC_MUL
497                | EC_PAIRING
498                | BLAKE_2F
499                | POINT_EVALUATION
500                | BLS12_G1ADD
501                | BLS12_G1MSM
502                | BLS12_G2ADD
503                | BLS12_G2MSM
504                | BLS12_PAIRING_CHECK
505                | BLS12_MAP_FP_TO_G1
506                | BLS12_MAP_FP2_TO_G2
507                | P256_VERIFY
508        );
509    if is_standard {
510        return true;
511    }
512    // Tempo precompiles and TIP20 fee tokens (only on Tempo chains).
513    let is_tempo_precompile = match tempo_hardfork {
514        Some(hardfork) => active_tempo_precompile_addresses(hardfork).any(|addr| addr == address),
515        None => TEMPO_PRECOMPILE_ADDRESSES.contains(&address),
516    };
517    let is_tempo_context = networks.map_or_else(
518        || {
519            chain_id
520                .map(|id| Chain::from_id(id).is_tempo())
521                .unwrap_or_else(|| tempo_hardfork.is_some())
522        },
523        |networks| networks.is_tempo(),
524    );
525    if is_tempo_context && (is_tempo_precompile || TEMPO_TIP20_TOKENS.contains(&address)) {
526        return true;
527    }
528    // Monad precompiles (only on a Monad chain or in an explicitly configured Monad context).
529    #[cfg(feature = "monad")]
530    {
531        let is_monad_context = networks.map_or_else(
532            || {
533                chain_id.is_some_and(|id| {
534                    matches!(
535                        Chain::from_id(id).named(),
536                        Some(NamedChain::Monad | NamedChain::MonadTestnet)
537                    )
538                }) || monad_hardfork.is_some()
539            },
540            |networks| networks.is_monad(),
541        );
542        if is_monad_context {
543            if address == monad_revm::staking::STAKING_ADDRESS {
544                return true;
545            }
546            if address == monad_revm::reserve_balance::abi::RESERVE_BALANCE_ADDRESS
547                && monad_hardfork.is_none_or(|hardfork| {
548                    foundry_evm_networks::is_monad_precompile_active_at(address, hardfork)
549                })
550            {
551                return true;
552            }
553        }
554    }
555    // Celo transfer precompile (only on Celo chains).
556    let is_celo_context = networks.map_or_else(
557        || {
558            chain_id.is_some_and(|id| {
559                matches!(
560                    Chain::from_id(id).named(),
561                    Some(NamedChain::Celo | NamedChain::CeloSepolia)
562                )
563            })
564        },
565        |networks| networks.is_celo(),
566    );
567    is_celo_context && address == CELO_TRANSFER
568}
569
570pub(crate) fn is_known_precompile_call(
571    trace: &CallTrace,
572    networks: Option<NetworkConfigs>,
573    chain_id: Option<u64>,
574    tempo_hardfork: Option<TempoHardfork>,
575    monad_hardfork: Option<MonadHardfork>,
576) -> bool {
577    // Unlike the long-established low addresses, P256 is hardfork-dependent. Traces without an
578    // execution classification, such as RPC callTracer frames, cannot safely infer it by address.
579    if trace.address == P256_VERIFY && trace.maybe_precompile != Some(true) {
580        return false;
581    }
582    is_known_precompile(trace.address, networks, chain_id, tempo_hardfork, monad_hardfork)
583}
584
585/// Tries to decode a precompile call. Returns `Some` if successful.
586pub(super) fn decode(
587    trace: &CallTrace,
588    networks: Option<NetworkConfigs>,
589    chain_id: Option<u64>,
590    tempo_hardfork: Option<TempoHardfork>,
591    monad_hardfork: Option<MonadHardfork>,
592) -> Option<DecodedCallTrace> {
593    if !is_known_precompile_call(trace, networks, chain_id, tempo_hardfork, monad_hardfork) {
594        return None;
595    }
596
597    for &precompile in PRECOMPILES {
598        if trace.address == precompile.address() {
599            let signature = precompile.signature(&trace.data);
600
601            let args = precompile
602                .decode_call(&trace.data)
603                .unwrap_or_else(|_| vec![trace.data.to_string()]);
604
605            let return_data = precompile
606                .decode_return(&trace.output)
607                .unwrap_or_else(|_| vec![trace.output.to_string()]);
608            let return_data = if return_data.len() == 1 {
609                return_data.into_iter().next().unwrap()
610            } else {
611                format!("({})", return_data.join(", "))
612            };
613
614            return Some(DecodedCallTrace {
615                label: Some("PRECOMPILES".to_string()),
616                call_data: Some(DecodedCallData { signature: signature.to_string(), args }),
617                return_data: Some(return_data),
618            });
619        }
620    }
621
622    None
623}
624
625#[cfg(test)]
626mod tests {
627    use super::*;
628    use alloy_primitives::{address, hex};
629
630    #[test]
631    fn known_precompile_boundaries() {
632        assert!(is_known_precompile(P256_VERIFY, None, None, None, None));
633        assert!(!is_known_precompile(
634            address!("0x0000000000000000000000000000000000000101"),
635            None,
636            None,
637            None,
638            None
639        ));
640        assert!(!is_known_precompile(
641            address!("0x0000000000000000000000000000000000000012"),
642            None,
643            None,
644            None,
645            None
646        ));
647    }
648
649    #[test]
650    fn decodes_only_confirmed_p256_precompile_calls() {
651        for maybe_precompile in [None, Some(false)] {
652            let trace = CallTrace { address: P256_VERIFY, maybe_precompile, ..Default::default() };
653            assert!(decode(&trace, None, None, None, None).is_none());
654        }
655
656        let trace =
657            CallTrace { address: P256_VERIFY, maybe_precompile: Some(true), ..Default::default() };
658        assert!(decode(&trace, None, None, None, None).is_some());
659    }
660
661    #[test]
662    fn decodes_established_precompile_despite_negative_execution_classification() {
663        let trace =
664            CallTrace { address: SHA_256, maybe_precompile: Some(false), ..Default::default() };
665        assert!(decode(&trace, None, None, None, None).is_some());
666    }
667
668    #[test]
669    fn ecpairing() {
670        // https://github.com/foundry-rs/foundry/issues/5337#issuecomment-1627384480
671        let data = hex!(
672            "
673            26bbb723f965460ca7282cd75f0e3e7c67b15817f7cee60856b394936ed02917
674            0fbe873ac672168143a91535450bab6c412dce8dc8b66a88f2da6e245f9282df
675            13cd4f0451538ece5014fe6688b197aefcc611a5c6a7c319f834f2188ba04b08
676            126ff07e81490a1b6ae92b2d9e700c8e23e9d5c7f6ab857027213819a6c9ae7d
677            04183624c9858a56c54deb237c26cb4355bc2551312004e65fc5b299440b15a3
678            2e4b11aa549ad6c667057b18be4f4437fda92f018a59430ebb992fa3462c9ca1
679            2d4d9aa7e302d9df41749d5507949d05dbea33fbb16c643b22f599a2be6df2e2
680            14bedd503c37ceb061d8ec60209fe345ce89830a19230301f076caff004d1926
681            0967032fcbf776d1afc985f88877f182d38480a653f2decaa9794cbc3bf3060c
682            0e187847ad4c798374d0d6732bf501847dd68bc0e071241e0213bc7fc13db7ab
683            304cfbd1e08a704a99f5e847d93f8c3caafddec46b7a0d379da69a4d112346a7
684            1739c1b1a457a8c7313123d24d2f9192f896b7c63eea05a9d57f06547ad0cec8
685            001d6fedb032f70e377635238e0563f131670001f6abf439adb3a9d5d52073c6
686            1889afe91e4e367f898a7fcd6464e5ca4e822fe169bccb624f6aeb87e4d060bc
687            198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2
688            1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed
689            090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b
690            12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa
691            2dde6d7baf0bfa09329ec8d44c38282f5bf7f9ead1914edd7dcaebb498c84519
692            0c359f868a85c6e6c1ea819cfab4a867501a3688324d74df1fe76556558b1937
693            29f41c6e0e30802e2749bfb0729810876f3423e6f24829ad3e30adb1934f1c8a
694            030e7a5f70bb5daa6e18d80d6d447e772efb0bb7fb9d0ffcd54fc5a48af1286d
695            0ea726b117e48cda8bce2349405f006a84cdd3dcfba12efc990df25970a27b6d
696            30364cd4f8a293b1a04f0153548d3e01baad091c69097ca4e9f26be63e4095b5
697        "
698        );
699        let decoded = Ecpairing.decode_call(&data).unwrap();
700        // 4 arrays of 6 32-byte values
701        assert_eq!(decoded.len(), 4);
702    }
703}