Skip to main content

ethrex_levm/
precompiles.rs

1use bytes::{Buf, Bytes};
2use ethrex_common::H160;
3use ethrex_common::utils::u256_from_big_endian_const;
4use ethrex_common::{
5    Address, H256, U256, types::Fork, types::Fork::*, utils::u256_from_big_endian,
6};
7use ethrex_crypto::{Crypto, CryptoError};
8use rustc_hash::FxHashMap;
9use std::borrow::Cow;
10use std::collections::{BTreeMap, BTreeSet, hash_map::Entry};
11use std::sync::RwLock;
12
13use crate::gas_cost::{MODEXP_STATIC_COST, P256_VERIFY_COST};
14use crate::vm::VMType;
15use crate::{
16    constants::VERSIONED_HASH_VERSION_KZG,
17    errors::{InternalError, PrecompileError, VMError},
18    gas_cost::{
19        self, BLAKE2F_ROUND_COST, BLS12_381_G1_K_DISCOUNT, BLS12_381_G1ADD_COST,
20        BLS12_381_G2_K_DISCOUNT, BLS12_381_G2ADD_COST, BLS12_381_MAP_FP_TO_G1_COST,
21        BLS12_381_MAP_FP2_TO_G2_COST, ECADD_COST, ECMUL_COST, G1_MUL_COST, G2_MUL_COST,
22        POINT_EVALUATION_COST,
23    },
24};
25
26pub const BLAKE2F_ELEMENT_SIZE: usize = 8;
27
28pub const SIZE_PRECOMPILES_PRE_CANCUN: u64 = 9;
29pub const SIZE_PRECOMPILES_CANCUN: u64 = 10;
30pub const SIZE_PRECOMPILES_PRAGUE: u64 = 17;
31
32pub const BLS12_381_G1_MSM_PAIR_LENGTH: usize = 160;
33pub const BLS12_381_G2_MSM_PAIR_LENGTH: usize = 288;
34pub const BLS12_381_PAIRING_CHECK_PAIR_LENGTH: usize = 384;
35
36const BLS12_381_FP2_VALID_INPUT_LENGTH: usize = 128;
37const BLS12_381_FP_VALID_INPUT_LENGTH: usize = 64;
38
39pub const FIELD_ELEMENT_WITHOUT_PADDING_LENGTH: usize = 48;
40pub const PADDED_FIELD_ELEMENT_SIZE_IN_BYTES: usize = 64;
41
42pub const G1_POINT_AT_INFINITY: [u8; 128] = [0_u8; 128];
43pub const G2_POINT_AT_INFINITY: [u8; 256] = [0_u8; 256];
44
45/// Precomputed result for MAP_FP2_TO_G2 when input is (Fp::zero(), Fp::zero()).
46/// The bls12_381 crate's map_to_curve does not handle the zero Fp2 case correctly,
47/// so we return the known-correct padded result directly.
48const FP2_ZERO_MAPPED_TO_G2: [u8; 256] = [
49    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 131, 32, 137, 110, 201, 238, 249, 213, 230,
50    25, 132, 141, 194, 156, 226, 102, 244, 19, 208, 45, 211, 29, 155, 157, 68, 236, 12, 121, 205,
51    97, 241, 139, 7, 93, 219, 166, 215, 189, 32, 183, 255, 39, 164, 179, 36, 191, 206, 0, 0, 0, 0,
52    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 103, 209, 33, 24, 181, 163, 91, 176, 45, 46, 134, 179,
53    235, 250, 126, 35, 65, 13, 185, 61, 227, 159, 176, 109, 112, 37, 250, 149, 233, 111, 250, 66,
54    138, 122, 39, 195, 174, 77, 212, 180, 11, 210, 81, 172, 101, 136, 146, 0, 0, 0, 0, 0, 0, 0, 0,
55    0, 0, 0, 0, 0, 0, 0, 0, 2, 96, 224, 54, 68, 209, 162, 195, 33, 37, 107, 50, 70, 186, 210, 184,
56    149, 202, 209, 56, 144, 203, 230, 248, 93, 245, 81, 6, 160, 211, 52, 96, 79, 177, 67, 199, 160,
57    66, 216, 120, 0, 98, 113, 134, 91, 195, 89, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
58    4, 198, 151, 119, 164, 63, 11, 218, 7, 103, 157, 88, 5, 230, 63, 24, 207, 78, 14, 124, 97, 18,
59    172, 127, 112, 38, 109, 25, 155, 79, 118, 174, 39, 198, 38, 154, 60, 238, 189, 174, 48, 128,
60    110, 154, 118, 170, 223, 92,
61];
62
63pub struct Precompile {
64    pub address: H160,
65    pub name: &'static str,
66    pub active_since_fork: Fork,
67}
68
69pub const ECRECOVER: Precompile = Precompile {
70    address: H160([
71        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
72        0x00, 0x00, 0x00, 0x00, 0x01,
73    ]),
74    name: "ECREC",
75    active_since_fork: Paris,
76};
77
78pub const SHA2_256: Precompile = Precompile {
79    address: H160([
80        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
81        0x00, 0x00, 0x00, 0x00, 0x02,
82    ]),
83    name: "SHA256",
84    active_since_fork: Paris,
85};
86
87pub const RIPEMD_160: Precompile = Precompile {
88    address: H160([
89        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
90        0x00, 0x00, 0x00, 0x00, 0x03,
91    ]),
92    name: "RIPEMD160",
93    active_since_fork: Paris,
94};
95
96pub const IDENTITY: Precompile = Precompile {
97    address: H160([
98        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
99        0x00, 0x00, 0x00, 0x00, 0x04,
100    ]),
101    name: "ID",
102    active_since_fork: Paris,
103};
104
105pub const MODEXP: Precompile = Precompile {
106    address: H160([
107        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
108        0x00, 0x00, 0x00, 0x00, 0x05,
109    ]),
110    name: "MODEXP",
111    active_since_fork: Paris,
112};
113
114pub const ECADD: Precompile = Precompile {
115    address: H160([
116        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
117        0x00, 0x00, 0x00, 0x00, 0x06,
118    ]),
119    name: "BN254_ADD",
120    active_since_fork: Paris,
121};
122
123pub const ECMUL: Precompile = Precompile {
124    address: H160([
125        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
126        0x00, 0x00, 0x00, 0x00, 0x07,
127    ]),
128    name: "BN254_MUL",
129    active_since_fork: Paris,
130};
131
132pub const ECPAIRING: Precompile = Precompile {
133    address: H160([
134        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
135        0x00, 0x00, 0x00, 0x00, 0x08,
136    ]),
137    name: "BN254_PAIRING",
138    active_since_fork: Paris,
139};
140
141pub const BLAKE2F: Precompile = Precompile {
142    address: H160([
143        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
144        0x00, 0x00, 0x00, 0x00, 0x09,
145    ]),
146    name: "BLAKE2F",
147    active_since_fork: Paris,
148};
149
150pub const POINT_EVALUATION: Precompile = Precompile {
151    address: H160([
152        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
153        0x00, 0x00, 0x00, 0x00, 0x0a,
154    ]),
155    name: "KZG_POINT_EVALUATION",
156    active_since_fork: Cancun,
157};
158
159pub const BLS12_G1ADD: Precompile = Precompile {
160    address: H160([
161        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
162        0x00, 0x00, 0x00, 0x00, 0x0b,
163    ]),
164    name: "BLS12_G1ADD",
165    active_since_fork: Prague,
166};
167
168pub const BLS12_G1MSM: Precompile = Precompile {
169    address: H160([
170        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
171        0x00, 0x00, 0x00, 0x00, 0x0c,
172    ]),
173    name: "BLS12_G1MSM",
174    active_since_fork: Prague,
175};
176
177pub const BLS12_G2ADD: Precompile = Precompile {
178    address: H160([
179        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
180        0x00, 0x00, 0x00, 0x00, 0x0d,
181    ]),
182    name: "BLS12_G2ADD",
183    active_since_fork: Prague,
184};
185
186pub const BLS12_G2MSM: Precompile = Precompile {
187    address: H160([
188        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
189        0x00, 0x00, 0x00, 0x00, 0x0e,
190    ]),
191    name: "BLS12_G2MSM",
192    active_since_fork: Prague,
193};
194
195pub const BLS12_PAIRING_CHECK: Precompile = Precompile {
196    address: H160([
197        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
198        0x00, 0x00, 0x00, 0x00, 0x0f,
199    ]),
200    name: "BLS12_PAIRING_CHECK",
201    active_since_fork: Prague,
202};
203
204pub const BLS12_MAP_FP_TO_G1: Precompile = Precompile {
205    address: H160([
206        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
207        0x00, 0x00, 0x00, 0x00, 0x10,
208    ]),
209    name: "BLS12_MAP_FP_TO_G1",
210    active_since_fork: Prague,
211};
212
213pub const BLS12_MAP_FP2_TO_G2: Precompile = Precompile {
214    address: H160([
215        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
216        0x00, 0x00, 0x00, 0x00, 0x11,
217    ]),
218    name: "BLS12_MAP_FP2_TO_G2",
219    active_since_fork: Prague,
220};
221
222pub const P256VERIFY: Precompile = Precompile {
223    address: H160([
224        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
225        0x00, 0x00, 0x00, 0x01, 0x00,
226    ]),
227    name: "P256VERIFY",
228    active_since_fork: Osaka,
229};
230
231/// EXECUTE precompile address (0x0101) for Native Rollups (EIP-8079).
232/// Active only on L1 at or above `Fork::LStar`. The L1-only + LStar gate is
233/// enforced in `is_precompile` (the `matches!(vm_type, VMType::L1)` check);
234/// `execute_precompile`'s dispatch only re-checks `fork >= LStar` and relies on
235/// `is_precompile` having already rejected the L2 case at the call site.
236/// NOTE: `EXECUTE` is intentionally NOT in the `PRECOMPILES` array, so its
237/// `active_since_fork` below is never consulted by `precompiles_for_fork` — the
238/// gating is entirely the hand-written checks above. The field is set for
239/// documentation only.
240pub const EXECUTE: Precompile = Precompile {
241    address: H160([
242        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
243        0x00, 0x00, 0x00, 0x01, 0x01,
244    ]),
245    name: "EXECUTE",
246    active_since_fork: LStar,
247};
248
249pub const PRECOMPILES: [Precompile; 18] = [
250    ECRECOVER,
251    SHA2_256,
252    RIPEMD_160,
253    IDENTITY,
254    MODEXP,
255    ECADD,
256    ECMUL,
257    ECPAIRING,
258    BLAKE2F,
259    POINT_EVALUATION,
260    BLS12_G1ADD,
261    BLS12_G1MSM,
262    BLS12_G2ADD,
263    BLS12_G2MSM,
264    BLS12_PAIRING_CHECK,
265    BLS12_MAP_FP_TO_G1,
266    BLS12_MAP_FP2_TO_G2,
267    P256VERIFY,
268];
269
270pub fn precompiles_for_fork(fork: Fork) -> impl Iterator<Item = Precompile> {
271    PRECOMPILES
272        .into_iter()
273        .filter(move |precompile| precompile.active_since_fork <= fork)
274}
275
276pub fn is_precompile(address: &Address, fork: Fork, vm_type: VMType) -> bool {
277    if fork >= Fork::LStar && matches!(vm_type, VMType::L1) && *address == EXECUTE.address {
278        return true;
279    }
280    (matches!(vm_type, VMType::L2(_)) && *address == P256VERIFY.address)
281        || precompiles_for_fork(fork).any(|precompile| precompile.address == *address)
282}
283
284/// Precompile-dispatch changes requested by a geth State Override Set. Simulation-only:
285/// this is `None` on every consensus path, in which case precompile dispatch is
286/// bit-identical to [`is_precompile`].
287///
288/// Two independent effects, both matching geth's `StateOverride.Apply`:
289///
290/// - `movePrecompileToAddress` makes a destination address dispatch the precompile that
291///   lives at the named source.
292/// - **Any** overridden address stops dispatching as a precompile. geth does
293///   `delete(precompiles, addr)` for every address in the override set, not only the
294///   sources of a move, so overriding `0x04`'s balance alone takes the identity precompile
295///   out of the active set and leaves an ordinary account behind.
296#[derive(Debug, Default, Clone, PartialEq, Eq)]
297pub struct PrecompileMoves {
298    /// Overridden addresses, which no longer dispatch as precompiles.
299    suppressed: BTreeSet<Address>,
300    /// Destination -> the original precompile address whose implementation runs there.
301    relocated: BTreeMap<Address, Address>,
302}
303
304impl PrecompileMoves {
305    /// Build from the `(precompile_address, destination_address)` relocations and the full
306    /// set of overridden addresses.
307    ///
308    /// A later pair wins if two moves name the same destination, which is also what
309    /// geth's `StateOverride.Apply` does: it assigns `precompiles[dest]` per move, in
310    /// address order, and only refuses a destination that is itself overridden.
311    pub fn from_parts(
312        moves: impl IntoIterator<Item = (Address, Address)>,
313        overridden: impl IntoIterator<Item = Address>,
314    ) -> Self {
315        let mut result = Self {
316            suppressed: overridden.into_iter().collect(),
317            relocated: BTreeMap::new(),
318        };
319        for (source, destination) in moves {
320            result.relocated.insert(destination, source);
321        }
322        result
323    }
324
325    pub fn is_empty(&self) -> bool {
326        self.suppressed.is_empty() && self.relocated.is_empty()
327    }
328
329    /// True if `address` is overridden and so no longer dispatches as a precompile.
330    pub fn is_suppressed(&self, address: &Address) -> bool {
331        self.suppressed.contains(address)
332    }
333
334    /// The address whose precompile implementation should run when `address` is
335    /// called, or `None` if `address` is not a relocation destination.
336    pub fn source_for(&self, address: &Address) -> Option<Address> {
337        self.relocated.get(address).copied()
338    }
339}
340
341/// [`is_precompile`], honoring any simulation-only precompile relocations.
342///
343/// With `moves == None` this is exactly [`is_precompile`] — the consensus path.
344pub fn is_precompile_with_moves(
345    address: &Address,
346    fork: Fork,
347    vm_type: VMType,
348    moves: Option<&PrecompileMoves>,
349) -> bool {
350    let Some(moves) = moves else {
351        return is_precompile(address, fork, vm_type);
352    };
353    // A destination wins over the address's own identity: `movePrecompileToAddress`
354    // may legitimately target an address that is itself a precompile. A destination
355    // cannot also be overridden — the RPC layer rejects that, as geth does — so this
356    // never has to arbitrate between a relocation and a suppression.
357    if let Some(source) = moves.source_for(address) {
358        return is_precompile(&source, fork, vm_type);
359    }
360    if moves.is_suppressed(address) {
361        return false;
362    }
363    is_precompile(address, fork, vm_type)
364}
365
366/// The address whose precompile implementation should execute for a call to
367/// `address`. Identity mapping unless `address` is a relocation destination.
368pub fn effective_precompile_address(address: Address, moves: Option<&PrecompileMoves>) -> Address {
369    moves
370        .and_then(|m| m.source_for(&address))
371        .unwrap_or(address)
372}
373
374/// Upper bound on the memory one [`PrecompileCache`] may hold. A cache lives for a
375/// single block, and a block's working set sits far below this, so in normal operation
376/// no result is ever turned away.
377pub const PRECOMPILE_CACHE_MAX_BYTES: usize = 128 * 1024 * 1024;
378
379#[derive(Default)]
380struct PrecompileCacheEntries {
381    map: FxHashMap<(Address, Bytes), (Bytes, u64)>,
382    /// Sum of [`PrecompileCache::entry_size`] over `map`.
383    used_bytes: usize,
384}
385
386/// Per-block cache for precompile results shared between warmer and executor.
387///
388/// Holds at most [`PRECOMPILE_CACHE_MAX_BYTES`], as counted by
389/// [`PrecompileCache::entry_size`]; once full, further results are simply not cached.
390/// Nothing is evicted: the cache is dropped with its block, so there is no recency worth
391/// tracking, and lookups stay on the shared read lock.
392#[derive(Default)]
393pub struct PrecompileCache {
394    cache: RwLock<PrecompileCacheEntries>,
395}
396
397impl PrecompileCache {
398    pub fn new() -> Self {
399        Self::default()
400    }
401
402    /// Bytes one entry is charged against the budget: its calldata and output, plus
403    /// the fixed size of the key and value it is stored as. Hash-table slack is not
404    /// counted, so the budget is approximate, not an exact resident size.
405    pub fn entry_size(calldata_len: usize, output_len: usize) -> usize {
406        calldata_len
407            .saturating_add(output_len)
408            .saturating_add(size_of::<((Address, Bytes), (Bytes, u64))>())
409    }
410
411    pub fn get(&self, address: &Address, calldata: &Bytes) -> Option<(Bytes, u64)> {
412        // Graceful degradation: if the lock is poisoned (a thread panicked while
413        // holding it), skip the cache rather than propagating the panic. The cache
414        // is a pure optimization — missing it only costs a recomputation.
415        self.cache
416            .read()
417            .unwrap_or_else(|poisoned| poisoned.into_inner())
418            .map
419            .get(&(*address, calldata.clone()))
420            .cloned()
421    }
422
423    pub fn insert(&self, address: Address, calldata: Bytes, output: Bytes, gas_cost: u64) {
424        let entry_size = Self::entry_size(calldata.len(), output.len());
425        let mut guard = self
426            .cache
427            .write()
428            .unwrap_or_else(|poisoned| poisoned.into_inner());
429        let entries = &mut *guard;
430        let used_bytes = entries.used_bytes.saturating_add(entry_size);
431        if used_bytes > PRECOMPILE_CACHE_MAX_BYTES {
432            return;
433        }
434        // The warmer and the executor can both compute the same call; the result is
435        // identical, so keep the first one and charge it once.
436        if let Entry::Vacant(slot) = entries.map.entry((address, calldata)) {
437            slot.insert((output, gas_cost));
438            entries.used_bytes = used_bytes;
439        }
440    }
441}
442
443#[expect(clippy::as_conversions, clippy::indexing_slicing)]
444pub fn execute_precompile(
445    address: Address,
446    calldata: &Bytes,
447    gas_remaining: &mut u64,
448    fork: Fork,
449    cache: Option<&PrecompileCache>,
450    crypto: &dyn Crypto,
451    _stateless_validator: Option<&dyn crate::StatelessValidator>,
452) -> Result<Bytes, VMError> {
453    type PrecompileFn = fn(&Bytes, &mut u64, Fork, &dyn Crypto) -> Result<Bytes, VMError>;
454
455    const PRECOMPILES: [Option<PrecompileFn>; 512] = const {
456        let mut precompiles = [const { None }; 512];
457        precompiles[ECRECOVER.address.0[19] as usize] = Some(ecrecover as PrecompileFn);
458        precompiles[IDENTITY.address.0[19] as usize] = Some(identity as PrecompileFn);
459        precompiles[SHA2_256.address.0[19] as usize] = Some(sha2_256 as PrecompileFn);
460        precompiles[RIPEMD_160.address.0[19] as usize] = Some(ripemd_160 as PrecompileFn);
461        precompiles[MODEXP.address.0[19] as usize] = Some(modexp as PrecompileFn);
462        precompiles[ECADD.address.0[19] as usize] = Some(ecadd as PrecompileFn);
463        precompiles[ECMUL.address.0[19] as usize] = Some(ecmul as PrecompileFn);
464        precompiles[ECPAIRING.address.0[19] as usize] = Some(ecpairing as PrecompileFn);
465        precompiles[BLAKE2F.address.0[19] as usize] = Some(blake2f as PrecompileFn);
466        precompiles[POINT_EVALUATION.address.0[19] as usize] =
467            Some(point_evaluation as PrecompileFn);
468        precompiles[BLS12_G1ADD.address.0[19] as usize] = Some(bls12_g1add as PrecompileFn);
469        precompiles[BLS12_G1MSM.address.0[19] as usize] = Some(bls12_g1msm as PrecompileFn);
470        precompiles[BLS12_G2ADD.address.0[19] as usize] = Some(bls12_g2add as PrecompileFn);
471        precompiles[BLS12_G2MSM.address.0[19] as usize] = Some(bls12_g2msm as PrecompileFn);
472        precompiles[BLS12_PAIRING_CHECK.address.0[19] as usize] =
473            Some(bls12_pairing_check as PrecompileFn);
474        precompiles[BLS12_MAP_FP_TO_G1.address.0[19] as usize] =
475            Some(bls12_map_fp_to_g1 as PrecompileFn);
476        precompiles[BLS12_MAP_FP2_TO_G2.address.0[19] as usize] =
477            Some(bls12_map_fp2_to_g2 as PrecompileFn);
478        precompiles
479            [u16::from_be_bytes([P256VERIFY.address.0[18], P256VERIFY.address.0[19]]) as usize] =
480            Some(p_256_verify as PrecompileFn);
481        precompiles
482    };
483
484    // EXECUTE precompile is dispatched before the const table (LStar runtime gate;
485    // L1-only enforcement is at the is_precompile call site).
486    if fork >= Fork::LStar && address == EXECUTE.address {
487        return crate::execute_precompile::execute_precompile(
488            calldata,
489            gas_remaining,
490            fork,
491            crypto,
492            _stateless_validator,
493        );
494    }
495
496    if address[0..18] != [0u8; 18] {
497        return Err(VMError::Internal(InternalError::InvalidPrecompileAddress));
498    }
499    let index = u16::from_be_bytes([address[18], address[19]]) as usize;
500
501    let precompile = PRECOMPILES
502        .get(index)
503        .copied()
504        .flatten()
505        .ok_or(VMError::Internal(InternalError::InvalidPrecompileAddress))?;
506
507    // Check cache (skip identity -- copy is cheaper than lookup)
508    if address != IDENTITY.address
509        && let Some((output, gas_cost)) = cache.and_then(|c| c.get(&address, calldata))
510    {
511        increase_precompile_consumed_gas(gas_cost, gas_remaining)?;
512        return Ok(output);
513    }
514
515    #[cfg(feature = "perf_opcode_timings")]
516    let precompile_time_start = std::time::Instant::now();
517
518    let gas_before = *gas_remaining;
519    let result = precompile(calldata, gas_remaining, fork, crypto);
520
521    #[cfg(feature = "perf_opcode_timings")]
522    {
523        let time = precompile_time_start.elapsed();
524        let mut timings = crate::timings::PRECOMPILES_TIMINGS.lock().expect("poison");
525        timings.update(address, time);
526    }
527
528    // Cache result on success (skip identity)
529    if address != IDENTITY.address
530        && let Some(cache) = cache
531        && let Ok(output) = &result
532    {
533        let gas_cost = gas_before.saturating_sub(*gas_remaining);
534        cache.insert(address, calldata.clone(), output.clone(), gas_cost);
535    }
536
537    result
538}
539
540/// Consumes gas and if it's higher than the gas limit returns an error.
541pub(crate) fn increase_precompile_consumed_gas(
542    gas_cost: u64,
543    gas_remaining: &mut u64,
544) -> Result<(), VMError> {
545    *gas_remaining = gas_remaining
546        .checked_sub(gas_cost)
547        .ok_or(PrecompileError::NotEnoughGas)?;
548    Ok(())
549}
550
551/// When slice length is less than `target_len`, the rest is filled with zeros. If slice length is
552/// more than `target_len`, the excess bytes are kept.
553#[inline(always)]
554pub(crate) fn fill_with_zeros(calldata: &Bytes, target_len: usize) -> Bytes {
555    if calldata.len() >= target_len {
556        // this clone is cheap (Arc)
557        return calldata.clone();
558    }
559    let mut padded_calldata = calldata.to_vec();
560    padded_calldata.resize(target_len, 0);
561    padded_calldata.into()
562}
563
564fn crypto_error_to_precompile(e: CryptoError) -> VMError {
565    match e {
566        CryptoError::InvalidPoint(_) => PrecompileError::InvalidPoint.into(),
567        CryptoError::InvalidInput(_) => PrecompileError::ParsingInputError.into(),
568        CryptoError::InvalidSignature => PrecompileError::ParsingInputError.into(),
569        CryptoError::InvalidRecoveryId => PrecompileError::ParsingInputError.into(),
570        CryptoError::RecoveryFailed => PrecompileError::ParsingInputError.into(),
571        CryptoError::VerificationFailed => PrecompileError::ParsingInputError.into(),
572        CryptoError::Other(_) => PrecompileError::ParsingInputError.into(),
573        // Misconfiguration rather than bad input: the BLS12-381 backend is
574        // unavailable (host built without `blst`, or a provider failed to
575        // override the trait default). Surface it as an internal error.
576        CryptoError::Unsupported(msg) => InternalError::Custom(msg.to_string()).into(),
577    }
578}
579
580/// Map crypto errors for BLS12-381 G1 operations (add).
581/// The old code returned BLS12381G1PointNotInCurve for invalid G1 points.
582fn bls12_g1_crypto_error(e: CryptoError) -> VMError {
583    match e {
584        CryptoError::InvalidPoint(_) => PrecompileError::BLS12381G1PointNotInCurve.into(),
585        other => crypto_error_to_precompile(other),
586    }
587}
588
589/// Map crypto errors for BLS12-381 G2 operations (add).
590/// The old code returned BLS12381G2PointNotInCurve for invalid G2 points.
591fn bls12_g2_crypto_error(e: CryptoError) -> VMError {
592    match e {
593        CryptoError::InvalidPoint(_) => PrecompileError::BLS12381G2PointNotInCurve.into(),
594        other => crypto_error_to_precompile(other),
595    }
596}
597
598/// Map crypto errors for BLS12-381 MSM and pairing operations.
599/// The old code used `G1Affine::from_uncompressed` (checked), which returned
600/// ParsingInputError for any invalid point (not on curve or not in subgroup).
601fn bls12_msm_pairing_crypto_error(e: CryptoError) -> VMError {
602    match e {
603        CryptoError::InvalidPoint(_) => PrecompileError::ParsingInputError.into(),
604        other => crypto_error_to_precompile(other),
605    }
606}
607
608/// ## ECRECOVER precompile.
609/// Elliptic curve digital signature algorithm (ECDSA) public key recovery function.
610///
611/// Input is 128 bytes (padded with zeros if shorter):
612///   [0..32)  : keccak-256 hash (message digest)
613///   [32..64) : v (27 or 28)
614///   [64..128): r||s (64 bytes)
615///
616/// Returns the recovered address.
617pub fn ecrecover(
618    calldata: &Bytes,
619    gas_remaining: &mut u64,
620    _fork: Fork,
621    crypto: &dyn Crypto,
622) -> Result<Bytes, VMError> {
623    use crate::gas_cost::ECRECOVER_COST;
624
625    increase_precompile_consumed_gas(ECRECOVER_COST, gas_remaining)?;
626
627    const INPUT_LEN: usize = 128;
628    const WORD: usize = 32;
629
630    let input = fill_with_zeros(calldata, INPUT_LEN);
631
632    #[expect(
633        clippy::indexing_slicing,
634        reason = "fill_with_zeros guarantees len >= 128"
635    )]
636    let raw_hash: &[u8] = &input[0..WORD];
637    #[expect(
638        clippy::indexing_slicing,
639        reason = "fill_with_zeros guarantees len >= 128"
640    )]
641    let raw_v: &[u8] = &input[WORD..WORD * 2];
642    #[expect(
643        clippy::indexing_slicing,
644        reason = "fill_with_zeros guarantees len >= 128"
645    )]
646    let raw_sig: &[u8] = &input[WORD * 2..WORD * 2 + 64];
647
648    // EVM expects v ∈ {27, 28}. Anything else is invalid → empty return.
649    let recid_byte: u8 = match u8::try_from(u256_from_big_endian(raw_v)) {
650        Ok(27) => 0,
651        Ok(28) => 1,
652        _ => return Ok(Bytes::new()),
653    };
654
655    let msg_hash: [u8; 32] = raw_hash
656        .try_into()
657        .map_err(|_| InternalError::TypeConversion)?;
658    let sig: [u8; 64] = raw_sig
659        .try_into()
660        .map_err(|_| InternalError::TypeConversion)?;
661
662    let pk_hash = match crypto.secp256k1_ecrecover(&sig, recid_byte, &msg_hash) {
663        Ok(h) => h,
664        Err(_) => return Ok(Bytes::new()),
665    };
666
667    // Address is the last 20 bytes of the keccak hash of the public key.
668    let mut out = [0u8; 32];
669    out[12..32].copy_from_slice(&pk_hash[12..32]);
670
671    Ok(Bytes::copy_from_slice(&out))
672}
673
674/// Returns the calldata received
675pub fn identity(
676    calldata: &Bytes,
677    gas_remaining: &mut u64,
678    _fork: Fork,
679    _crypto: &dyn Crypto,
680) -> Result<Bytes, VMError> {
681    let gas_cost = gas_cost::identity(calldata.len())?;
682
683    increase_precompile_consumed_gas(gas_cost, gas_remaining)?;
684
685    Ok(calldata.clone())
686}
687
688/// Returns the calldata hashed by sha2-256 algorithm
689pub fn sha2_256(
690    calldata: &Bytes,
691    gas_remaining: &mut u64,
692    _fork: Fork,
693    crypto: &dyn Crypto,
694) -> Result<Bytes, VMError> {
695    let gas_cost = gas_cost::sha2_256(calldata.len())?;
696
697    increase_precompile_consumed_gas(gas_cost, gas_remaining)?;
698
699    let digest = crypto.sha256(calldata);
700    Ok(Bytes::copy_from_slice(&digest))
701}
702
703/// Returns the calldata hashed by ripemd-160 algorithm, padded by zeros at left
704pub fn ripemd_160(
705    calldata: &Bytes,
706    gas_remaining: &mut u64,
707    _fork: Fork,
708    crypto: &dyn Crypto,
709) -> Result<Bytes, VMError> {
710    let gas_cost = gas_cost::ripemd_160(calldata.len())?;
711
712    increase_precompile_consumed_gas(gas_cost, gas_remaining)?;
713
714    let result = crypto.ripemd160(calldata);
715    Ok(Bytes::copy_from_slice(&result))
716}
717
718/// Returns the result of the module-exponentiation operation
719#[expect(clippy::indexing_slicing, reason = "bounds checked at start")]
720pub fn modexp(
721    calldata: &Bytes,
722    gas_remaining: &mut u64,
723    fork: Fork,
724    crypto: &dyn Crypto,
725) -> Result<Bytes, VMError> {
726    // If calldata does not reach the required length, we should fill the rest with zeros
727    let calldata = fill_with_zeros(calldata, 96);
728
729    // Defer converting to a U256 after the zero check.
730    if fork < Fork::Osaka {
731        let base_size_bytes: [u8; 32] = calldata[0..32].try_into()?;
732        let modulus_size_bytes: [u8; 32] = calldata[64..96].try_into()?;
733        const ZERO_BYTES: [u8; 32] = [0u8; 32];
734
735        if base_size_bytes == ZERO_BYTES && modulus_size_bytes == ZERO_BYTES {
736            // On Berlin or newer there is a floor cost for the modexp precompile
737            increase_precompile_consumed_gas(MODEXP_STATIC_COST, gas_remaining)?;
738            return Ok(Bytes::new());
739        }
740    }
741
742    // The try_into are infallible and the compiler optimizes them out, even without unsafe.
743    // https://godbolt.org/z/h8rW8M3c4
744    let base_size = u256_from_big_endian_const::<32>(calldata[0..32].try_into()?);
745    let modulus_size = u256_from_big_endian_const::<32>(calldata[64..96].try_into()?);
746    let exponent_size = u256_from_big_endian_const::<32>(calldata[32..64].try_into()?);
747
748    if fork >= Fork::Osaka {
749        if base_size > U256::from(1024) {
750            return Err(PrecompileError::ModExpBaseTooLarge.into());
751        }
752        if exponent_size > U256::from(1024) {
753            return Err(PrecompileError::ModExpExpTooLarge.into());
754        }
755        if modulus_size > U256::from(1024) {
756            return Err(PrecompileError::ModExpModulusTooLarge.into());
757        }
758    }
759
760    // Because on some cases conversions to usize exploded before the check of the zero value could be done
761    let base_size = usize::try_from(base_size).map_err(|_| PrecompileError::ParsingInputError)?;
762    let exponent_size =
763        usize::try_from(exponent_size).map_err(|_| PrecompileError::ParsingInputError)?;
764    let modulus_size =
765        usize::try_from(modulus_size).map_err(|_| PrecompileError::ParsingInputError)?;
766
767    let base_limit = base_size.checked_add(96).ok_or(InternalError::Overflow)?;
768
769    let exponent_limit = exponent_size
770        .checked_add(base_limit)
771        .ok_or(InternalError::Overflow)?;
772
773    let modulus_limit = modulus_size
774        .checked_add(exponent_limit)
775        .ok_or(InternalError::Overflow)?;
776
777    let b = get_slice_or_default(&calldata, 96, base_limit, base_size);
778    let e = get_slice_or_default(&calldata, base_limit, exponent_limit, exponent_size);
779    let m = get_slice_or_default(&calldata, exponent_limit, modulus_limit, modulus_size);
780
781    // Gas computation uses malachite Natural to compute bit length of exponent
782    use malachite::Natural;
783    use malachite::base::num::conversion::traits::*;
784    let exp_first_32_bytes = e.get(0..32.min(exponent_size)).unwrap_or_default();
785    let exp_first_32 =
786        Natural::from_power_of_2_digits_desc(8u64, exp_first_32_bytes.iter().cloned())
787            .ok_or(InternalError::TypeConversion)?;
788
789    let gas_cost = gas_cost::modexp(&exp_first_32, base_size, exponent_size, modulus_size, fork)?;
790
791    increase_precompile_consumed_gas(gas_cost, gas_remaining)?;
792
793    if base_size == 0 && modulus_size == 0 {
794        return Ok(Bytes::new());
795    }
796
797    let result = crypto
798        .modexp(&b, &e, &m)
799        .map_err(|_| VMError::from(PrecompileError::ParsingInputError))?;
800
801    let res_bytes = increase_left_pad(&Bytes::from(result), modulus_size);
802
803    Ok(res_bytes.slice(..modulus_size))
804}
805
806/// This function returns the slice between the lower and upper limit of the calldata (as a vector),
807/// padding with zeros at the end if necessary.
808///
809/// Uses Cow so that the best case of no resizing doesn't require an allocation.
810#[expect(clippy::indexing_slicing, reason = "bounds checked")]
811fn get_slice_or_default<'c>(
812    calldata: &'c Bytes,
813    lower_limit: usize,
814    upper_limit: usize,
815    size_to_expand: usize,
816) -> Cow<'c, [u8]> {
817    let upper_limit = calldata.len().min(upper_limit);
818    if let Some(data) = calldata.get(lower_limit..upper_limit)
819        && !data.is_empty()
820    {
821        if data.len() == size_to_expand {
822            return data.into();
823        }
824        let mut extended = vec![0u8; size_to_expand];
825        let copy_size = size_to_expand.min(data.len());
826        extended[..copy_size].copy_from_slice(&data[..copy_size]);
827        return extended.into();
828    }
829    Vec::new().into()
830}
831
832/// If the result size is less than needed, pads left with zeros.
833#[inline(always)]
834pub fn increase_left_pad(result: &Bytes, m_size: usize) -> Bytes {
835    #[expect(
836        clippy::arithmetic_side_effects,
837        clippy::indexing_slicing,
838        reason = "overflow checked with the if condition, bounds checked"
839    )]
840    if result.len() < m_size {
841        let mut padded_result = vec![0u8; m_size];
842        let size_diff = m_size - result.len();
843        padded_result[size_diff..].copy_from_slice(result);
844
845        padded_result.into()
846    } else {
847        // this clone is cheap (Arc)
848        result.clone()
849    }
850}
851
852/// Makes a point addition on the elliptic curve 'alt_bn128'
853pub fn ecadd(
854    calldata: &Bytes,
855    gas_remaining: &mut u64,
856    _fork: Fork,
857    crypto: &dyn Crypto,
858) -> Result<Bytes, VMError> {
859    // If calldata does not reach the required length, we should fill the rest with zeros
860    let calldata = fill_with_zeros(calldata, 128);
861
862    increase_precompile_consumed_gas(ECADD_COST, gas_remaining)?;
863
864    let (Some(first_point), Some(second_point)) =
865        (parse_bn254_g1(&calldata, 0), parse_bn254_g1(&calldata, 64))
866    else {
867        return Err(InternalError::Slicing.into());
868    };
869    validate_bn254_g1_coords(&first_point)?;
870    validate_bn254_g1_coords(&second_point)?;
871
872    #[expect(clippy::indexing_slicing, reason = "calldata padded to 128 bytes")]
873    let result = crypto
874        .bn254_g1_add(&calldata[..64], &calldata[64..128])
875        .map_err(crypto_error_to_precompile)?;
876
877    Ok(Bytes::copy_from_slice(&result))
878}
879
880/// Makes a scalar multiplication on the elliptic curve 'alt_bn128'
881pub fn ecmul(
882    calldata: &Bytes,
883    gas_remaining: &mut u64,
884    _fork: Fork,
885    crypto: &dyn Crypto,
886) -> Result<Bytes, VMError> {
887    // If calldata does not reach the required length, we should fill the rest with zeros
888    let calldata = fill_with_zeros(calldata, 96);
889    increase_precompile_consumed_gas(ECMUL_COST, gas_remaining)?;
890
891    let (Some(g1), Some(_scalar)) = (
892        parse_bn254_g1(&calldata, 0),
893        parse_bn254_scalar(&calldata, 64),
894    ) else {
895        return Err(InternalError::Slicing.into());
896    };
897    validate_bn254_g1_coords(&g1)?;
898
899    #[expect(clippy::indexing_slicing, reason = "calldata padded to 96 bytes")]
900    let result = crypto
901        .bn254_g1_mul(&calldata[..64], &calldata[64..96])
902        .map_err(crypto_error_to_precompile)?;
903
904    Ok(Bytes::copy_from_slice(&result))
905}
906
907const ALT_BN128_PRIME: U256 = U256([
908    0x3c208c16d87cfd47,
909    0x97816a916871ca8d,
910    0xb85045b68181585d,
911    0x30644e72e131a029,
912]);
913
914pub struct G1(U256, U256);
915impl G1 {
916    /// According to EIP-197, the point at infinity (also called neutral element of G1 or zero) is encoded as (0, 0)
917    pub fn is_zero(&self) -> bool {
918        self.0.is_zero() && self.1.is_zero()
919    }
920}
921pub struct G2(U256, U256, U256, U256);
922impl G2 {
923    /// According to EIP-197, the point at infinity (also called neutral element of G2 or zero) is encoded as (0, 0, 0, 0)
924    pub fn is_zero(&self) -> bool {
925        self.0.is_zero() && self.1.is_zero() && self.2.is_zero() && self.3.is_zero()
926    }
927}
928
929/// Parses 32 bytes as BN254 scalar
930#[inline]
931fn parse_bn254_scalar(buf: &[u8], offset: usize) -> Option<U256> {
932    buf.get(offset..offset.checked_add(32)?)
933        .map(u256_from_big_endian)
934}
935
936/// Parses 64 bytes as a BN254 G1 point
937#[inline]
938fn parse_bn254_g1(buf: &[u8], offset: usize) -> Option<G1> {
939    let chunk = buf.get(offset..offset.checked_add(64)?)?;
940    let (x_bytes, y_bytes) = chunk.split_at_checked(32)?;
941    Some(G1(
942        u256_from_big_endian(x_bytes),
943        u256_from_big_endian(y_bytes),
944    ))
945}
946
947/// Parses 128 bytes as a BN254 G2 point
948fn parse_bn254_g2(buf: &[u8], offset: usize) -> Option<G2> {
949    let chunk = buf.get(offset..offset.checked_add(128)?)?;
950    let (g2_xy, rest) = chunk.split_at_checked(32)?;
951    let (g2_xx, rest) = rest.split_at_checked(32)?;
952    let (g2_yy, g2_yx) = rest.split_at_checked(32)?;
953    Some(G2(
954        u256_from_big_endian(g2_xx),
955        u256_from_big_endian(g2_xy),
956        u256_from_big_endian(g2_yx),
957        u256_from_big_endian(g2_yy),
958    ))
959}
960
961#[inline]
962fn validate_bn254_g1_coords(g1: &G1) -> Result<(), VMError> {
963    // check each element is in field
964    if g1.0 >= ALT_BN128_PRIME || g1.1 >= ALT_BN128_PRIME {
965        return Err(PrecompileError::CoordinateExceedsFieldModulus.into());
966    }
967    Ok(())
968}
969
970#[inline]
971fn validate_bn254_g2_coords(g2: &G2) -> Result<(), VMError> {
972    // check each element is in field
973    if g2.0 >= ALT_BN128_PRIME
974        || g2.1 >= ALT_BN128_PRIME
975        || g2.2 >= ALT_BN128_PRIME
976        || g2.3 >= ALT_BN128_PRIME
977    {
978        return Err(PrecompileError::CoordinateExceedsFieldModulus.into());
979    }
980    Ok(())
981}
982
983/// Performs a bilinear pairing on points on the elliptic curve 'alt_bn128', returns 1 on success and 0 on failure
984pub fn ecpairing(
985    calldata: &Bytes,
986    gas_remaining: &mut u64,
987    _fork: Fork,
988    crypto: &dyn Crypto,
989) -> Result<Bytes, VMError> {
990    // The input must always be a multiple of 192 (6 32-byte values)
991    if !calldata.len().is_multiple_of(192) {
992        return Err(PrecompileError::ParsingInputError.into());
993    }
994
995    let inputs_amount = calldata.len() / 192;
996    let gas_cost = gas_cost::ecpairing(inputs_amount)?;
997    increase_precompile_consumed_gas(gas_cost, gas_remaining)?;
998
999    let mut pairs: Vec<(&[u8], &[u8])> = Vec::new();
1000    for input in calldata.chunks_exact(192) {
1001        let (Some(g1), Some(g2)) = (parse_bn254_g1(input, 0), parse_bn254_g2(input, 64)) else {
1002            return Err(InternalError::Slicing.into());
1003        };
1004        validate_bn254_g1_coords(&g1)?;
1005        validate_bn254_g2_coords(&g2)?;
1006        #[expect(clippy::indexing_slicing, reason = "chunks_exact guarantees 192 bytes")]
1007        pairs.push((&input[..64], &input[64..192]));
1008    }
1009
1010    let pairing_check = if pairs.is_empty() {
1011        true
1012    } else {
1013        crypto
1014            .bn254_pairing_check(&pairs)
1015            .map_err(crypto_error_to_precompile)?
1016    };
1017
1018    let mut result = [0; 32];
1019    result[31] = u8::from(pairing_check);
1020    Ok(Bytes::from_owner(result))
1021}
1022
1023/// Returns the result of Blake2 hashing algorithm given a certain parameters from the calldata.
1024pub fn blake2f(
1025    calldata: &Bytes,
1026    gas_remaining: &mut u64,
1027    _fork: Fork,
1028    crypto: &dyn Crypto,
1029) -> Result<Bytes, VMError> {
1030    if calldata.len() != 213 {
1031        return Err(PrecompileError::ParsingInputError.into());
1032    }
1033
1034    let mut calldata = calldata.slice(0..213);
1035
1036    let rounds = calldata.get_u32();
1037
1038    let gas_cost = u64::from(rounds) * BLAKE2F_ROUND_COST;
1039    increase_precompile_consumed_gas(gas_cost, gas_remaining)?;
1040
1041    let mut h = [0; 8];
1042
1043    h.copy_from_slice(&std::array::from_fn::<u64, 8, _>(|_| calldata.get_u64_le()));
1044
1045    let mut m = [0; 16];
1046
1047    m.copy_from_slice(&std::array::from_fn::<u64, 16, _>(|_| {
1048        calldata.get_u64_le()
1049    }));
1050
1051    let mut t = [0; 2];
1052    t.copy_from_slice(&std::array::from_fn::<u64, 2, _>(|_| calldata.get_u64_le()));
1053
1054    let f = calldata.get_u8();
1055    if f != 0 && f != 1 {
1056        return Err(PrecompileError::ParsingInputError.into());
1057    }
1058    let f = f == 1;
1059
1060    crypto.blake2_compress(rounds, &mut h, m, t, f);
1061
1062    Ok(Bytes::from_iter(
1063        h.into_iter().flat_map(|value| value.to_le_bytes()),
1064    ))
1065}
1066
1067/// Converts the provided commitment to match the provided versioned_hash.
1068fn kzg_commitment_to_versioned_hash(commitment_bytes: &[u8; 48], crypto: &dyn Crypto) -> H256 {
1069    let mut versioned_hash: [u8; 32] = crypto.sha256(commitment_bytes);
1070    versioned_hash[0] = VERSIONED_HASH_VERSION_KZG;
1071    versioned_hash.into()
1072}
1073
1074const POINT_EVALUATION_OUTPUT_BYTES: [u8; 64] = [
1075    // Big endian FIELD_ELEMENTS_PER_BLOB bytes
1076    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1077    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
1078    // Big endian BLS_MODULUS bytes
1079    0x73, 0xED, 0xA7, 0x53, 0x29, 0x9D, 0x7D, 0x48, 0x33, 0x39, 0xD8, 0x08, 0x09, 0xA1, 0xD8, 0x05,
1080    0x53, 0xBD, 0xA4, 0x02, 0xFF, 0xFE, 0x5B, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01,
1081];
1082
1083/// Makes verifications on the received point, proof and commitment, if true returns a constant value
1084fn point_evaluation(
1085    calldata: &Bytes,
1086    gas_remaining: &mut u64,
1087    _fork: Fork,
1088    crypto: &dyn Crypto,
1089) -> Result<Bytes, VMError> {
1090    if calldata.len() != 192 {
1091        return Err(PrecompileError::ParsingInputError.into());
1092    }
1093
1094    // Consume gas
1095    let gas_cost = POINT_EVALUATION_COST;
1096    increase_precompile_consumed_gas(gas_cost, gas_remaining)?;
1097
1098    // Parse inputs
1099    let versioned_hash: [u8; 32] = calldata
1100        .get(..32)
1101        .ok_or(InternalError::Slicing)?
1102        .try_into()
1103        .map_err(|_| InternalError::TypeConversion)?;
1104
1105    let z: [u8; 32] = calldata
1106        .get(32..64)
1107        .ok_or(InternalError::Slicing)?
1108        .try_into()
1109        .map_err(|_| InternalError::TypeConversion)?;
1110
1111    let y: [u8; 32] = calldata
1112        .get(64..96)
1113        .ok_or(InternalError::Slicing)?
1114        .try_into()
1115        .map_err(|_| InternalError::TypeConversion)?;
1116
1117    let commitment: [u8; 48] = calldata
1118        .get(96..144)
1119        .ok_or(InternalError::Slicing)?
1120        .try_into()
1121        .map_err(|_| InternalError::TypeConversion)?;
1122
1123    let proof: [u8; 48] = calldata
1124        .get(144..192)
1125        .ok_or(InternalError::Slicing)?
1126        .try_into()
1127        .map_err(|_| InternalError::TypeConversion)?;
1128
1129    // Perform the evaluation
1130
1131    // This checks if the commitment is equal to the versioned hash
1132    if kzg_commitment_to_versioned_hash(&commitment, crypto) != H256::from(versioned_hash) {
1133        return Err(PrecompileError::ParsingInputError.into());
1134    }
1135
1136    // This verifies the proof from a point (z, y) and a commitment
1137    crypto
1138        .verify_kzg_proof(&z, &y, &commitment, &proof)
1139        .map_err(|_| VMError::from(PrecompileError::ParsingInputError))?;
1140
1141    // The first 32 bytes consist of the number of field elements in the blob, and the
1142    // other 32 bytes consist of the modulus used in the BLS signature scheme.
1143    let output = POINT_EVALUATION_OUTPUT_BYTES.to_vec();
1144
1145    Ok(Bytes::from(output))
1146}
1147
1148/// Signature verification in the "secp256r1" elliptic curve
1149/// If the verification succeeds, returns 1 in a 32-bit big-endian format.
1150/// If the verification fails, returns an empty `Bytes` object.
1151/// Implemented following https://github.com/ethereum/EIPs/blob/master/EIPS/eip-7951.md
1152pub fn p_256_verify(
1153    calldata: &Bytes,
1154    gas_remaining: &mut u64,
1155    _fork: Fork,
1156    crypto: &dyn Crypto,
1157) -> Result<Bytes, VMError> {
1158    increase_precompile_consumed_gas(P256_VERIFY_COST, gas_remaining)
1159        .map_err(|_| PrecompileError::NotEnoughGas)?;
1160
1161    // Validate input data length is 160 bytes
1162    if calldata.len() != 160 {
1163        return Ok(Bytes::new());
1164    }
1165
1166    // Parse parameters
1167    #[expect(
1168        clippy::indexing_slicing,
1169        reason = "length of the calldata is checked before slicing"
1170    )]
1171    let msg: &[u8; 32] = calldata[0..32].try_into()?;
1172    #[expect(clippy::indexing_slicing, reason = "length checked")]
1173    let r: &[u8; 32] = calldata[32..64].try_into()?;
1174    #[expect(clippy::indexing_slicing, reason = "length checked")]
1175    let s: &[u8; 32] = calldata[64..96].try_into()?;
1176    #[expect(clippy::indexing_slicing, reason = "length checked")]
1177    let pk_x: &[u8; 32] = calldata[96..128].try_into()?;
1178    #[expect(clippy::indexing_slicing, reason = "length checked")]
1179    let pk_y: &[u8; 32] = calldata[128..160].try_into()?;
1180
1181    // Build 64-byte sig (r||s) and 64-byte pk (x||y)
1182    let mut sig_bytes = [0u8; 64];
1183    sig_bytes[..32].copy_from_slice(r);
1184    sig_bytes[32..].copy_from_slice(s);
1185
1186    let mut pk_bytes = [0u8; 64];
1187    pk_bytes[..32].copy_from_slice(pk_x);
1188    pk_bytes[32..].copy_from_slice(pk_y);
1189
1190    let success = crypto.secp256r1_verify(msg, &sig_bytes, &pk_bytes);
1191
1192    // If the verification succeeds, returns 1 in a 32-bit big-endian format.
1193    // If the verification fails, returns an empty `Bytes` object.
1194    if success {
1195        const RESULT: [u8; 32] = [
1196            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1197            0, 0, 1,
1198        ];
1199        Ok(Bytes::from_static(&RESULT))
1200    } else {
1201        Ok(Bytes::new())
1202    }
1203}
1204
1205/// Parse a 64-byte padded BLS12-381 field element into a 48-byte unpadded element.
1206/// The first 16 bytes must be zero (padding). Returns error if padding is invalid.
1207fn parse_bls12_padded_fp(padded: &[u8; 64]) -> Result<[u8; 48], VMError> {
1208    if padded[..16] != [0u8; 16] {
1209        return Err(PrecompileError::ParsingInputError.into());
1210    }
1211    let fp: [u8; 48] = padded[16..64]
1212        .try_into()
1213        .map_err(|_| InternalError::TypeConversion)?;
1214    Ok(fp)
1215}
1216
1217pub fn bls12_g1add(
1218    calldata: &Bytes,
1219    gas_remaining: &mut u64,
1220    _fork: Fork,
1221    crypto: &dyn Crypto,
1222) -> Result<Bytes, VMError> {
1223    let (x_data, calldata) = calldata
1224        .split_first_chunk::<128>()
1225        .ok_or(PrecompileError::ParsingInputError)?;
1226    let (y_data, calldata) = calldata
1227        .split_first_chunk::<128>()
1228        .ok_or(PrecompileError::ParsingInputError)?;
1229    if !calldata.is_empty() {
1230        return Err(PrecompileError::ParsingInputError.into());
1231    }
1232
1233    // Apply precompile gas cost.
1234    increase_precompile_consumed_gas(BLS12_381_G1ADD_COST, gas_remaining)
1235        .map_err(|_| PrecompileError::NotEnoughGas)?;
1236
1237    // Parse two 128-byte padded G1 points into 48-byte unpadded coordinates.
1238    let ax = parse_bls12_padded_fp(
1239        x_data[..64]
1240            .try_into()
1241            .map_err(|_| InternalError::TypeConversion)?,
1242    )?;
1243    let ay = parse_bls12_padded_fp(
1244        x_data[64..128]
1245            .try_into()
1246            .map_err(|_| InternalError::TypeConversion)?,
1247    )?;
1248    let bx = parse_bls12_padded_fp(
1249        y_data[..64]
1250            .try_into()
1251            .map_err(|_| InternalError::TypeConversion)?,
1252    )?;
1253    let by = parse_bls12_padded_fp(
1254        y_data[64..128]
1255            .try_into()
1256            .map_err(|_| InternalError::TypeConversion)?,
1257    )?;
1258
1259    let result = crypto
1260        .bls12_381_g1_add((ax, ay), (bx, by))
1261        .map_err(bls12_g1_crypto_error)?;
1262
1263    // Re-pad the 96-byte unpadded result (x||y each 48 bytes) to 128 bytes.
1264    let mut output = [0u8; 128];
1265    output[16..64].copy_from_slice(&result[..48]);
1266    output[80..128].copy_from_slice(&result[48..96]);
1267    Ok(Bytes::copy_from_slice(&output))
1268}
1269
1270pub fn bls12_g1msm(
1271    calldata: &Bytes,
1272    gas_remaining: &mut u64,
1273    _fork: Fork,
1274    crypto: &dyn Crypto,
1275) -> Result<Bytes, VMError> {
1276    if calldata.is_empty() || !calldata.len().is_multiple_of(BLS12_381_G1_MSM_PAIR_LENGTH) {
1277        return Err(PrecompileError::ParsingInputError.into());
1278    }
1279
1280    let k = calldata.len() / BLS12_381_G1_MSM_PAIR_LENGTH;
1281    let required_gas = gas_cost::bls12_msm(k, &BLS12_381_G1_K_DISCOUNT, G1_MUL_COST)?;
1282    increase_precompile_consumed_gas(required_gas, gas_remaining)?;
1283
1284    #[allow(clippy::type_complexity)]
1285    let mut pairs: Vec<(([u8; 48], [u8; 48]), [u8; 32])> = Vec::with_capacity(k);
1286
1287    #[expect(
1288        clippy::arithmetic_side_effects,
1289        clippy::indexing_slicing,
1290        reason = "bounds checked"
1291    )]
1292    for i in 0..k {
1293        let point_offset = i * BLS12_381_G1_MSM_PAIR_LENGTH;
1294        let scalar_offset = point_offset + 128;
1295        let pair_end = scalar_offset + 32;
1296
1297        let point_bytes = &calldata[point_offset..scalar_offset];
1298        let scalar_bytes = &calldata[scalar_offset..pair_end];
1299
1300        let px = parse_bls12_padded_fp(
1301            point_bytes[..64]
1302                .try_into()
1303                .map_err(|_| InternalError::TypeConversion)?,
1304        )?;
1305        let py = parse_bls12_padded_fp(
1306            point_bytes[64..128]
1307                .try_into()
1308                .map_err(|_| InternalError::TypeConversion)?,
1309        )?;
1310        let scalar: [u8; 32] = scalar_bytes
1311            .try_into()
1312            .map_err(|_| InternalError::TypeConversion)?;
1313
1314        pairs.push(((px, py), scalar));
1315    }
1316
1317    let result = crypto
1318        .bls12_381_g1_msm(&pairs)
1319        .map_err(bls12_msm_pairing_crypto_error)?;
1320
1321    // Re-pad output: 96-byte result → 128-byte padded
1322    let mut output = [0u8; 128];
1323    output[16..64].copy_from_slice(&result[..48]);
1324    output[80..128].copy_from_slice(&result[48..96]);
1325    Ok(Bytes::copy_from_slice(&output))
1326}
1327
1328pub fn bls12_g2add(
1329    calldata: &Bytes,
1330    gas_remaining: &mut u64,
1331    _fork: Fork,
1332    crypto: &dyn Crypto,
1333) -> Result<Bytes, VMError> {
1334    let (x_data, calldata) = calldata
1335        .split_first_chunk::<256>()
1336        .ok_or(PrecompileError::ParsingInputError)?;
1337    let (y_data, calldata) = calldata
1338        .split_first_chunk::<256>()
1339        .ok_or(PrecompileError::ParsingInputError)?;
1340    if !calldata.is_empty() {
1341        return Err(PrecompileError::ParsingInputError.into());
1342    }
1343
1344    // Apply precompile gas cost.
1345    increase_precompile_consumed_gas(BLS12_381_G2ADD_COST, gas_remaining)
1346        .map_err(|_| PrecompileError::NotEnoughGas)?;
1347
1348    // Parse two 256-byte padded G2 points into four 48-byte unpadded coordinates each.
1349    // G2 point layout: x_0(64) || x_1(64) || y_0(64) || y_1(64) = 256 bytes
1350    let ax0 = parse_bls12_padded_fp(
1351        x_data[0..64]
1352            .try_into()
1353            .map_err(|_| InternalError::TypeConversion)?,
1354    )?;
1355    let ax1 = parse_bls12_padded_fp(
1356        x_data[64..128]
1357            .try_into()
1358            .map_err(|_| InternalError::TypeConversion)?,
1359    )?;
1360    let ay0 = parse_bls12_padded_fp(
1361        x_data[128..192]
1362            .try_into()
1363            .map_err(|_| InternalError::TypeConversion)?,
1364    )?;
1365    let ay1 = parse_bls12_padded_fp(
1366        x_data[192..256]
1367            .try_into()
1368            .map_err(|_| InternalError::TypeConversion)?,
1369    )?;
1370
1371    let bx0 = parse_bls12_padded_fp(
1372        y_data[0..64]
1373            .try_into()
1374            .map_err(|_| InternalError::TypeConversion)?,
1375    )?;
1376    let bx1 = parse_bls12_padded_fp(
1377        y_data[64..128]
1378            .try_into()
1379            .map_err(|_| InternalError::TypeConversion)?,
1380    )?;
1381    let by0 = parse_bls12_padded_fp(
1382        y_data[128..192]
1383            .try_into()
1384            .map_err(|_| InternalError::TypeConversion)?,
1385    )?;
1386    let by1 = parse_bls12_padded_fp(
1387        y_data[192..256]
1388            .try_into()
1389            .map_err(|_| InternalError::TypeConversion)?,
1390    )?;
1391
1392    let result = crypto
1393        .bls12_381_g2_add((ax0, ax1, ay0, ay1), (bx0, bx1, by0, by1))
1394        .map_err(bls12_g2_crypto_error)?;
1395
1396    // Re-pad the 192-byte unpadded result to 256 bytes.
1397    // Unpadded: x_0(48) || x_1(48) || y_0(48) || y_1(48) = 192 bytes
1398    // Padded output: x_0_padded(64) || x_1_padded(64) || y_0_padded(64) || y_1_padded(64) = 256 bytes
1399    let mut output = [0u8; 256];
1400    output[16..64].copy_from_slice(&result[0..48]);
1401    output[80..128].copy_from_slice(&result[48..96]);
1402    output[144..192].copy_from_slice(&result[96..144]);
1403    output[208..256].copy_from_slice(&result[144..192]);
1404    Ok(Bytes::copy_from_slice(&output))
1405}
1406
1407pub fn bls12_g2msm(
1408    calldata: &Bytes,
1409    gas_remaining: &mut u64,
1410    _fork: Fork,
1411    crypto: &dyn Crypto,
1412) -> Result<Bytes, VMError> {
1413    if calldata.is_empty() || !calldata.len().is_multiple_of(BLS12_381_G2_MSM_PAIR_LENGTH) {
1414        return Err(PrecompileError::ParsingInputError.into());
1415    }
1416
1417    let k = calldata.len() / BLS12_381_G2_MSM_PAIR_LENGTH;
1418    let required_gas = gas_cost::bls12_msm(k, &BLS12_381_G2_K_DISCOUNT, G2_MUL_COST)?;
1419    increase_precompile_consumed_gas(required_gas, gas_remaining)?;
1420
1421    #[allow(clippy::type_complexity)]
1422    let mut pairs: Vec<(([u8; 48], [u8; 48], [u8; 48], [u8; 48]), [u8; 32])> =
1423        Vec::with_capacity(k);
1424
1425    #[expect(
1426        clippy::indexing_slicing,
1427        clippy::arithmetic_side_effects,
1428        reason = "bounds checked"
1429    )]
1430    for i in 0..k {
1431        let point_offset = i * BLS12_381_G2_MSM_PAIR_LENGTH;
1432        let scalar_offset = point_offset + 256;
1433        let pair_end = scalar_offset + 32;
1434
1435        let point_bytes = &calldata[point_offset..scalar_offset];
1436        let scalar_bytes = &calldata[scalar_offset..pair_end];
1437
1438        let x0 = parse_bls12_padded_fp(
1439            point_bytes[0..64]
1440                .try_into()
1441                .map_err(|_| InternalError::TypeConversion)?,
1442        )?;
1443        let x1 = parse_bls12_padded_fp(
1444            point_bytes[64..128]
1445                .try_into()
1446                .map_err(|_| InternalError::TypeConversion)?,
1447        )?;
1448        let y0 = parse_bls12_padded_fp(
1449            point_bytes[128..192]
1450                .try_into()
1451                .map_err(|_| InternalError::TypeConversion)?,
1452        )?;
1453        let y1 = parse_bls12_padded_fp(
1454            point_bytes[192..256]
1455                .try_into()
1456                .map_err(|_| InternalError::TypeConversion)?,
1457        )?;
1458        let scalar: [u8; 32] = scalar_bytes
1459            .try_into()
1460            .map_err(|_| InternalError::TypeConversion)?;
1461
1462        pairs.push(((x0, x1, y0, y1), scalar));
1463    }
1464
1465    let result = crypto
1466        .bls12_381_g2_msm(&pairs)
1467        .map_err(bls12_msm_pairing_crypto_error)?;
1468
1469    // Re-pad the 192-byte unpadded result to 256 bytes.
1470    let mut output = [0u8; 256];
1471    output[16..64].copy_from_slice(&result[0..48]);
1472    output[80..128].copy_from_slice(&result[48..96]);
1473    output[144..192].copy_from_slice(&result[96..144]);
1474    output[208..256].copy_from_slice(&result[144..192]);
1475    Ok(Bytes::copy_from_slice(&output))
1476}
1477
1478pub fn bls12_pairing_check(
1479    calldata: &Bytes,
1480    gas_remaining: &mut u64,
1481    _fork: Fork,
1482    crypto: &dyn Crypto,
1483) -> Result<Bytes, VMError> {
1484    if calldata.is_empty()
1485        || !calldata
1486            .len()
1487            .is_multiple_of(BLS12_381_PAIRING_CHECK_PAIR_LENGTH)
1488    {
1489        return Err(PrecompileError::ParsingInputError.into());
1490    }
1491
1492    // GAS
1493    let k = calldata.len() / BLS12_381_PAIRING_CHECK_PAIR_LENGTH;
1494    let gas_cost = gas_cost::bls12_pairing_check(k)?;
1495    increase_precompile_consumed_gas(gas_cost, gas_remaining)?;
1496
1497    #[allow(clippy::type_complexity)]
1498    let mut pairs: Vec<(
1499        ([u8; 48], [u8; 48]),
1500        ([u8; 48], [u8; 48], [u8; 48], [u8; 48]),
1501    )> = Vec::with_capacity(k);
1502
1503    #[expect(
1504        clippy::indexing_slicing,
1505        clippy::arithmetic_side_effects,
1506        reason = "bounds checked"
1507    )]
1508    for i in 0..k {
1509        let g1_offset = i * BLS12_381_PAIRING_CHECK_PAIR_LENGTH;
1510        let g2_offset = g1_offset + 128;
1511        let pair_end = g2_offset + 256;
1512
1513        let g1_bytes = &calldata[g1_offset..g2_offset];
1514        let g2_bytes = &calldata[g2_offset..pair_end];
1515
1516        let g1x = parse_bls12_padded_fp(
1517            g1_bytes[0..64]
1518                .try_into()
1519                .map_err(|_| InternalError::TypeConversion)?,
1520        )?;
1521        let g1y = parse_bls12_padded_fp(
1522            g1_bytes[64..128]
1523                .try_into()
1524                .map_err(|_| InternalError::TypeConversion)?,
1525        )?;
1526
1527        let g2x0 = parse_bls12_padded_fp(
1528            g2_bytes[0..64]
1529                .try_into()
1530                .map_err(|_| InternalError::TypeConversion)?,
1531        )?;
1532        let g2x1 = parse_bls12_padded_fp(
1533            g2_bytes[64..128]
1534                .try_into()
1535                .map_err(|_| InternalError::TypeConversion)?,
1536        )?;
1537        let g2y0 = parse_bls12_padded_fp(
1538            g2_bytes[128..192]
1539                .try_into()
1540                .map_err(|_| InternalError::TypeConversion)?,
1541        )?;
1542        let g2y1 = parse_bls12_padded_fp(
1543            g2_bytes[192..256]
1544                .try_into()
1545                .map_err(|_| InternalError::TypeConversion)?,
1546        )?;
1547
1548        pairs.push(((g1x, g1y), (g2x0, g2x1, g2y0, g2y1)));
1549    }
1550
1551    let result = crypto
1552        .bls12_381_pairing_check(&pairs)
1553        .map_err(bls12_msm_pairing_crypto_error)?;
1554
1555    if result {
1556        let mut out = vec![0_u8; 31];
1557        out.push(1);
1558        Ok(Bytes::from(out))
1559    } else {
1560        Ok(Bytes::copy_from_slice(&[0_u8; 32]))
1561    }
1562}
1563
1564pub fn bls12_map_fp_to_g1(
1565    calldata: &Bytes,
1566    gas_remaining: &mut u64,
1567    _fork: Fork,
1568    crypto: &dyn Crypto,
1569) -> Result<Bytes, VMError> {
1570    if calldata.len() != BLS12_381_FP_VALID_INPUT_LENGTH {
1571        return Err(PrecompileError::ParsingInputError.into());
1572    }
1573
1574    // GAS
1575    increase_precompile_consumed_gas(BLS12_381_MAP_FP_TO_G1_COST, gas_remaining)?;
1576
1577    // Parse the 64-byte padded field element into 48-byte unpadded.
1578    #[expect(clippy::indexing_slicing, reason = "bounds checked")]
1579    let fp = parse_bls12_padded_fp(
1580        calldata[0..PADDED_FIELD_ELEMENT_SIZE_IN_BYTES]
1581            .try_into()
1582            .map_err(|_| InternalError::TypeConversion)?,
1583    )?;
1584
1585    let result = crypto
1586        .bls12_381_fp_to_g1(&fp)
1587        .map_err(crypto_error_to_precompile)?;
1588
1589    // Re-pad the 96-byte unpadded G1 result to 128 bytes.
1590    let mut output = [0u8; 128];
1591    output[16..64].copy_from_slice(&result[0..48]);
1592    output[80..128].copy_from_slice(&result[48..96]);
1593    Ok(Bytes::copy_from_slice(&output))
1594}
1595
1596pub fn bls12_map_fp2_to_g2(
1597    calldata: &Bytes,
1598    gas_remaining: &mut u64,
1599    _fork: Fork,
1600    crypto: &dyn Crypto,
1601) -> Result<Bytes, VMError> {
1602    if calldata.len() != BLS12_381_FP2_VALID_INPUT_LENGTH {
1603        return Err(PrecompileError::ParsingInputError.into());
1604    }
1605
1606    // GAS
1607    increase_precompile_consumed_gas(BLS12_381_MAP_FP2_TO_G2_COST, gas_remaining)?;
1608
1609    // Parse the two 64-byte padded field elements into 48-byte unpadded.
1610    #[expect(clippy::indexing_slicing, reason = "bounds checked")]
1611    let c0 = parse_bls12_padded_fp(
1612        calldata[0..PADDED_FIELD_ELEMENT_SIZE_IN_BYTES]
1613            .try_into()
1614            .map_err(|_| InternalError::TypeConversion)?,
1615    )?;
1616    #[expect(clippy::indexing_slicing, reason = "bounds checked")]
1617    let c1 = parse_bls12_padded_fp(
1618        calldata[PADDED_FIELD_ELEMENT_SIZE_IN_BYTES..BLS12_381_FP2_VALID_INPUT_LENGTH]
1619            .try_into()
1620            .map_err(|_| InternalError::TypeConversion)?,
1621    )?;
1622
1623    if c0 == [0u8; 48] && c1 == [0u8; 48] {
1624        return Ok(Bytes::copy_from_slice(&FP2_ZERO_MAPPED_TO_G2));
1625    }
1626
1627    let result = crypto
1628        .bls12_381_fp2_to_g2((c0, c1))
1629        .map_err(crypto_error_to_precompile)?;
1630
1631    // Re-pad the 192-byte unpadded G2 result to 256 bytes.
1632    // Unpadded: x_0(48) || x_1(48) || y_0(48) || y_1(48) = 192 bytes
1633    // Padded output: x_0_padded(64) || x_1_padded(64) || y_0_padded(64) || y_1_padded(64) = 256 bytes
1634    let mut output = [0u8; 256];
1635    output[16..64].copy_from_slice(&result[0..48]);
1636    output[80..128].copy_from_slice(&result[48..96]);
1637    output[144..192].copy_from_slice(&result[96..144]);
1638    output[208..256].copy_from_slice(&result[144..192]);
1639    Ok(Bytes::copy_from_slice(&output))
1640}
1641
1642#[cfg(test)]
1643mod tests {
1644    use super::*;
1645    use ethrex_common::types::Fork;
1646
1647    /// I3 gate test: EXECUTE precompile is L1-only and only active since LStar.
1648    #[test]
1649    fn execute_precompile_gated_by_lstar_and_l1() {
1650        let execute_addr = EXECUTE.address;
1651        // Pre-LStar L1: not a precompile.
1652        assert!(!is_precompile(&execute_addr, Fork::Amsterdam, VMType::L1));
1653        // LStar on L2: not a precompile (L1-only, I3).
1654        assert!(!is_precompile(
1655            &execute_addr,
1656            Fork::LStar,
1657            VMType::L2(Default::default())
1658        ));
1659        // LStar on L1: is a precompile.
1660        assert!(is_precompile(&execute_addr, Fork::LStar, VMType::L1));
1661    }
1662}