Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/precompile/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
}
Expand Down Expand Up @@ -102,6 +103,15 @@ pub(crate) fn feynman() -> &'static Precompiles {
})
}

pub(crate) fn galileo() -> &'static Precompiles {
static INSTANCE: OnceBox<Precompiles> = OnceBox::new();
INSTANCE.get_or_init(|| {
let mut precompiles = feynman().clone();
precompiles.extend([modexp::GALILEO, secp256r1::P256VERIFY_OSAKA]);
Box::new(precompiles)
})
}

impl<CTX> PrecompileProvider<CTX> for ScrollPrecompileProvider
where
CTX: ContextTr<Cfg: Cfg<Spec = ScrollSpecId>>,
Expand Down
150 changes: 150 additions & 0 deletions src/precompile/modexp.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use revm::{
precompile::{
modexp,
modexp::{berlin_gas_calc, run_inner},
u64_to_address,
utilities::right_pad_with_offset,
Expand All @@ -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
Expand Down Expand Up @@ -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"
);
}
}
}
7 changes: 6 additions & 1 deletion src/spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub enum ScrollSpecId {
#[default]
EUCLID = 5,
FEYNMAN = 6,
GALILEO = 7,
}

impl ScrollSpecId {
Expand Down Expand Up @@ -41,7 +42,8 @@ impl ScrollSpecId {
Self::CURIE |
Self::DARWIN |
Self::EUCLID |
Self::FEYNMAN => SpecId::SHANGHAI,
Self::FEYNMAN |
Self::GALILEO => SpecId::SHANGHAI,
}
}
}
Expand All @@ -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 {
Expand All @@ -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(),
}
}
Expand All @@ -87,6 +91,7 @@ impl From<ScrollSpecId> for &'static str {
ScrollSpecId::DARWIN => name::DARWIN,
ScrollSpecId::EUCLID => name::EUCLID,
ScrollSpecId::FEYNMAN => name::FEYNMAN,
ScrollSpecId::GALILEO => name::GALILEO,
}
}
}
Loading