diff --git a/src/precompile/mod.rs b/src/precompile/mod.rs index 1028d9b..8aa7129 100644 --- a/src/precompile/mod.rs +++ b/src/precompile/mod.rs @@ -31,6 +31,7 @@ impl ScrollPrecompileProvider { ScrollSpecId::BERNOULLI | ScrollSpecId::CURIE | ScrollSpecId::DARWIN => bernoulli(), ScrollSpecId::EUCLID => euclid(), ScrollSpecId::FEYNMAN => feynman(), + ScrollSpecId::GALILEO => galileo(), }; Self { precompile_provider: EthPrecompiles { precompiles, spec: SpecId::default() }, spec } } @@ -102,6 +103,15 @@ pub(crate) fn feynman() -> &'static Precompiles { }) } +pub(crate) fn galileo() -> &'static Precompiles { + static INSTANCE: OnceBox = OnceBox::new(); + INSTANCE.get_or_init(|| { + let mut precompiles = feynman().clone(); + precompiles.extend([modexp::GALILEO, secp256r1::P256VERIFY_OSAKA]); + Box::new(precompiles) + }) +} + impl PrecompileProvider for ScrollPrecompileProvider where CTX: ContextTr>, diff --git a/src/precompile/modexp.rs b/src/precompile/modexp.rs index 4954343..bd5454a 100644 --- a/src/precompile/modexp.rs +++ b/src/precompile/modexp.rs @@ -1,5 +1,6 @@ use revm::{ precompile::{ + modexp, modexp::{berlin_gas_calc, run_inner}, u64_to_address, utilities::right_pad_with_offset, @@ -17,6 +18,9 @@ pub const BERNOULLI_LEN_LIMIT: U256 = U256::from_limbs([32, 0, 0, 0]); /// The MODEXP precompile with BERNOULLI length limit rule. pub const BERNOULLI: Precompile = Precompile::new(PrecompileId::ModExp, ADDRESS, bernoulli_run); +/// The Galileo MODEXP precompile. +pub const GALILEO: Precompile = Precompile::new(PrecompileId::ModExp, ADDRESS, modexp::osaka_run); + /// The bernoulli MODEXP precompile implementation. /// /// # Errors @@ -45,3 +49,149 @@ pub fn bernoulli_run(input: &[u8], gas_limit: u64) -> PrecompileResult { const OSAKA: bool = false; run_inner::<_, OSAKA>(input, gas_limit, 200, berlin_gas_calc) } + +#[cfg(test)] +mod tests { + use super::*; + use revm::primitives::hex; + use std::vec; + + #[test] + fn test_galileo_modexp_backward_compatibility() { + // Test case: verify that Galileo modexp doesn't affect behavior before FEYNMAN + // Using a standard modexp test case: base=3, exp=5, mod=7 + // Expected result: 3^5 mod 7 = 243 mod 7 = 5 + + // Input format: [base_len(32 bytes)][exp_len(32 bytes)][mod_len(32 bytes)][base][exp][mod] + // base_len = 1, exp_len = 1, mod_len = 1 + // base = 3, exp = 5, mod = 7 + let input = hex::decode( + "0000000000000000000000000000000000000000000000000000000000000001\ + 0000000000000000000000000000000000000000000000000000000000000001\ + 0000000000000000000000000000000000000000000000000000000000000001\ + 03\ + 05\ + 07", + ) + .unwrap(); + + let gas_limit = 100000u64; + + // Test BERNOULLI behavior + let bernoulli_result = bernoulli_run(&input, gas_limit); + assert!(bernoulli_result.is_ok(), "BERNOULLI modexp should succeed"); + let bernoulli_output = bernoulli_result.unwrap(); + + // Test Galileo behavior + let galileo_result = modexp::osaka_run(&input, gas_limit); + assert!(galileo_result.is_ok(), "GALILEO modexp should succeed"); + let galileo_output = galileo_result.unwrap(); + + // Verify both produce the same result + assert_eq!( + bernoulli_output.bytes, galileo_output.bytes, + "Galileo modexp should produce the same result as BERNOULLI for valid inputs" + ); + + // Verify that Galileo uses more gas (OSAKA gas rules vs Berlin gas rules) + assert!( + galileo_output.gas_used >= bernoulli_output.gas_used, + "Galileo should use at least as much gas as BERNOULLI (OSAKA gas rules)" + ); + + // Expected result: 3^5 mod 7 = 5 + let expected = vec![5u8]; + assert_eq!(bernoulli_output.bytes.as_ref(), &expected); + assert_eq!(galileo_output.bytes.as_ref(), &expected); + } + + #[test] + fn test_galileo_modexp_with_32_byte_limit() { + // Test that Galileo handles the 32-byte limit differently than BERNOULLI + // Input with base_len = 33 (exceeds BERNOULLI limit) + let input = hex::decode( + "0000000000000000000000000000000000000000000000000000000000000021\ + 0000000000000000000000000000000000000000000000000000000000000001\ + 0000000000000000000000000000000000000000000000000000000000000001\ + 030303030303030303030303030303030303030303030303030303030303030303\ + 05\ + 07", + ) + .unwrap(); + + let gas_limit = 100000u64; + + // BERNOULLI should reject this (base length > 32) + let bernoulli_result = bernoulli_run(&input, gas_limit); + assert!(bernoulli_result.is_err(), "BERNOULLI should reject base_len > 32"); + assert!( + matches!(bernoulli_result.unwrap_err(), PrecompileError::Other(msg) if msg.contains("ModexpBaseOverflow")), + "BERNOULLI should return ModexpBaseOverflow error" + ); + + // Galileo should accept this (no 32-byte limit) + let galileo_result = modexp::osaka_run(&input, gas_limit); + assert!(galileo_result.is_ok(), "Galileo should accept base_len > 32"); + + // Verify the computed result is correct + // (0x030303...0303)^5 mod 7 = 0 + let galileo_output = galileo_result.unwrap(); + let expected = vec![0u8]; + assert_eq!( + galileo_output.bytes.as_ref(), + &expected, + "Galileo should compute correct modexp result" + ); + } + + #[test] + fn test_galileo_modexp_preserves_feynman_behavior() { + // Test with inputs that should work in all versions + // This ensures Galileo doesn't break existing functionality + let test_cases = vec![ + // Small values + ( + "0000000000000000000000000000000000000000000000000000000000000001\ + 0000000000000000000000000000000000000000000000000000000000000001\ + 0000000000000000000000000000000000000000000000000000000000000001\ + 02\ + 03\ + 05", + vec![3u8], // 2^3 mod 5 = 8 mod 5 = 3 + ), + // Zero exponent (should return 1) + ( + "0000000000000000000000000000000000000000000000000000000000000001\ + 0000000000000000000000000000000000000000000000000000000000000001\ + 0000000000000000000000000000000000000000000000000000000000000001\ + 05\ + 00\ + 0d", + vec![1u8], // 5^0 mod 13 = 1 + ), + ]; + + for (input_hex, expected) in test_cases { + let input = hex::decode(input_hex).unwrap(); + let gas_limit = 100000u64; + + let bernoulli_result = bernoulli_run(&input, gas_limit).unwrap(); + let galileo_result = modexp::osaka_run(&input, gas_limit).unwrap(); + + assert_eq!( + bernoulli_result.bytes.as_ref(), + &expected, + "BERNOULLI should produce expected result" + ); + assert_eq!( + galileo_result.bytes.as_ref(), + &expected, + "Galileo should produce expected result" + ); + assert_eq!( + bernoulli_result.bytes, galileo_result.bytes, + "Galileo and BERNOULLI should produce identical results for valid inputs" + ); + } + } +} diff --git a/src/spec.rs b/src/spec.rs index 47e341b..680ba11 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -12,6 +12,7 @@ pub enum ScrollSpecId { #[default] EUCLID = 5, FEYNMAN = 6, + GALILEO = 7, } impl ScrollSpecId { @@ -41,7 +42,8 @@ impl ScrollSpecId { Self::CURIE | Self::DARWIN | Self::EUCLID | - Self::FEYNMAN => SpecId::SHANGHAI, + Self::FEYNMAN | + Self::GALILEO => SpecId::SHANGHAI, } } } @@ -62,6 +64,7 @@ pub mod name { pub const DARWIN: &str = "darwin"; pub const EUCLID: &str = "euclid"; pub const FEYNMAN: &str = "feynman"; + pub const GALILEO: &str = "galileo"; } impl From<&str> for ScrollSpecId { @@ -73,6 +76,7 @@ impl From<&str> for ScrollSpecId { name::DARWIN => Self::DARWIN, name::EUCLID => Self::EUCLID, name::FEYNMAN => Self::FEYNMAN, + name::GALILEO => Self::GALILEO, _ => Self::default(), } } @@ -87,6 +91,7 @@ impl From for &'static str { ScrollSpecId::DARWIN => name::DARWIN, ScrollSpecId::EUCLID => name::EUCLID, ScrollSpecId::FEYNMAN => name::FEYNMAN, + ScrollSpecId::GALILEO => name::GALILEO, } } }