Skip to main content

revm_precompile/
interface.rs

1//! Interface for the precompiles. It contains the precompile result type,
2//! the precompile output type, and the precompile error type.
3use context_interface::{cfg::gas::GasTracker, result::AnyError};
4use core::fmt::{self, Debug};
5use primitives::{Bytes, OnceLock};
6use std::{borrow::Cow, boxed::Box, string::String, vec::Vec};
7
8use crate::bls12_381::{G1Point, G1PointScalar, G2Point, G2PointScalar};
9
10/// Global crypto provider instance
11static CRYPTO: OnceLock<Box<dyn Crypto>> = OnceLock::new();
12
13/// Install a custom crypto provider globally.
14pub fn install_crypto<C: Crypto + 'static>(crypto: C) -> bool {
15    CRYPTO.set(Box::new(crypto)).is_ok()
16}
17
18/// Get the installed crypto provider, or the default if none is installed.
19pub fn crypto() -> &'static dyn Crypto {
20    CRYPTO.get_or_init(|| Box::new(DefaultCrypto)).as_ref()
21}
22
23/// A precompile operation result type for individual Ethereum precompile functions.
24///
25/// Returns either `Ok(EthPrecompileOutput)` or `Err(PrecompileHalt)`.
26pub type EthPrecompileResult = Result<EthPrecompileOutput, PrecompileHalt>;
27
28/// A precompile operation result type for the precompile provider.
29///
30/// Returns either `Ok(PrecompileOutput)` or `Err(PrecompileError)`.
31/// `PrecompileError` only represents fatal errors that abort EVM execution.
32pub type PrecompileResult = Result<PrecompileOutput, PrecompileError>;
33
34/// Simple precompile execution output used by individual Ethereum precompile functions.
35///
36/// Contains only the gas used and output bytes. For the richer output type
37/// with state gas accounting and halt support, see [`PrecompileOutput`].
38#[derive(Clone, Debug, PartialEq, Eq, Hash)]
39pub struct EthPrecompileOutput {
40    /// Gas used by the precompile.
41    pub gas_used: u64,
42    /// Output bytes
43    pub bytes: Bytes,
44}
45
46impl EthPrecompileOutput {
47    /// Returns new precompile output with the given gas used and output bytes.
48    pub const fn new(gas_used: u64, bytes: Bytes) -> Self {
49        Self { gas_used, bytes }
50    }
51}
52
53/// Status of a precompile execution.
54#[derive(Clone, Debug, PartialEq, Eq, Hash)]
55pub enum PrecompileStatus {
56    /// Precompile executed successfully.
57    Success,
58    /// Precompile reverted (non-fatal, returns remaining gas).
59    Revert,
60    /// Precompile halted with a specific reason.
61    Halt(PrecompileHalt),
62}
63
64impl PrecompileStatus {
65    /// Returns `true` if the precompile execution was successful or reverted.
66    #[inline]
67    pub const fn is_success_or_revert(&self) -> bool {
68        matches!(self, PrecompileStatus::Success | PrecompileStatus::Revert)
69    }
70
71    /// Returns `true` if the precompile execution was reverted or halted.
72    #[inline]
73    pub const fn is_revert_or_halt(&self) -> bool {
74        matches!(self, PrecompileStatus::Revert | PrecompileStatus::Halt(_))
75    }
76
77    /// Returns the halt reason if the precompile halted, `None` otherwise.
78    #[inline]
79    pub const fn halt_reason(&self) -> Option<&PrecompileHalt> {
80        match &self {
81            PrecompileStatus::Halt(reason) => Some(reason),
82            _ => None,
83        }
84    }
85
86    /// Returns `true` if the precompile execution was successful.
87    #[inline]
88    pub const fn is_success(&self) -> bool {
89        matches!(self, PrecompileStatus::Success)
90    }
91
92    /// Returns `true` if the precompile reverted.
93    #[inline]
94    pub const fn is_revert(&self) -> bool {
95        matches!(self, PrecompileStatus::Revert)
96    }
97
98    /// Returns `true` if the precompile halted.
99    #[inline]
100    pub const fn is_halt(&self) -> bool {
101        matches!(self, PrecompileStatus::Halt(_))
102    }
103}
104
105/// Rich precompile execution output with gas accounting and status support.
106///
107/// This is the output type used at the precompile provider level. It can express
108/// successful execution, reverts, and halts (non-fatal errors like out-of-gas).
109#[derive(Clone, Debug, PartialEq, Eq, Hash)]
110pub struct PrecompileOutput {
111    /// Status of the precompile execution.
112    pub status: PrecompileStatus,
113    /// Regular gas used by the precompile.
114    pub gas_used: u64,
115    /// Gas refunded by the precompile.
116    pub gas_refunded: i64,
117    /// State gas used by the precompile.
118    pub state_gas_used: i64,
119    /// State gas that was drawn from regular gas because the reservoir was
120    /// empty (EIP-8037's `state_gas_from_gas_left`).
121    ///
122    /// Must be the portion of `state_gas_used` that did not fit in the
123    /// reservoir the precompile was given. It is propagated to the frame's gas
124    /// tracker so that a later revert or halt credits that portion back to
125    /// regular gas instead of the reservoir.
126    pub state_gas_spilled: u64,
127    /// Reservoir gas for EIP-8037.
128    pub reservoir: u64,
129    /// Output bytes.
130    pub bytes: Bytes,
131}
132
133impl PrecompileOutput {
134    /// Returns a new precompile output from an Ethereum precompile result.
135    pub fn from_eth_result(result: EthPrecompileResult, reservoir: u64) -> Self {
136        match result {
137            Ok(output) => Self::new(output.gas_used, output.bytes, reservoir),
138            Err(halt) => Self::halt(halt, reservoir),
139        }
140    }
141    /// Returns a new successful precompile output.
142    pub const fn new(gas_used: u64, bytes: Bytes, reservoir: u64) -> Self {
143        Self {
144            status: PrecompileStatus::Success,
145            gas_used,
146            gas_refunded: 0,
147            state_gas_used: 0,
148            state_gas_spilled: 0,
149            reservoir,
150            bytes,
151        }
152    }
153
154    /// Returns a new halted precompile output with the given halt reason.
155    pub const fn halt(reason: PrecompileHalt, reservoir: u64) -> Self {
156        Self {
157            status: PrecompileStatus::Halt(reason),
158            gas_used: 0,
159            gas_refunded: 0,
160            state_gas_used: 0,
161            state_gas_spilled: 0,
162            reservoir,
163            bytes: Bytes::new(),
164        }
165    }
166
167    /// Returns a new reverted precompile output.
168    pub const fn revert(gas_used: u64, bytes: Bytes, reservoir: u64) -> Self {
169        Self {
170            status: PrecompileStatus::Revert,
171            gas_used,
172            gas_refunded: 0,
173            state_gas_used: 0,
174            state_gas_spilled: 0,
175            reservoir,
176            bytes,
177        }
178    }
179
180    /// Returns a precompile output that mirrors the gas accounting of `tracker`.
181    ///
182    /// The regular gas used is `tracker.limit() - tracker.remaining()`; the
183    /// refund, state gas (with its spilled portion) and the reservoir are taken
184    /// as-is. Use this when a precompile drives a [`GasTracker`] internally and
185    /// needs to hand the result back to the provider.
186    pub const fn from_gas_tracker(
187        status: PrecompileStatus,
188        bytes: Bytes,
189        tracker: GasTracker,
190    ) -> Self {
191        let mut output = Self {
192            status,
193            gas_used: 0,
194            gas_refunded: 0,
195            state_gas_used: 0,
196            state_gas_spilled: 0,
197            reservoir: 0,
198            bytes,
199        };
200        output.set_gas(tracker);
201        output
202    }
203
204    /// Overwrites the gas accounting fields from `tracker`, leaving status and
205    /// output bytes untouched.
206    ///
207    /// The regular gas used is `tracker.limit() - tracker.remaining()`; the
208    /// refund, state gas (with its spilled portion) and the reservoir are taken
209    /// as-is. All fields are replaced, not accumulated.
210    pub const fn set_gas(&mut self, tracker: GasTracker) {
211        self.gas_used = tracker.limit().saturating_sub(tracker.remaining());
212        self.gas_refunded = tracker.refunded();
213        self.state_gas_used = tracker.state_gas_spent();
214        self.state_gas_spilled = tracker.state_gas_spilled();
215        self.reservoir = tracker.reservoir();
216    }
217
218    /// Returns a [`GasTracker`] for `gas_limit` that reflects this output.
219    ///
220    /// Inverse of [`from_gas_tracker`](Self::from_gas_tracker): `gas_used` is
221    /// deducted from the regular gas (saturating at zero), and the refund, state
222    /// gas, spilled state gas and reservoir are restored.
223    pub const fn to_gas_tracker(&self, gas_limit: u64) -> GasTracker {
224        let mut tracker = GasTracker::new_used_gas(gas_limit, self.gas_used, self.reservoir);
225        tracker.set_refunded(self.gas_refunded);
226        tracker.set_state_gas_spent(self.state_gas_used);
227        tracker.set_state_gas_spilled(self.state_gas_spilled);
228        tracker
229    }
230
231    /// Returns `true` if the precompile execution was successful.
232    pub const fn is_success(&self) -> bool {
233        matches!(self.status, PrecompileStatus::Success)
234    }
235
236    /// Returns `true` if the precompile execution was successful.
237    #[deprecated(note = "use `is_success` instead")]
238    pub const fn is_ok(&self) -> bool {
239        self.is_success()
240    }
241
242    /// Returns `true` if the precompile reverted.
243    pub const fn is_revert(&self) -> bool {
244        matches!(self.status, PrecompileStatus::Revert)
245    }
246
247    /// Returns `true` if the precompile halted.
248    pub const fn is_halt(&self) -> bool {
249        matches!(self.status, PrecompileStatus::Halt(_))
250    }
251
252    /// Returns the halt reason if the precompile halted, `None` otherwise.
253    #[inline]
254    pub const fn halt_reason(&self) -> Option<&PrecompileHalt> {
255        self.status.halt_reason()
256    }
257}
258
259/// Crypto operations trait for precompiles.
260pub trait Crypto: Send + Sync + Debug {
261    /// Compute SHA-256 hash
262    #[inline]
263    fn sha256(&self, input: &[u8]) -> [u8; 32] {
264        use sha2::Digest;
265        let output = sha2::Sha256::digest(input);
266        output.into()
267    }
268
269    /// Compute RIPEMD-160 hash
270    #[inline]
271    fn ripemd160(&self, input: &[u8]) -> [u8; 32] {
272        use ripemd::Digest;
273        let mut hasher = ripemd::Ripemd160::new();
274        hasher.update(input);
275
276        let mut output = [0u8; 32];
277        let hash: &mut [u8; 20] = (&mut output[12..]).try_into().unwrap();
278        hasher.finalize_into(hash.into());
279        output
280    }
281
282    /// BN254 elliptic curve addition.
283    #[inline]
284    fn bn254_g1_add(&self, p1: &[u8], p2: &[u8]) -> Result<[u8; 64], PrecompileHalt> {
285        crate::bn254::crypto_backend::g1_point_add(p1, p2)
286    }
287
288    /// BN254 elliptic curve scalar multiplication.
289    #[inline]
290    fn bn254_g1_mul(&self, point: &[u8], scalar: &[u8]) -> Result<[u8; 64], PrecompileHalt> {
291        crate::bn254::crypto_backend::g1_point_mul(point, scalar)
292    }
293
294    /// BN254 pairing check.
295    #[inline]
296    fn bn254_pairing_check(&self, pairs: &[(&[u8], &[u8])]) -> Result<bool, PrecompileHalt> {
297        crate::bn254::crypto_backend::pairing_check(pairs)
298    }
299
300    /// secp256k1 ECDSA signature recovery.
301    #[inline]
302    fn secp256k1_ecrecover(
303        &self,
304        sig: &[u8; 64],
305        recid: u8,
306        msg: &[u8; 32],
307    ) -> Result<[u8; 32], PrecompileHalt> {
308        crate::secp256k1::ecrecover_bytes(sig, recid, msg)
309            .ok_or(PrecompileHalt::Secp256k1RecoverFailed)
310    }
311
312    /// Modular exponentiation.
313    #[inline]
314    fn modexp(&self, base: &[u8], exp: &[u8], modulus: &[u8]) -> Result<Vec<u8>, PrecompileHalt> {
315        Ok(crate::modexp::modexp(base, exp, modulus))
316    }
317
318    /// Blake2 compression function.
319    #[inline]
320    fn blake2_compress(&self, rounds: u32, h: &mut [u64; 8], m: &[u64; 16], t: &[u64; 2], f: bool) {
321        crate::blake2::compress(rounds, h, m, t, f);
322    }
323
324    /// secp256r1 (P-256) signature verification.
325    #[inline]
326    fn secp256r1_verify_signature(&self, msg: &[u8; 32], sig: &[u8; 64], pk: &[u8; 64]) -> bool {
327        crate::secp256r1::verify_signature(msg, sig, pk).is_some()
328    }
329
330    /// KZG point evaluation.
331    #[inline]
332    fn verify_kzg_proof(
333        &self,
334        z: &[u8; 32],
335        y: &[u8; 32],
336        commitment: &[u8; 48],
337        proof: &[u8; 48],
338    ) -> Result<(), PrecompileHalt> {
339        if !crate::kzg_point_evaluation::verify_kzg_proof(commitment, z, y, proof) {
340            return Err(PrecompileHalt::BlobVerifyKzgProofFailed);
341        }
342
343        Ok(())
344    }
345
346    /// BLS12-381 G1 addition (returns 96-byte unpadded G1 point)
347    fn bls12_381_g1_add(&self, a: G1Point, b: G1Point) -> Result<[u8; 96], PrecompileHalt> {
348        crate::bls12_381::crypto_backend::p1_add_affine_bytes(a, b)
349    }
350
351    /// BLS12-381 G1 multi-scalar multiplication (returns 96-byte unpadded G1 point)
352    fn bls12_381_g1_msm(
353        &self,
354        pairs: &mut dyn Iterator<Item = Result<G1PointScalar, PrecompileHalt>>,
355    ) -> Result<[u8; 96], PrecompileHalt> {
356        crate::bls12_381::crypto_backend::p1_msm_bytes(pairs)
357    }
358
359    /// BLS12-381 G2 addition (returns 192-byte unpadded G2 point)
360    fn bls12_381_g2_add(&self, a: G2Point, b: G2Point) -> Result<[u8; 192], PrecompileHalt> {
361        crate::bls12_381::crypto_backend::p2_add_affine_bytes(a, b)
362    }
363
364    /// BLS12-381 G2 multi-scalar multiplication (returns 192-byte unpadded G2 point)
365    fn bls12_381_g2_msm(
366        &self,
367        pairs: &mut dyn Iterator<Item = Result<G2PointScalar, PrecompileHalt>>,
368    ) -> Result<[u8; 192], PrecompileHalt> {
369        crate::bls12_381::crypto_backend::p2_msm_bytes(pairs)
370    }
371
372    /// BLS12-381 pairing check.
373    fn bls12_381_pairing_check(
374        &self,
375        pairs: &[(G1Point, G2Point)],
376    ) -> Result<bool, PrecompileHalt> {
377        crate::bls12_381::crypto_backend::pairing_check_bytes(pairs)
378    }
379
380    /// BLS12-381 map field element to G1.
381    fn bls12_381_fp_to_g1(&self, fp: &[u8; 48]) -> Result<[u8; 96], PrecompileHalt> {
382        crate::bls12_381::crypto_backend::map_fp_to_g1_bytes(fp)
383    }
384
385    /// BLS12-381 map field element to G2.
386    fn bls12_381_fp2_to_g2(&self, fp2: ([u8; 48], [u8; 48])) -> Result<[u8; 192], PrecompileHalt> {
387        crate::bls12_381::crypto_backend::map_fp2_to_g2_bytes(&fp2.0, &fp2.1)
388    }
389}
390
391/// Eth precompile function type. Takes input and gas limit, returns an Eth precompile result.
392///
393/// This is the function signature used by individual Ethereum precompile implementations.
394/// Use [`PrecompileFn`] for the higher-level type that returns [`PrecompileOutput`].
395pub type PrecompileEthFn = fn(&[u8], u64) -> EthPrecompileResult;
396
397/// Precompile function type. Takes input, gas limit and reservoir, returns a [`PrecompileResult`].
398///
399/// Returns `Ok(PrecompileOutput)` for successful execution or non-fatal halts,
400/// or `Err(PrecompileError)` for fatal/unrecoverable errors that should abort EVM execution.
401pub type PrecompileFn = fn(&[u8], u64, u64) -> PrecompileResult;
402
403/// Macro that generates a thin wrapper function converting a [`PrecompileEthFn`] into a [`PrecompileFn`].
404///
405/// Usage:
406/// ```ignore
407/// eth_precompile_fn!(my_precompile, my_eth_fn);
408/// ```
409/// Expands to:
410/// ```ignore
411/// fn my_precompile(input: &[u8], gas_limit: u64, reservoir: u64) -> PrecompileOutput {
412///     call_eth_precompile(my_eth_fn, input, gas_limit, reservoir)
413/// }
414/// ```
415#[macro_export]
416macro_rules! eth_precompile_fn {
417    ($name:ident, $eth_fn:expr) => {
418        fn $name(input: &[u8], gas_limit: u64, reservoir: u64) -> $crate::PrecompileResult {
419            Ok($crate::call_eth_precompile(
420                $eth_fn, input, gas_limit, reservoir,
421            ))
422        }
423    };
424}
425
426/// Calls a [`PrecompileEthFn`] and wraps the result into a [`PrecompileOutput`].
427///
428/// Use this in wrapper functions to adapt an eth precompile to the [`PrecompileFn`] signature:
429/// ```ignore
430/// fn my_precompile(input: &[u8], gas_limit: u64, reservoir: u64) -> PrecompileOutput {
431///     call_eth_precompile(my_eth_fn, input, gas_limit, reservoir)
432/// }
433/// ```
434#[inline]
435pub fn call_eth_precompile(
436    f: PrecompileEthFn,
437    input: &[u8],
438    gas_limit: u64,
439    reservoir: u64,
440) -> PrecompileOutput {
441    match f(input, gas_limit) {
442        Ok(output) => PrecompileOutput::new(output.gas_used, output.bytes, reservoir),
443        Err(halt) => PrecompileOutput::halt(halt, reservoir),
444    }
445}
446
447/// Non-fatal halt reasons for precompiles.
448///
449/// These represent conditions that halt precompile execution but do not abort
450/// the entire EVM transaction. They are expressed through [`PrecompileStatus::Halt`]
451/// at the provider level.
452#[derive(Clone, Debug, PartialEq, Eq, Hash)]
453pub enum PrecompileHalt {
454    /// out of gas is the main error. Others are here just for completeness
455    OutOfGas,
456    /// Blake2 errors
457    Blake2WrongLength,
458    /// Blake2 wrong final indicator flag
459    Blake2WrongFinalIndicatorFlag,
460    /// Modexp errors
461    ModexpExpOverflow,
462    /// Modexp base overflow
463    ModexpBaseOverflow,
464    /// Modexp mod overflow
465    ModexpModOverflow,
466    /// Modexp limit all input sizes.
467    ModexpEip7823LimitSize,
468    /// Bn254 errors
469    Bn254FieldPointNotAMember,
470    /// Bn254 affine g failed to create
471    Bn254AffineGFailedToCreate,
472    /// Bn254 pair length
473    Bn254PairLength,
474    // Blob errors
475    /// The input length is not exactly 192 bytes
476    BlobInvalidInputLength,
477    /// The commitment does not match the versioned hash
478    BlobMismatchedVersion,
479    /// The proof verification failed
480    BlobVerifyKzgProofFailed,
481    /// Non-canonical field element
482    NonCanonicalFp,
483    /// BLS12-381 G1 point not on curve
484    Bls12381G1NotOnCurve,
485    /// BLS12-381 G1 point not in correct subgroup
486    Bls12381G1NotInSubgroup,
487    /// BLS12-381 G2 point not on curve
488    Bls12381G2NotOnCurve,
489    /// BLS12-381 G2 point not in correct subgroup
490    Bls12381G2NotInSubgroup,
491    /// BLS12-381 scalar input length error
492    Bls12381ScalarInputLength,
493    /// BLS12-381 G1 add input length error
494    Bls12381G1AddInputLength,
495    /// BLS12-381 G1 msm input length error
496    Bls12381G1MsmInputLength,
497    /// BLS12-381 G2 add input length error
498    Bls12381G2AddInputLength,
499    /// BLS12-381 G2 msm input length error
500    Bls12381G2MsmInputLength,
501    /// BLS12-381 pairing input length error
502    Bls12381PairingInputLength,
503    /// BLS12-381 map fp to g1 input length error
504    Bls12381MapFpToG1InputLength,
505    /// BLS12-381 map fp2 to g2 input length error
506    Bls12381MapFp2ToG2InputLength,
507    /// BLS12-381 padding error
508    Bls12381FpPaddingInvalid,
509    /// BLS12-381 fp padding length error
510    Bls12381FpPaddingLength,
511    /// BLS12-381 g1 padding length error
512    Bls12381G1PaddingLength,
513    /// BLS12-381 g2 padding length error
514    Bls12381G2PaddingLength,
515    /// KZG invalid G1 point
516    KzgInvalidG1Point,
517    /// KZG G1 point not on curve
518    KzgG1PointNotOnCurve,
519    /// KZG G1 point not in correct subgroup
520    KzgG1PointNotInSubgroup,
521    /// KZG input length error
522    KzgInvalidInputLength,
523    /// secp256k1 ecrecover failed
524    Secp256k1RecoverFailed,
525    /// Catch-all variant for precompile halt reasons without a dedicated variant.
526    Other(Cow<'static, str>),
527}
528
529impl PrecompileHalt {
530    /// Returns another halt reason with the given message.
531    pub fn other(err: impl Into<String>) -> Self {
532        Self::Other(Cow::Owned(err.into()))
533    }
534
535    /// Returns another halt reason with the given static string.
536    pub const fn other_static(err: &'static str) -> Self {
537        Self::Other(Cow::Borrowed(err))
538    }
539
540    /// Returns `true` if the halt reason is out of gas.
541    pub const fn is_oog(&self) -> bool {
542        matches!(self, Self::OutOfGas)
543    }
544}
545
546impl core::error::Error for PrecompileHalt {}
547
548impl fmt::Display for PrecompileHalt {
549    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
550        let s = match self {
551            Self::OutOfGas => "out of gas",
552            Self::Blake2WrongLength => "wrong input length for blake2",
553            Self::Blake2WrongFinalIndicatorFlag => "wrong final indicator flag for blake2",
554            Self::ModexpExpOverflow => "modexp exp overflow",
555            Self::ModexpBaseOverflow => "modexp base overflow",
556            Self::ModexpModOverflow => "modexp mod overflow",
557            Self::ModexpEip7823LimitSize => "Modexp limit all input sizes.",
558            Self::Bn254FieldPointNotAMember => "field point not a member of bn254 curve",
559            Self::Bn254AffineGFailedToCreate => "failed to create affine g point for bn254 curve",
560            Self::Bn254PairLength => "bn254 invalid pair length",
561            Self::BlobInvalidInputLength => "invalid blob input length",
562            Self::BlobMismatchedVersion => "mismatched blob version",
563            Self::BlobVerifyKzgProofFailed => "verifying blob kzg proof failed",
564            Self::NonCanonicalFp => "non-canonical field element",
565            Self::Bls12381G1NotOnCurve => "bls12-381 g1 point not on curve",
566            Self::Bls12381G1NotInSubgroup => "bls12-381 g1 point not in correct subgroup",
567            Self::Bls12381G2NotOnCurve => "bls12-381 g2 point not on curve",
568            Self::Bls12381G2NotInSubgroup => "bls12-381 g2 point not in correct subgroup",
569            Self::Bls12381ScalarInputLength => "bls12-381 scalar input length error",
570            Self::Bls12381G1AddInputLength => "bls12-381 g1 add input length error",
571            Self::Bls12381G1MsmInputLength => "bls12-381 g1 msm input length error",
572            Self::Bls12381G2AddInputLength => "bls12-381 g2 add input length error",
573            Self::Bls12381G2MsmInputLength => "bls12-381 g2 msm input length error",
574            Self::Bls12381PairingInputLength => "bls12-381 pairing input length error",
575            Self::Bls12381MapFpToG1InputLength => "bls12-381 map fp to g1 input length error",
576            Self::Bls12381MapFp2ToG2InputLength => "bls12-381 map fp2 to g2 input length error",
577            Self::Bls12381FpPaddingInvalid => "bls12-381 fp 64 top bytes of input are not zero",
578            Self::Bls12381FpPaddingLength => "bls12-381 fp padding length error",
579            Self::Bls12381G1PaddingLength => "bls12-381 g1 padding length error",
580            Self::Bls12381G2PaddingLength => "bls12-381 g2 padding length error",
581            Self::KzgInvalidG1Point => "kzg invalid g1 point",
582            Self::KzgG1PointNotOnCurve => "kzg g1 point not on curve",
583            Self::KzgG1PointNotInSubgroup => "kzg g1 point not in correct subgroup",
584            Self::KzgInvalidInputLength => "kzg invalid input length",
585            Self::Secp256k1RecoverFailed => "secp256k1 signature recovery failed",
586            Self::Other(s) => s,
587        };
588        f.write_str(s)
589    }
590}
591
592/// Fatal precompile error type.
593///
594/// These errors represent unrecoverable conditions that abort the entire EVM
595/// transaction. They propagate as `EVMError::Custom`.
596///
597/// For non-fatal halt reasons (like out-of-gas or invalid input), see
598/// [`PrecompileHalt`] which is expressed through [`PrecompileStatus::Halt`].
599#[derive(Clone, Debug, PartialEq, Eq, Hash)]
600pub enum PrecompileError {
601    /// Unrecoverable error that halts EVM execution.
602    Fatal(String),
603    /// Unrecoverable error that halts EVM execution.
604    FatalAny(AnyError),
605}
606
607impl PrecompileError {
608    /// Returns `true` if the error is `Fatal` or `FatalAny`.
609    pub const fn is_fatal(&self) -> bool {
610        true
611    }
612}
613
614impl core::error::Error for PrecompileError {}
615
616impl fmt::Display for PrecompileError {
617    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
618        match self {
619            Self::Fatal(s) => write!(f, "fatal: {s}"),
620            Self::FatalAny(s) => write!(f, "fatal: {s}"),
621        }
622    }
623}
624
625/// Default implementation of the Crypto trait using the existing crypto libraries.
626#[derive(Clone, Debug)]
627pub struct DefaultCrypto;
628
629impl Crypto for DefaultCrypto {}
630
631#[cfg(test)]
632mod tests {
633    use super::*;
634
635    /// A tracker that spilled state gas must survive the round trip through
636    /// [`PrecompileOutput`] unchanged.
637    #[test]
638    fn gas_tracker_round_trip() {
639        // 100 regular gas, 10 reservoir. Charge 10 regular and 30 state gas,
640        // 20 of which spills out of the reservoir into regular gas.
641        let mut tracker = GasTracker::new(100, 100, 10);
642        assert!(tracker.record_regular_cost(10));
643        assert!(tracker.record_state_cost(30));
644        tracker.record_refund(5);
645        assert_eq!(
646            (tracker.remaining(), tracker.reservoir()),
647            (70, 0),
648            "10 regular + 20 spilled state gas"
649        );
650
651        let output =
652            PrecompileOutput::from_gas_tracker(PrecompileStatus::Success, Bytes::new(), tracker);
653        assert_eq!(output.gas_used, 30);
654        assert_eq!(output.gas_refunded, 5);
655        assert_eq!(output.state_gas_used, 30);
656        assert_eq!(output.state_gas_spilled, 20);
657        assert_eq!(output.reservoir, 0);
658
659        assert_eq!(output.to_gas_tracker(tracker.limit()), tracker);
660
661        // set_gas replaces the gas fields of an existing output, keeping the rest
662        let mut existing = PrecompileOutput::revert(1, Bytes::from_static(b"out"), 999);
663        existing.set_gas(tracker);
664        assert_eq!(existing.status, PrecompileStatus::Revert);
665        assert_eq!(existing.bytes, Bytes::from_static(b"out"));
666        assert_eq!(
667            (
668                existing.gas_used,
669                existing.gas_refunded,
670                existing.state_gas_used,
671                existing.state_gas_spilled,
672                existing.reservoir
673            ),
674            (30, 5, 30, 20, 0)
675        );
676    }
677
678    /// Gas used above the limit saturates instead of wrapping.
679    #[test]
680    fn to_gas_tracker_saturates_on_overspend() {
681        let mut output = PrecompileOutput::new(0, Bytes::new(), 0);
682        output.gas_used = u64::MAX;
683        assert_eq!(output.to_gas_tracker(100).remaining(), 0);
684    }
685}