Skip to main content

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