use crate::{api::exec::MonadContextTr, reserve_balance, staking, MonadHardfork};
use revm::{
context::Cfg,
handler::{EthPrecompiles, PrecompileProvider},
interpreter::{CallInputs, InterpreterResult},
precompile::{
bn254, kzg_point_evaluation, secp256r1, Precompile, PrecompileHalt, PrecompileId,
PrecompileOutput, PrecompileResult, Precompiles,
},
primitives::{alloy_primitives::B512, hardfork::SpecId, Address, Bytes, B256},
};
use std::{boxed::Box, string::String};
pub const MONAD_ECRECOVER_GAS: u64 = 6_000;
pub const MONAD_EC_ADD_GAS: u64 = 300;
pub const MONAD_EC_MUL_GAS: u64 = 30_000;
pub const MONAD_EC_PAIRING_BASE_GAS: u64 = 225_000;
pub const MONAD_EC_PAIRING_PER_POINT_GAS: u64 = 170_000;
pub const MONAD_BLAKE2F_ROUND_GAS: u64 = 2;
pub const MONAD_POINT_EVALUATION_GAS: u64 = 200_000;
fn monad_ecrecover_run(input: &[u8], gas_limit: u64, reservoir: u64) -> PrecompileResult {
use revm::precompile::{crypto, utilities::right_pad};
if MONAD_ECRECOVER_GAS > gas_limit {
return Ok(PrecompileOutput::halt(PrecompileHalt::OutOfGas, reservoir));
}
let input = right_pad::<128>(input);
if !(input[32..63].iter().all(|&b| b == 0) && matches!(input[63], 27 | 28)) {
return Ok(PrecompileOutput::new(MONAD_ECRECOVER_GAS, Bytes::new(), reservoir));
}
let msg = <&B256>::try_from(&input[0..32]).unwrap();
let recid = input[63] - 27;
let sig = <&B512>::try_from(&input[64..128]).unwrap();
let res = crypto().secp256k1_ecrecover(&sig.0, recid, &msg.0).ok();
let out = res.map(|o| o.to_vec().into()).unwrap_or_default();
Ok(PrecompileOutput::new(MONAD_ECRECOVER_GAS, out, reservoir))
}
fn monad_ec_add_run(input: &[u8], gas_limit: u64, reservoir: u64) -> PrecompileResult {
Ok(PrecompileOutput::from_eth_result(
bn254::run_add(input, MONAD_EC_ADD_GAS, gas_limit),
reservoir,
))
}
fn monad_ec_mul_run(input: &[u8], gas_limit: u64, reservoir: u64) -> PrecompileResult {
Ok(PrecompileOutput::from_eth_result(
bn254::run_mul(input, MONAD_EC_MUL_GAS, gas_limit),
reservoir,
))
}
fn monad_ec_pairing_run(input: &[u8], gas_limit: u64, reservoir: u64) -> PrecompileResult {
Ok(PrecompileOutput::from_eth_result(
bn254::run_pair(
input,
MONAD_EC_PAIRING_PER_POINT_GAS,
MONAD_EC_PAIRING_BASE_GAS,
gas_limit,
),
reservoir,
))
}
fn monad_blake2f_run(input: &[u8], gas_limit: u64, reservoir: u64) -> PrecompileResult {
use revm::precompile::crypto;
const INPUT_LENGTH: usize = 213;
if input.len() != INPUT_LENGTH {
return Ok(PrecompileOutput::halt(PrecompileHalt::Blake2WrongLength, reservoir));
}
let rounds = u32::from_be_bytes(input[..4].try_into().unwrap());
let gas_used = rounds as u64 * MONAD_BLAKE2F_ROUND_GAS;
if gas_used > gas_limit {
return Ok(PrecompileOutput::halt(PrecompileHalt::OutOfGas, reservoir));
}
let f = match input[212] {
0 => false,
1 => true,
_ => {
return Ok(PrecompileOutput::halt(
PrecompileHalt::Blake2WrongFinalIndicatorFlag,
reservoir,
));
}
};
let mut h = [0u64; 8];
input[4..68].chunks_exact(8).enumerate().for_each(|(i, chunk)| {
h[i] = u64::from_le_bytes(chunk.try_into().unwrap());
});
let mut m = [0u64; 16];
input[68..196].chunks_exact(8).enumerate().for_each(|(i, chunk)| {
m[i] = u64::from_le_bytes(chunk.try_into().unwrap());
});
let t_0 = u64::from_le_bytes(input[196..204].try_into().unwrap());
let t_1 = u64::from_le_bytes(input[204..212].try_into().unwrap());
crypto().blake2_compress(rounds, &mut h, &m, &[t_0, t_1], f);
let mut out = [0u8; 64];
for (i, h) in (0..64).step_by(8).zip(h.iter()) {
out[i..i + 8].copy_from_slice(&h.to_le_bytes());
}
Ok(PrecompileOutput::new(gas_used, out.into(), reservoir))
}
fn monad_point_evaluation_run(input: &[u8], gas_limit: u64, reservoir: u64) -> PrecompileResult {
use revm::precompile::crypto;
if gas_limit < MONAD_POINT_EVALUATION_GAS {
return Ok(PrecompileOutput::halt(PrecompileHalt::OutOfGas, reservoir));
}
if input.len() != 192 {
return Ok(PrecompileOutput::halt(PrecompileHalt::BlobInvalidInputLength, reservoir));
}
let versioned_hash = &input[..32];
let commitment = &input[96..144];
if kzg_point_evaluation::kzg_to_versioned_hash(commitment) != versioned_hash {
return Ok(PrecompileOutput::halt(PrecompileHalt::BlobMismatchedVersion, reservoir));
}
let commitment: &[u8; 48] = commitment.try_into().unwrap();
let z = input[32..64].try_into().unwrap();
let y = input[64..96].try_into().unwrap();
let proof = input[144..192].try_into().unwrap();
if let Err(halt) = crypto().verify_kzg_proof(z, y, commitment, proof) {
return Ok(PrecompileOutput::halt(halt, reservoir));
}
Ok(PrecompileOutput::new(
MONAD_POINT_EVALUATION_GAS,
kzg_point_evaluation::RETURN_VALUE.into(),
reservoir,
))
}
pub const MONAD_ECRECOVER: Precompile =
Precompile::new(PrecompileId::EcRec, revm::precompile::u64_to_address(1), monad_ecrecover_run);
pub const MONAD_EC_ADD: Precompile =
Precompile::new(PrecompileId::Bn254Add, revm::precompile::u64_to_address(6), monad_ec_add_run);
pub const MONAD_EC_MUL: Precompile =
Precompile::new(PrecompileId::Bn254Mul, revm::precompile::u64_to_address(7), monad_ec_mul_run);
pub const MONAD_EC_PAIRING: Precompile = Precompile::new(
PrecompileId::Bn254Pairing,
revm::precompile::u64_to_address(8),
monad_ec_pairing_run,
);
pub const MONAD_BLAKE2F: Precompile =
Precompile::new(PrecompileId::Blake2F, revm::precompile::u64_to_address(9), monad_blake2f_run);
pub const MONAD_POINT_EVALUATION: Precompile = Precompile::new(
PrecompileId::KzgPointEvaluation,
revm::precompile::u64_to_address(0x0A),
monad_point_evaluation_run,
);
#[derive(Debug, Clone)]
pub struct MonadPrecompiles {
inner: EthPrecompiles,
spec: MonadHardfork,
}
impl MonadPrecompiles {
#[inline]
pub fn new_with_spec(spec: MonadHardfork) -> Self {
let mut precompiles = Precompiles::new(spec.into_eth_spec().into()).clone();
precompiles.extend([
MONAD_ECRECOVER,
MONAD_EC_ADD,
MONAD_EC_MUL,
MONAD_EC_PAIRING,
MONAD_BLAKE2F,
MONAD_POINT_EVALUATION,
]);
precompiles.extend([secp256r1::P256VERIFY_OSAKA]);
Self {
inner: EthPrecompiles {
precompiles: Box::leak(Box::new(precompiles)),
spec: SpecId::default(),
},
spec,
}
}
#[inline]
pub const fn precompiles(&self) -> &'static Precompiles {
self.inner.precompiles
}
}
impl<CTX> PrecompileProvider<CTX> for MonadPrecompiles
where
CTX: MonadContextTr,
{
type Output = InterpreterResult;
#[inline]
fn set_spec(&mut self, spec: <CTX::Cfg as Cfg>::Spec) -> bool {
if spec == self.spec {
return false;
}
*self = Self::new_with_spec(spec);
true
}
#[inline]
fn run(
&mut self,
context: &mut CTX,
inputs: &CallInputs,
) -> Result<Option<Self::Output>, String> {
if let Some(result) = staking::run_staking_precompile(context, inputs)? {
return Ok(Some(result));
}
if let Some(result) = reserve_balance::run_reserve_balance_precompile(context, inputs)? {
return Ok(Some(result));
}
self.inner.run(context, inputs)
}
#[inline]
fn warm_addresses(&self) -> Box<impl Iterator<Item = Address>> {
let mut addresses = vec![staking::storage::STAKING_ADDRESS];
if MonadHardfork::MonadNine.is_enabled_in(self.spec) {
addresses.push(reserve_balance::abi::RESERVE_BALANCE_ADDRESS);
}
addresses.extend(self.inner.warm_addresses());
Box::new(addresses.into_iter())
}
#[inline]
fn contains(&self, address: &Address) -> bool {
*address == staking::storage::STAKING_ADDRESS
|| (MonadHardfork::MonadNine.is_enabled_in(self.spec)
&& *address == reserve_balance::abi::RESERVE_BALANCE_ADDRESS)
|| self.inner.contains(address)
}
}
impl Default for MonadPrecompiles {
fn default() -> Self {
Self::new_with_spec(MonadHardfork::default())
}
}
#[cfg(test)]
mod tests {
use super::*;
use revm::precompile::{self, PrecompileHalt, PrecompileStatus};
use revm::primitives::{hex, U256};
fn modexp_input(base: &[u8], exponent: &[u8], modulus: &[u8]) -> Vec<u8> {
let mut input = Vec::with_capacity(96 + base.len() + exponent.len() + modulus.len());
input.extend_from_slice(&U256::from(base.len()).to_be_bytes::<32>());
input.extend_from_slice(&U256::from(exponent.len()).to_be_bytes::<32>());
input.extend_from_slice(&U256::from(modulus.len()).to_be_bytes::<32>());
input.extend_from_slice(base);
input.extend_from_slice(exponent);
input.extend_from_slice(modulus);
input
}
fn modexp_precompile(spec: MonadHardfork) -> &'static Precompile {
MonadPrecompiles::new_with_spec(spec)
.precompiles()
.get(&precompile::u64_to_address(0x05))
.expect("modexp (0x05) should exist")
}
#[test]
fn test_monad_precompile_gas_costs() {
assert_eq!(MONAD_ECRECOVER_GAS, 6_000);
assert_eq!(MONAD_EC_ADD_GAS, 300);
assert_eq!(MONAD_EC_MUL_GAS, 30_000);
assert_eq!(MONAD_EC_PAIRING_BASE_GAS, 225_000);
assert_eq!(MONAD_EC_PAIRING_PER_POINT_GAS, 170_000);
assert_eq!(MONAD_BLAKE2F_ROUND_GAS, 2);
assert_eq!(MONAD_POINT_EVALUATION_GAS, 200_000);
}
#[test]
fn test_monad_precompiles_contains_addresses() {
let monad_precompiles = MonadPrecompiles::default();
let precompiles = monad_precompiles.precompiles();
assert!(
precompiles.contains(&revm::precompile::u64_to_address(0x01)),
"ecRecover (0x01) should exist"
);
assert!(
precompiles.contains(&revm::precompile::u64_to_address(0x02)),
"sha256 (0x02) should exist"
);
assert!(
precompiles.contains(&revm::precompile::u64_to_address(0x03)),
"ripemd160 (0x03) should exist"
);
assert!(
precompiles.contains(&revm::precompile::u64_to_address(0x04)),
"identity (0x04) should exist"
);
assert!(
precompiles.contains(&revm::precompile::u64_to_address(0x05)),
"modexp (0x05) should exist"
);
assert!(
precompiles.contains(&revm::precompile::u64_to_address(0x06)),
"ecAdd (0x06) should exist"
);
assert!(
precompiles.contains(&revm::precompile::u64_to_address(0x07)),
"ecMul (0x07) should exist"
);
assert!(
precompiles.contains(&revm::precompile::u64_to_address(0x08)),
"ecPairing (0x08) should exist"
);
assert!(
precompiles.contains(&revm::precompile::u64_to_address(0x09)),
"blake2f (0x09) should exist"
);
assert!(
precompiles.contains(&revm::precompile::u64_to_address(0x0a)),
"point_eval (0x0a) should exist"
);
assert!(
precompiles.contains(&revm::precompile::u64_to_address(0x0b)),
"bls12_g1_add (0x0b) should exist"
);
assert!(
precompiles.contains(&revm::precompile::u64_to_address(0x0c)),
"bls12_g1_msm (0x0c) should exist"
);
assert!(
precompiles.contains(&revm::precompile::u64_to_address(0x0d)),
"bls12_g2_add (0x0d) should exist"
);
assert!(
precompiles.contains(&revm::precompile::u64_to_address(0x0e)),
"bls12_g2_msm (0x0e) should exist"
);
assert!(
precompiles.contains(&revm::precompile::u64_to_address(0x0f)),
"bls12_pairing_check (0x0f) should exist"
);
assert!(
precompiles.contains(&revm::precompile::u64_to_address(0x10)),
"bls12_map_fp_to_g1 (0x10) should exist"
);
assert!(
precompiles.contains(&revm::precompile::u64_to_address(0x11)),
"bls12_map_fp2_to_g2 (0x11) should exist"
);
assert!(
precompiles.contains(&revm::precompile::u64_to_address(0x0100)),
"p256_verify (0x0100) should exist"
);
assert_eq!(
crate::staking::storage::STAKING_ADDRESS,
revm::precompile::u64_to_address(0x1000),
"staking address should be 0x1000"
);
}
#[test]
fn test_monad_ecadd_precompile_gas_cost() {
use revm::primitives::hex;
let monad_precompiles = MonadPrecompiles::default();
let precompiles = monad_precompiles.precompiles();
let ec_add_address = revm::precompile::u64_to_address(6);
let precompile = precompiles.get(&ec_add_address).expect("ecAdd should exist");
let input = hex::decode(
"0000000000000000000000000000000000000000000000000000000000000001\
0000000000000000000000000000000000000000000000000000000000000002\
0000000000000000000000000000000000000000000000000000000000000001\
0000000000000000000000000000000000000000000000000000000000000002",
)
.unwrap();
let result = precompile.execute(&input, 100_000, 0).expect("ecAdd should succeed");
assert_eq!(result.gas_used, MONAD_EC_ADD_GAS, "ecAdd should use Monad gas cost of 300");
}
#[test]
fn test_monad_ecmul_precompile_gas_cost() {
use revm::primitives::hex;
let monad_precompiles = MonadPrecompiles::default();
let precompiles = monad_precompiles.precompiles();
let ec_mul_address = revm::precompile::u64_to_address(7);
let precompile = precompiles.get(&ec_mul_address).expect("ecMul should exist");
let input = hex::decode(
"0000000000000000000000000000000000000000000000000000000000000001\
0000000000000000000000000000000000000000000000000000000000000002\
0000000000000000000000000000000000000000000000000000000000000002",
)
.unwrap();
let result = precompile.execute(&input, 100_000, 0).expect("ecMul should succeed");
assert_eq!(result.gas_used, MONAD_EC_MUL_GAS, "ecMul should use Monad gas cost of 30000");
}
#[test]
fn test_monad_ecrecover_precompile_gas_cost() {
use revm::primitives::hex;
let monad_precompiles = MonadPrecompiles::default();
let precompiles = monad_precompiles.precompiles();
let ecrecover_address = revm::precompile::u64_to_address(1);
let precompile = precompiles.get(&ecrecover_address).expect("ecRecover should exist");
let input = hex::decode(
"456e9aea5e197a1f1af7a3e85a3212fa4049a3ba34c2289b4c860fc0b0c64ef3\
000000000000000000000000000000000000000000000000000000000000001c\
9242685bf161793cc25603c231bc2f568eb630ea16aa137d2664ac8038825608\
4f8ae3bd7535248d0bd448298cc2e2071e56992d0774dc340c368ae950852ada",
)
.unwrap();
let result = precompile.execute(&input, 100_000, 0).expect("ecRecover should succeed");
assert_eq!(
result.gas_used, MONAD_ECRECOVER_GAS,
"ecRecover should use Monad gas cost of 6000"
);
}
#[test]
fn test_monad_ecpairing_precompile_gas_cost() {
use revm::primitives::hex;
let monad_precompiles = MonadPrecompiles::default();
let precompiles = monad_precompiles.precompiles();
let ec_pairing_address = revm::precompile::u64_to_address(8);
let precompile = precompiles.get(&ec_pairing_address).expect("ecPairing should exist");
let input = hex::decode(
"0000000000000000000000000000000000000000000000000000000000000000\
0000000000000000000000000000000000000000000000000000000000000000\
198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2\
1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed\
090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b\
12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa",
)
.unwrap();
let result = precompile.execute(&input, 500_000, 0).expect("ecPairing should succeed");
let expected_gas = MONAD_EC_PAIRING_BASE_GAS + MONAD_EC_PAIRING_PER_POINT_GAS;
assert_eq!(result.gas_used, expected_gas, "ecPairing should use Monad gas cost of 395000");
}
#[test]
fn test_monad_blake2f_precompile_gas_cost() {
use revm::primitives::hex;
let monad_precompiles = MonadPrecompiles::default();
let precompiles = monad_precompiles.precompiles();
let blake2f_address = revm::precompile::u64_to_address(9);
let precompile = precompiles.get(&blake2f_address).expect("blake2f should exist");
let input = hex::decode(
"0000000c\
48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5\
d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b\
6162630000000000000000000000000000000000000000000000000000000000\
0000000000000000000000000000000000000000000000000000000000000000\
0000000000000000000000000000000000000000000000000000000000000000\
0000000000000000000000000000000000000000000000000000000000000000\
0300000000000000\
0000000000000000\
01",
)
.unwrap();
let result = precompile.execute(&input, 100, 0).expect("blake2f should succeed");
let expected_gas = 12 * MONAD_BLAKE2F_ROUND_GAS;
assert_eq!(
result.gas_used, expected_gas,
"blake2f should use Monad gas cost of 24 for 12 rounds"
);
}
#[test]
fn test_p256verify_precompile_gas_cost() {
use revm::primitives::hex;
let monad_precompiles = MonadPrecompiles::default();
let precompiles = monad_precompiles.precompiles();
let p256verify_address = revm::precompile::u64_to_address(0x0100);
let precompile = precompiles.get(&p256verify_address).expect("P256VERIFY should exist");
let input = hex::decode(
"4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4d\
a73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac\
36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d60\
4aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff3\
7618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e",
)
.unwrap();
let result = precompile.execute(&input, 10_000, 0).expect("P256VERIFY should succeed");
assert_eq!(
result.gas_used,
revm::precompile::secp256r1::P256VERIFY_BASE_GAS_FEE_OSAKA,
"P256VERIFY should use Osaka gas cost of 6900"
);
}
#[test]
fn test_modexp_size_limit_is_enabled_on_monad_nine() {
let oversized_input = modexp_input(&vec![0u8; 1025], &[0x01], &[0x01]);
let monad_eight_result =
modexp_precompile(MonadHardfork::MonadEight).execute(&oversized_input, 100_000_000, 0);
assert!(
monad_eight_result.is_ok(),
"MonadEight should not apply the Osaka MODEXP size limit"
);
let monad_nine_result =
modexp_precompile(MonadHardfork::MonadNine).execute(&oversized_input, 100_000_000, 0);
assert!(
matches!(
monad_nine_result,
Ok(ref output)
if matches!(
output.status,
PrecompileStatus::Halt(PrecompileHalt::ModexpEip7823LimitSize)
)
),
"MonadNine should reject oversized MODEXP input, got {monad_nine_result:?}"
);
}
#[test]
fn test_modexp_gas_increases_on_monad_nine() {
let input = modexp_input(
&[0xff; 32],
&[0xff; 32],
&hex::decode("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd")
.expect("valid modulus hex"),
);
let monad_eight_result = modexp_precompile(MonadHardfork::MonadEight)
.execute(&input, 10_000_000, 0)
.expect("MonadEight MODEXP should succeed");
let monad_nine_result = modexp_precompile(MonadHardfork::MonadNine)
.execute(&input, 10_000_000, 0)
.expect("MonadNine MODEXP should succeed");
assert_eq!(
monad_eight_result.bytes, monad_nine_result.bytes,
"MODEXP output should stay the same across MonadEight and MonadNine"
);
assert!(
monad_nine_result.gas_used > monad_eight_result.gas_used,
"MonadNine MODEXP gas should increase: MonadEight={}, MonadNine={}",
monad_eight_result.gas_used,
monad_nine_result.gas_used
);
}
}