From 928883c26c21feaed45fefc78a4d1a810bbbd3b5 Mon Sep 17 00:00:00 2001 From: Brian Bland Date: Sun, 26 May 2024 09:02:34 -0700 Subject: [PATCH] feat(optimism): Add secp256r1 precompile for Fjord (#1436) * feat(optimism): Add secp256r1 precompile for Fjord * Fix docs * Fix nostd build * Load fjord precompiles via optimism handler register * Remove outdated fjord() precompile spec constructor * Document the secp256r1 feature * Address feedback * Handle invalid signatures * Update crates/precompile/src/secp256r1.rs * Update crates/precompile/src/secp256r1.rs * Blank return on failed signature verification * Add test case for invalid (zero) pubkey --------- Co-authored-by: rakita --- Cargo.lock | 22 ++++ crates/precompile/Cargo.toml | 8 +- crates/precompile/src/lib.rs | 4 +- crates/precompile/src/secp256r1.rs | 128 +++++++++++++++++++ crates/primitives/src/specification.rs | 43 ++++++- crates/revm/src/db/in_memory_db.rs | 2 +- crates/revm/src/optimism.rs | 4 +- crates/revm/src/optimism/handler_register.rs | 20 ++- 8 files changed, 224 insertions(+), 7 deletions(-) create mode 100644 crates/precompile/src/secp256r1.rs diff --git a/Cargo.lock b/Cargo.lock index fa834e7cb6..46150b3b02 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2404,6 +2404,18 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + [[package]] name = "parity-scale-codec" version = "3.6.12" @@ -2630,6 +2642,15 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "primitive-types" version = "0.12.2" @@ -2972,6 +2993,7 @@ dependencies = [ "eyre", "k256", "once_cell", + "p256", "rand", "revm-primitives", "ripemd", diff --git a/crates/precompile/Cargo.toml b/crates/precompile/Cargo.toml index f761e726e1..08abf55bb6 100644 --- a/crates/precompile/Cargo.toml +++ b/crates/precompile/Cargo.toml @@ -44,6 +44,9 @@ secp256k1 = { version = "0.29.0", default-features = false, features = [ # BLS12-381 precompiles blst = { version = "0.3.11", optional = true } +# p256verify precompile +p256 = { version = "0.13.2", optional = true, default-features = false, features = ["ecdsa"] } + [dev-dependencies] criterion = { version = "0.5" } rand = { version = "0.8", features = ["std"] } @@ -67,7 +70,7 @@ std = [ hashbrown = ["revm-primitives/hashbrown"] asm-keccak = ["revm-primitives/asm-keccak"] -optimism = ["revm-primitives/optimism"] +optimism = ["revm-primitives/optimism", "secp256r1"] # Optimism default handler enabled Optimism handler register by default in EvmBuilder. optimism-default-handler = [ "optimism", @@ -77,6 +80,9 @@ negate-optimism-default-handler = [ "revm-primitives/negate-optimism-default-handler", ] +# Enables the p256verify precompile. +secp256r1 = ["dep:p256"] + # These libraries may not work on all no_std platforms as they depend on C. # Enables the KZG point evaluation precompile. diff --git a/crates/precompile/src/lib.rs b/crates/precompile/src/lib.rs index fe2486d3d1..ca797c8118 100644 --- a/crates/precompile/src/lib.rs +++ b/crates/precompile/src/lib.rs @@ -18,6 +18,8 @@ pub mod identity; pub mod kzg_point_evaluation; pub mod modexp; pub mod secp256k1; +#[cfg(feature = "secp256r1")] +pub mod secp256r1; pub mod utilities; use core::hash::Hash; @@ -271,7 +273,7 @@ impl PrecompileSpecId { #[cfg(feature = "optimism")] BEDROCK | REGOLITH | CANYON => Self::BERLIN, #[cfg(feature = "optimism")] - ECOTONE => Self::CANCUN, + ECOTONE | FJORD => Self::CANCUN, } } } diff --git a/crates/precompile/src/secp256r1.rs b/crates/precompile/src/secp256r1.rs new file mode 100644 index 0000000000..e2c9951a21 --- /dev/null +++ b/crates/precompile/src/secp256r1.rs @@ -0,0 +1,128 @@ +//! # EIP-7212 secp256r1 Precompile +//! +//! This module implements the [EIP-7212](https://eips.ethereum.org/EIPS/eip-7212) precompile for +//! secp256r1 curve support. +//! +//! The main purpose of this precompile is to verify ECDSA signatures that use the secp256r1, or +//! P256 elliptic curve. The [`P256VERIFY`] const represents the implementation of this precompile, +//! with the address that it is currently deployed at. +use crate::{u64_to_address, Precompile, PrecompileWithAddress}; +use p256::ecdsa::{signature::hazmat::PrehashVerifier, Signature, VerifyingKey}; +use revm_primitives::{Bytes, PrecompileError, PrecompileResult, B256}; + +/// Base gas fee for secp256r1 p256verify operation. +const P256VERIFY_BASE: u64 = 3450; + +/// Returns the secp256r1 precompile with its address. +pub fn precompiles() -> impl Iterator { + [P256VERIFY].into_iter() +} + +/// [EIP-7212](https://eips.ethereum.org/EIPS/eip-7212#specification) secp256r1 precompile. +pub const P256VERIFY: PrecompileWithAddress = + PrecompileWithAddress(u64_to_address(0x100), Precompile::Standard(p256_verify)); + +/// secp256r1 precompile logic. It takes the input bytes sent to the precompile +/// and the gas limit. The output represents the result of verifying the +/// secp256r1 signature of the input. +/// +/// The input is encoded as follows: +/// +/// | signed message hash | r | s | public key x | public key y | +/// | :-----------------: | :-: | :-: | :----------: | :----------: | +/// | 32 | 32 | 32 | 32 | 32 | +pub fn p256_verify(input: &Bytes, gas_limit: u64) -> PrecompileResult { + if P256VERIFY_BASE > gas_limit { + return Err(PrecompileError::OutOfGas); + } + let result = if verify_impl(input).is_some() { + B256::with_last_byte(1).into() + } else { + Bytes::new() + }; + Ok((P256VERIFY_BASE, result)) +} + +/// Returns `Some(())` if the signature included in the input byte slice is +/// valid, `None` otherwise. +pub fn verify_impl(input: &[u8]) -> Option<()> { + if input.len() != 160 { + return None; + } + + // msg signed (msg is already the hash of the original message) + let msg = &input[..32]; + // r, s: signature + let sig = &input[32..96]; + // x, y: public key + let pk = &input[96..160]; + + // prepend 0x04 to the public key: uncompressed form + let mut uncompressed_pk = [0u8; 65]; + uncompressed_pk[0] = 0x04; + uncompressed_pk[1..].copy_from_slice(pk); + + // Can fail only if the input is not exact length. + let signature = Signature::from_slice(sig).ok()?; + // Can fail if the input is not valid, so we have to propagate the error. + let public_key = VerifyingKey::from_sec1_bytes(&uncompressed_pk).ok()?; + + public_key.verify_prehash(msg, &signature).ok() +} + +#[cfg(test)] +mod test { + use super::*; + use revm_primitives::hex::FromHex; + use rstest::rstest; + + #[rstest] + // test vectors from https://github.com/daimo-eth/p256-verifier/tree/master/test-vectors + #[case::ok_1("4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e", true)] + #[case::ok_2("3fec5769b5cf4e310a7d150508e82fb8e3eda1c2c94c61492d3bd8aea99e06c9e22466e928fdccef0de49e3503d2657d00494a00e764fd437bdafa05f5922b1fbbb77c6817ccf50748419477e843d5bac67e6a70e97dde5a57e0c983b777e1ad31a80482dadf89de6302b1988c82c29544c9c07bb910596158f6062517eb089a2f54c9a0f348752950094d3228d3b940258c75fe2a413cb70baa21dc2e352fc5", true)] + #[case::ok_3("e775723953ead4a90411a02908fd1a629db584bc600664c609061f221ef6bf7c440066c8626b49daaa7bf2bcc0b74be4f7a1e3dcf0e869f1542fe821498cbf2de73ad398194129f635de4424a07ca715838aefe8fe69d1a391cfa70470795a80dd056866e6e1125aff94413921880c437c9e2570a28ced7267c8beef7e9b2d8d1547d76dfcf4bee592f5fefe10ddfb6aeb0991c5b9dbbee6ec80d11b17c0eb1a", true)] + #[case::ok_4("b5a77e7a90aa14e0bf5f337f06f597148676424fae26e175c6e5621c34351955289f319789da424845c9eac935245fcddd805950e2f02506d09be7e411199556d262144475b1fa46ad85250728c600c53dfd10f8b3f4adf140e27241aec3c2da3a81046703fccf468b48b145f939efdbb96c3786db712b3113bb2488ef286cdcef8afe82d200a5bb36b5462166e8ce77f2d831a52ef2135b2af188110beaefb1", true)] + #[case::ok_5("858b991cfd78f16537fe6d1f4afd10273384db08bdfc843562a22b0626766686f6aec8247599f40bfe01bec0e0ecf17b4319559022d4d9bf007fe929943004eb4866760dedf31b7c691f5ce665f8aae0bda895c23595c834fecc2390a5bcc203b04afcacbb4280713287a2d0c37e23f7513fab898f2c1fefa00ec09a924c335d9b629f1d4fb71901c3e59611afbfea354d101324e894c788d1c01f00b3c251b2", true)] + #[case::fail_wrong_msg_1("3cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e", false)] + #[case::fail_wrong_msg_2("afec5769b5cf4e310a7d150508e82fb8e3eda1c2c94c61492d3bd8aea99e06c9e22466e928fdccef0de49e3503d2657d00494a00e764fd437bdafa05f5922b1fbbb77c6817ccf50748419477e843d5bac67e6a70e97dde5a57e0c983b777e1ad31a80482dadf89de6302b1988c82c29544c9c07bb910596158f6062517eb089a2f54c9a0f348752950094d3228d3b940258c75fe2a413cb70baa21dc2e352fc5", false)] + #[case::fail_wrong_msg_3("f775723953ead4a90411a02908fd1a629db584bc600664c609061f221ef6bf7c440066c8626b49daaa7bf2bcc0b74be4f7a1e3dcf0e869f1542fe821498cbf2de73ad398194129f635de4424a07ca715838aefe8fe69d1a391cfa70470795a80dd056866e6e1125aff94413921880c437c9e2570a28ced7267c8beef7e9b2d8d1547d76dfcf4bee592f5fefe10ddfb6aeb0991c5b9dbbee6ec80d11b17c0eb1a", false)] + #[case::fail_wrong_msg_4("c5a77e7a90aa14e0bf5f337f06f597148676424fae26e175c6e5621c34351955289f319789da424845c9eac935245fcddd805950e2f02506d09be7e411199556d262144475b1fa46ad85250728c600c53dfd10f8b3f4adf140e27241aec3c2da3a81046703fccf468b48b145f939efdbb96c3786db712b3113bb2488ef286cdcef8afe82d200a5bb36b5462166e8ce77f2d831a52ef2135b2af188110beaefb1", false)] + #[case::fail_wrong_msg_5("958b991cfd78f16537fe6d1f4afd10273384db08bdfc843562a22b0626766686f6aec8247599f40bfe01bec0e0ecf17b4319559022d4d9bf007fe929943004eb4866760dedf31b7c691f5ce665f8aae0bda895c23595c834fecc2390a5bcc203b04afcacbb4280713287a2d0c37e23f7513fab898f2c1fefa00ec09a924c335d9b629f1d4fb71901c3e59611afbfea354d101324e894c788d1c01f00b3c251b2", false)] + #[case::fail_short_input_1("4cee90eb86eaa050036147a12d49004b6a", false)] + #[case::fail_short_input_2("4cee90eb86eaa050036147a12d49004b6a958b991cfd78f16537fe6d1f4afd10273384db08bdfc843562a22b0626766686f6aec8247599f40bfe01bec0e0ecf17b4319559022d4d9bf007fe929943004eb4866760dedf319", false)] + #[case::fail_long_input("4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e00", false)] + #[case::fail_invalid_sig("4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e", false)] + #[case::fail_invalid_pubkey("4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d6000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", false)] + fn test_sig_verify(#[case] input: &str, #[case] expect_success: bool) { + let input = Bytes::from_hex(input).unwrap(); + let target_gas = 3_500u64; + let (gas_used, res) = p256_verify(&input, target_gas).unwrap(); + assert_eq!(gas_used, 3_450u64); + let expected_result = if expect_success { + B256::with_last_byte(1).into() + } else { + Bytes::new() + }; + assert_eq!(res, expected_result); + } + + #[rstest] + fn test_not_enough_gas_errors() { + let input = Bytes::from_hex("4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e").unwrap(); + let target_gas = 2_500u64; + let result = p256_verify(&input, target_gas); + + assert!(result.is_err()); + assert_eq!(result.err(), Some(PrecompileError::OutOfGas)); + } + + #[rstest] + #[case::ok_1("b5a77e7a90aa14e0bf5f337f06f597148676424fae26e175c6e5621c34351955289f319789da424845c9eac935245fcddd805950e2f02506d09be7e411199556d262144475b1fa46ad85250728c600c53dfd10f8b3f4adf140e27241aec3c2da3a81046703fccf468b48b145f939efdbb96c3786db712b3113bb2488ef286cdcef8afe82d200a5bb36b5462166e8ce77f2d831a52ef2135b2af188110beaefb1", true)] + #[case::fail_1("b5a77e7a90aa14e0bf5f337f06f597148676424fae26e175c6e5621c34351955289f319789da424845c9eac935245fcddd805950e2f02506d09be7e411199556d262144475b1fa46ad85250728c600c53dfd10f8b3f4adf140e27241aec3c2daaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaef8afe82d200a5bb36b5462166e8ce77f2d831a52ef2135b2af188110beaefb1", false)] + fn test_verify_impl(#[case] input: &str, #[case] expect_success: bool) { + let input = Bytes::from_hex(input).unwrap(); + let result = verify_impl(&input); + + assert_eq!(result.is_some(), expect_success); + } +} diff --git a/crates/primitives/src/specification.rs b/crates/primitives/src/specification.rs index 52abf9ecba..d7c1d3e6cf 100644 --- a/crates/primitives/src/specification.rs +++ b/crates/primitives/src/specification.rs @@ -63,7 +63,8 @@ pub enum SpecId { CANYON = 19, CANCUN = 20, ECOTONE = 21, - PRAGUE = 22, + FJORD = 22, + PRAGUE = 23, #[default] LATEST = u8::MAX, } @@ -114,6 +115,8 @@ impl From<&str> for SpecId { "Canyon" => SpecId::CANYON, #[cfg(feature = "optimism")] "Ecotone" => SpecId::ECOTONE, + #[cfg(feature = "optimism")] + "Fjord" => SpecId::FJORD, _ => Self::LATEST, } } @@ -149,6 +152,8 @@ impl From for &'static str { SpecId::CANYON => "Canyon", #[cfg(feature = "optimism")] SpecId::ECOTONE => "Ecotone", + #[cfg(feature = "optimism")] + SpecId::FJORD => "Fjord", SpecId::LATEST => "Latest", } } @@ -207,6 +212,8 @@ spec!(REGOLITH, RegolithSpec); spec!(CANYON, CanyonSpec); #[cfg(feature = "optimism")] spec!(ECOTONE, EcotoneSpec); +#[cfg(feature = "optimism")] +spec!(FJORD, FjordSpec); #[cfg(not(feature = "optimism"))] #[macro_export] @@ -354,6 +361,10 @@ macro_rules! spec_to_generic { use $crate::EcotoneSpec as SPEC; $e } + $crate::SpecId::FJORD => { + use $crate::FjordSpec as SPEC; + $e + } } }}; } @@ -390,6 +401,10 @@ mod tests { #[cfg(feature = "optimism")] spec_to_generic!(CANYON, assert_eq!(SPEC::SPEC_ID, CANYON)); spec_to_generic!(CANCUN, assert_eq!(SPEC::SPEC_ID, CANCUN)); + #[cfg(feature = "optimism")] + spec_to_generic!(ECOTONE, assert_eq!(SPEC::SPEC_ID, ECOTONE)); + #[cfg(feature = "optimism")] + spec_to_generic!(FJORD, assert_eq!(SPEC::SPEC_ID, FJORD)); spec_to_generic!(PRAGUE, assert_eq!(SPEC::SPEC_ID, PRAGUE)); spec_to_generic!(LATEST, assert_eq!(SPEC::SPEC_ID, LATEST)); } @@ -485,4 +500,30 @@ mod optimism_tests { assert!(SpecId::enabled(SpecId::ECOTONE, SpecId::CANYON)); assert!(SpecId::enabled(SpecId::ECOTONE, SpecId::ECOTONE)); } + + #[test] + fn test_fjord_post_merge_hardforks() { + assert!(FjordSpec::enabled(SpecId::MERGE)); + assert!(FjordSpec::enabled(SpecId::SHANGHAI)); + assert!(FjordSpec::enabled(SpecId::CANCUN)); + assert!(!FjordSpec::enabled(SpecId::LATEST)); + assert!(FjordSpec::enabled(SpecId::BEDROCK)); + assert!(FjordSpec::enabled(SpecId::REGOLITH)); + assert!(FjordSpec::enabled(SpecId::CANYON)); + assert!(FjordSpec::enabled(SpecId::ECOTONE)); + assert!(FjordSpec::enabled(SpecId::FJORD)); + } + + #[test] + fn test_fjord_post_merge_hardforks_spec_id() { + assert!(SpecId::enabled(SpecId::FJORD, SpecId::MERGE)); + assert!(SpecId::enabled(SpecId::FJORD, SpecId::SHANGHAI)); + assert!(SpecId::enabled(SpecId::FJORD, SpecId::CANCUN)); + assert!(!SpecId::enabled(SpecId::FJORD, SpecId::LATEST)); + assert!(SpecId::enabled(SpecId::FJORD, SpecId::BEDROCK)); + assert!(SpecId::enabled(SpecId::FJORD, SpecId::REGOLITH)); + assert!(SpecId::enabled(SpecId::FJORD, SpecId::CANYON)); + assert!(SpecId::enabled(SpecId::FJORD, SpecId::ECOTONE)); + assert!(SpecId::enabled(SpecId::FJORD, SpecId::FJORD)); + } } diff --git a/crates/revm/src/db/in_memory_db.rs b/crates/revm/src/db/in_memory_db.rs index 00602d78b5..a46e6be9cf 100644 --- a/crates/revm/src/db/in_memory_db.rs +++ b/crates/revm/src/db/in_memory_db.rs @@ -482,7 +482,7 @@ mod tests { let serialized = serde_json::to_string(&init_state).unwrap(); let deserialized: CacheDB = serde_json::from_str(&serialized).unwrap(); - assert!(deserialized.accounts.get(&account).is_some()); + assert!(deserialized.accounts.contains_key(&account)); assert_eq!( deserialized.accounts.get(&account).unwrap().info.nonce, nonce diff --git a/crates/revm/src/optimism.rs b/crates/revm/src/optimism.rs index 3923b0c622..c72272b84d 100644 --- a/crates/revm/src/optimism.rs +++ b/crates/revm/src/optimism.rs @@ -4,7 +4,7 @@ mod handler_register; mod l1block; pub use handler_register::{ - deduct_caller, end, last_frame_return, load_accounts, optimism_handle_register, output, - reward_beneficiary, validate_env, validate_tx_against_state, + deduct_caller, end, last_frame_return, load_accounts, load_precompiles, + optimism_handle_register, output, reward_beneficiary, validate_env, validate_tx_against_state, }; pub use l1block::{L1BlockInfo, BASE_FEE_RECIPIENT, L1_BLOCK_CONTRACT, L1_FEE_RECIPIENT}; diff --git a/crates/revm/src/optimism/handler_register.rs b/crates/revm/src/optimism/handler_register.rs index f517f94117..d4996cca21 100644 --- a/crates/revm/src/optimism/handler_register.rs +++ b/crates/revm/src/optimism/handler_register.rs @@ -11,9 +11,10 @@ use crate::{ db::Database, spec_to_generic, Account, EVMError, Env, ExecutionResult, HaltReason, HashMap, InvalidTransaction, ResultAndState, Spec, SpecId, SpecId::REGOLITH, U256, }, - Context, FrameResult, + Context, ContextPrecompiles, FrameResult, }; use core::ops::Mul; +use revm_precompile::{secp256r1, PrecompileSpecId, Precompiles}; use std::string::ToString; use std::sync::Arc; @@ -23,6 +24,8 @@ pub fn optimism_handle_register(handler: &mut EvmHandler<'_, handler.validation.env = Arc::new(validate_env::); // Validate transaction against state. handler.validation.tx_against_state = Arc::new(validate_tx_against_state::); + // Load additional precompiles for the given chain spec. + handler.pre_execution.load_precompiles = Arc::new(load_precompiles::); // load l1 data handler.pre_execution.load_accounts = Arc::new(load_accounts::); // An estimated batch cost is charged from the caller and added to L1 Fee Vault. @@ -137,6 +140,21 @@ pub fn last_frame_return( Ok(()) } +/// Load precompiles for Optimism chain. +#[inline] +pub fn load_precompiles() -> ContextPrecompiles { + let mut precompiles = Precompiles::new(PrecompileSpecId::from_spec_id(SPEC::SPEC_ID)).clone(); + + if SPEC::enabled(SpecId::FJORD) { + precompiles.extend([ + // EIP-7212: secp256r1 P256verify + secp256r1::P256VERIFY, + ]) + } + + precompiles.into() +} + /// Load account (make them warm) and l1 data from database. #[inline] pub fn load_accounts(