Skip to main content

alloy_monad_evm/
lib.rs

1#![cfg_attr(not(test), warn(unused_crate_dependencies))]
2
3//! Alloy EVM implementation for Monad blockchain.
4//!
5//! This crate provides:
6//! - [`MonadEvm`]: Wrapper implementing [`alloy_evm::Evm`] trait
7//! - [`MonadEvmFactory`]: Factory implementing [`alloy_evm::EvmFactory`] trait
8//! - [`MonadContext`]: Type alias for Monad EVM context (re-exported from monad-revm)
9//! - [`extend_monad_precompiles`]: Function to extend `PrecompilesMap` with staking precompile
10
11use alloy_evm::{
12    precompiles::{DynPrecompile, Precompile, PrecompileInput, PrecompilesMap},
13    Database, Evm, EvmEnv, EvmFactory, EvmInternals,
14};
15use alloy_primitives::{Address, Bytes, U256};
16use monad_revm::{
17    instructions::MonadInstructions,
18    monad_context_with_db,
19    precompiles::MonadPrecompiles,
20    reserve_balance::{self, abi::RESERVE_BALANCE_ADDRESS},
21    staking::{self, write::StakingStorage, StorageReader, STAKING_ADDRESS},
22    MonadBuilder, MonadCfgEnv, MonadEvm as InnerMonadEvm, MonadSpecId,
23};
24use revm::{
25    context::{BlockEnv, TxEnv},
26    context_interface::result::{EVMError, HaltReason, ResultAndState},
27    context_interface::{ContextTr, JournalTr, LocalContextTr},
28    handler::PrecompileProvider,
29    inspector::NoOpInspector,
30    interpreter::{CallInput, CallInputs, Gas, InstructionResult, InterpreterResult},
31    precompile::{PrecompileError, PrecompileId, PrecompileOutput},
32    Context, ExecuteEvm, InspectEvm, Inspector, SystemCallEvm,
33};
34use std::ops::{Deref, DerefMut};
35
36// Re-export monad-revm types for external users
37pub use monad_revm::{handler::MonadHandler, MonadContext};
38
39/// Monad-aware precompile wrapper that works with `MonadJournal`.
40#[derive(Clone, Debug)]
41pub struct MonadPrecompilesMap {
42    inner: PrecompilesMap,
43    spec: MonadSpecId,
44}
45
46impl MonadPrecompilesMap {
47    /// Create a new Monad precompile map for the given spec.
48    pub fn new_with_spec(spec: MonadSpecId) -> Self {
49        let monad_precompiles = MonadPrecompiles::new_with_spec(spec);
50        let mut inner = PrecompilesMap::from_static(monad_precompiles.precompiles());
51        extend_monad_precompiles(&mut inner);
52        Self { inner, spec }
53    }
54
55    /// Returns the precompile addresses, including Monad-only precompiles.
56    pub fn addresses(&self) -> impl Iterator<Item = Address> + '_ {
57        let reserve_balance_enabled = MonadSpecId::MonadNine.is_enabled_in(self.spec);
58        std::iter::once(STAKING_ADDRESS)
59            .chain(reserve_balance_enabled.then_some(RESERVE_BALANCE_ADDRESS))
60            .chain(self.inner.addresses().copied().filter(move |address| {
61                *address != STAKING_ADDRESS
62                    && (!reserve_balance_enabled || *address != RESERVE_BALANCE_ADDRESS)
63            }))
64    }
65
66    /// Returns whether the address is a Monad precompile.
67    pub fn contains(&self, address: &Address) -> bool {
68        *address == STAKING_ADDRESS
69            || (MonadSpecId::MonadNine.is_enabled_in(self.spec)
70                && *address == RESERVE_BALANCE_ADDRESS)
71            || self.inner.get(address).is_some()
72    }
73
74    fn run_dynamic<DB: Database>(
75        &mut self,
76        context: &mut MonadContext<DB>,
77        inputs: &CallInputs,
78    ) -> Result<Option<InterpreterResult>, String> {
79        let Some(precompile) = self.inner.get(&inputs.bytecode_address) else {
80            return Ok(None);
81        };
82
83        let mut result = InterpreterResult {
84            result: InstructionResult::Return,
85            gas: Gas::new(inputs.gas_limit),
86            output: Bytes::new(),
87        };
88
89        let (block, tx, cfg, journaled_state, _, local) = context.all_mut();
90
91        let input_bytes = match &inputs.input {
92            CallInput::SharedBuffer(range) => {
93                if let Some(slice) = local.shared_memory_buffer_slice(range.clone()) {
94                    slice.to_vec()
95                } else {
96                    Vec::new()
97                }
98            }
99            CallInput::Bytes(bytes) => bytes.to_vec(),
100        };
101
102        let precompile_result = precompile.call(PrecompileInput {
103            data: &input_bytes,
104            gas: inputs.gas_limit,
105            caller: inputs.caller,
106            value: inputs.call_value(),
107            is_static: inputs.is_static,
108            internals: EvmInternals::new(journaled_state, block, cfg, tx),
109            target_address: inputs.target_address,
110            bytecode_address: inputs.bytecode_address,
111        });
112
113        match precompile_result {
114            Ok(output) => {
115                let underflow = result.gas.record_cost(output.gas_used);
116                assert!(underflow, "Gas underflow is not possible");
117                result.result = if output.reverted {
118                    InstructionResult::Revert
119                } else {
120                    InstructionResult::Return
121                };
122                result.output = output.bytes;
123            }
124            Err(PrecompileError::Fatal(error)) => return Err(error),
125            Err(error) => {
126                result.result = if error.is_oog() {
127                    InstructionResult::PrecompileOOG
128                } else {
129                    InstructionResult::PrecompileError
130                };
131                if !error.is_oog() && context.journal().depth() == 1 {
132                    context
133                        .local_mut()
134                        .set_precompile_error_context(error.to_string());
135                }
136            }
137        }
138
139        Ok(Some(result))
140    }
141}
142
143impl Deref for MonadPrecompilesMap {
144    type Target = PrecompilesMap;
145
146    fn deref(&self) -> &Self::Target {
147        &self.inner
148    }
149}
150
151impl DerefMut for MonadPrecompilesMap {
152    fn deref_mut(&mut self) -> &mut Self::Target {
153        &mut self.inner
154    }
155}
156
157impl<DB: Database> PrecompileProvider<MonadContext<DB>> for MonadPrecompilesMap {
158    type Output = InterpreterResult;
159
160    fn set_spec(&mut self, spec: MonadSpecId) -> bool {
161        if spec == self.spec {
162            return false;
163        }
164        *self = Self::new_with_spec(spec);
165        true
166    }
167
168    fn run(
169        &mut self,
170        context: &mut MonadContext<DB>,
171        inputs: &CallInputs,
172    ) -> Result<Option<Self::Output>, String> {
173        if let Some(result) = staking::run_staking_precompile(context, inputs)? {
174            return Ok(Some(result));
175        }
176
177        if let Some(result) = reserve_balance::run_reserve_balance_precompile(context, inputs)? {
178            return Ok(Some(result));
179        }
180
181        self.run_dynamic(context, inputs)
182    }
183
184    fn warm_addresses(&self) -> Box<impl Iterator<Item = Address>> {
185        Box::new(self.addresses())
186    }
187
188    fn contains(&self, address: &Address) -> bool {
189        Self::contains(self, address)
190    }
191}
192
193/// Monad EVM implementation.
194///
195/// This is a wrapper type around the `monad_revm::MonadEvm` with optional [`Inspector`] (tracing)
196/// support. [`Inspector`] support is configurable at runtime because it's part of the underlying
197/// [`InnerMonadEvm`](monad_revm::MonadEvm) type.
198#[allow(missing_debug_implementations)] // MonadEvm doesn't impl Debug
199pub struct MonadEvm<DB: Database, I, P = MonadPrecompilesMap> {
200    inner: InnerMonadEvm<MonadContext<DB>, I, MonadInstructions<MonadContext<DB>>, P>,
201    inspect: bool,
202}
203
204impl<DB: Database, I, P> MonadEvm<DB, I, P> {
205    /// Provides a reference to the EVM context.
206    pub const fn ctx(&self) -> &MonadContext<DB> {
207        &self.inner.0.ctx
208    }
209
210    /// Provides a mutable reference to the EVM context.
211    pub const fn ctx_mut(&mut self) -> &mut MonadContext<DB> {
212        &mut self.inner.0.ctx
213    }
214}
215
216impl<DB: Database, I, P> MonadEvm<DB, I, P> {
217    /// Creates a new Monad EVM instance.
218    ///
219    /// The `inspect` argument determines whether the configured [`Inspector`] of the given
220    /// [`InnerMonadEvm`](monad_revm::MonadEvm) should be invoked on [`Evm::transact`].
221    pub const fn new(
222        evm: InnerMonadEvm<MonadContext<DB>, I, MonadInstructions<MonadContext<DB>>, P>,
223        inspect: bool,
224    ) -> Self {
225        Self {
226            inner: evm,
227            inspect,
228        }
229    }
230}
231
232impl<DB: Database, I, P> Deref for MonadEvm<DB, I, P> {
233    type Target = MonadContext<DB>;
234
235    #[inline]
236    fn deref(&self) -> &Self::Target {
237        self.ctx()
238    }
239}
240
241impl<DB: Database, I, P> DerefMut for MonadEvm<DB, I, P> {
242    #[inline]
243    fn deref_mut(&mut self) -> &mut Self::Target {
244        self.ctx_mut()
245    }
246}
247
248impl<DB, I, P> Evm for MonadEvm<DB, I, P>
249where
250    DB: Database,
251    I: Inspector<MonadContext<DB>>,
252    P: PrecompileProvider<MonadContext<DB>, Output = InterpreterResult>,
253{
254    type DB = DB;
255    type Tx = TxEnv;
256    type Error = EVMError<DB::Error>;
257    type HaltReason = HaltReason;
258    type Spec = MonadSpecId;
259    type BlockEnv = BlockEnv;
260    type Precompiles = P;
261    type Inspector = I;
262
263    fn block(&self) -> &BlockEnv {
264        &self.block
265    }
266
267    fn chain_id(&self) -> u64 {
268        self.cfg.chain_id
269    }
270
271    fn transact_raw(
272        &mut self,
273        tx: Self::Tx,
274    ) -> Result<ResultAndState<Self::HaltReason>, Self::Error> {
275        if self.inspect {
276            self.inner.inspect_tx(tx)
277        } else {
278            self.inner.transact(tx)
279        }
280    }
281
282    fn transact_system_call(
283        &mut self,
284        caller: Address,
285        contract: Address,
286        data: Bytes,
287    ) -> Result<ResultAndState<Self::HaltReason>, Self::Error> {
288        self.inner.system_call_with_caller(caller, contract, data)
289    }
290
291    fn finish(self) -> (Self::DB, EvmEnv<Self::Spec>) {
292        let Context {
293            block: block_env,
294            cfg: monad_cfg,
295            journaled_state,
296            ..
297        } = self.inner.0.ctx;
298        // Convert MonadCfgEnv back to CfgEnv<MonadSpecId> for EvmEnv
299        let cfg_env = monad_cfg.into_inner();
300
301        (
302            journaled_state.into_database(),
303            EvmEnv { block_env, cfg_env },
304        )
305    }
306
307    fn set_inspector_enabled(&mut self, enabled: bool) {
308        self.inspect = enabled;
309    }
310
311    fn components(&self) -> (&Self::DB, &Self::Inspector, &Self::Precompiles) {
312        (
313            &self.inner.0.ctx.journaled_state.database,
314            &self.inner.0.inspector,
315            &self.inner.0.precompiles,
316        )
317    }
318
319    fn components_mut(&mut self) -> (&mut Self::DB, &mut Self::Inspector, &mut Self::Precompiles) {
320        (
321            &mut self.inner.0.ctx.journaled_state.database,
322            &mut self.inner.0.inspector,
323            &mut self.inner.0.precompiles,
324        )
325    }
326}
327
328/// Factory for creating [`MonadEvm`] instances.
329///
330/// Implements [`alloy_evm::EvmFactory`] for integration with Foundry.
331#[derive(Debug, Default, Clone, Copy)]
332#[non_exhaustive]
333pub struct MonadEvmFactory;
334
335impl EvmFactory for MonadEvmFactory {
336    type Evm<DB: Database, I: Inspector<MonadContext<DB>>> = MonadEvm<DB, I, Self::Precompiles>;
337    type Context<DB: Database> = MonadContext<DB>;
338    type Tx = TxEnv;
339    type Error<DBError: core::error::Error + Send + Sync + 'static> = EVMError<DBError>;
340    type HaltReason = HaltReason;
341    type Spec = MonadSpecId;
342    type BlockEnv = BlockEnv;
343    type Precompiles = MonadPrecompilesMap;
344
345    fn create_evm<DB: Database>(
346        &self,
347        db: DB,
348        input: EvmEnv<MonadSpecId>,
349    ) -> Self::Evm<DB, NoOpInspector> {
350        let spec_id = input.cfg_env.spec;
351        // Convert CfgEnv<MonadSpecId> to MonadCfgEnv for Monad-specific defaults (128KB code size)
352        let monad_cfg = MonadCfgEnv::from(input.cfg_env);
353
354        MonadEvm {
355            inner: monad_context_with_db(db)
356                .with_block(input.block_env)
357                .with_cfg(monad_cfg)
358                .build_monad_with_inspector(NoOpInspector {})
359                .with_precompiles(MonadPrecompilesMap::new_with_spec(spec_id)),
360            inspect: false,
361        }
362    }
363
364    fn create_evm_with_inspector<DB: Database, I: Inspector<Self::Context<DB>>>(
365        &self,
366        db: DB,
367        input: EvmEnv<MonadSpecId>,
368        inspector: I,
369    ) -> Self::Evm<DB, I> {
370        let spec_id = input.cfg_env.spec;
371        // Convert CfgEnv<MonadSpecId> to MonadCfgEnv for Monad-specific defaults (128KB code size)
372        let monad_cfg = MonadCfgEnv::from(input.cfg_env);
373
374        MonadEvm {
375            inner: monad_context_with_db(db)
376                .with_block(input.block_env)
377                .with_cfg(monad_cfg)
378                .build_monad_with_inspector(inspector)
379                .with_precompiles(MonadPrecompilesMap::new_with_spec(spec_id)),
380            inspect: true,
381        }
382    }
383}
384
385// ═══════════════════════════════════════════════════════════════════════════════
386// PrecompilesMap Integration
387// ═══════════════════════════════════════════════════════════════════════════════
388
389/// Extend a `PrecompilesMap` with Monad-specific precompiles.
390///
391/// This function adds the staking precompile (at address 0x1000) to the given
392/// `PrecompilesMap` via `apply_precompile`, which explicitly registers the address
393/// in the precompile address set. This ensures:
394/// - 0x1000 appears in `addresses()` / `precompile_addresses()`
395/// - Foundry's warm address set includes 0x1000
396/// - Foundry's `RevertDiagnostic` inspector skips 0x1000 (no misleading
397///   "call to non-contract address" on precompile reverts)
398///
399/// # Example
400///
401/// ```ignore
402/// use alloy_evm::precompiles::PrecompilesMap;
403/// use alloy_monad_evm::extend_monad_precompiles;
404///
405/// let mut precompiles = PrecompilesMap::default();
406/// extend_monad_precompiles(&mut precompiles);
407/// ```
408pub fn extend_monad_precompiles(precompiles: &mut PrecompilesMap) {
409    precompiles.apply_precompile(&STAKING_ADDRESS, |_| {
410        Some(DynPrecompile::new_stateful(
411            PrecompileId::Custom("MonadStaking".into()),
412            |input: PrecompileInput<'_>| -> Result<PrecompileOutput, PrecompileError> {
413                // Reject DELEGATECALL/CALLCODE (target_address != bytecode_address)
414                if !input.is_direct_call() {
415                    return Ok(PrecompileOutput::new_reverted(0, Bytes::new()));
416                }
417
418                // Reject STATICCALL and calls inside a static frame
419                if input.is_static {
420                    return Ok(PrecompileOutput::new_reverted(0, Bytes::new()));
421                }
422
423                // Decode selector — short input routes to fallback via write path
424                let selector: [u8; 4] = match input.data.get(..4).and_then(|s| s.try_into().ok()) {
425                    Some(s) => s,
426                    None => {
427                        // Route short input through write path for proper fallback handling
428                        let mut storage = PrecompileInputStakingStorage {
429                            internals: input.internals,
430                        };
431                        let result = staking::write::run_staking_write(
432                            input.data,
433                            input.gas,
434                            &mut storage,
435                            &input.caller,
436                            input.value,
437                        )
438                        .map_err(|e| PrecompileError::Other(e.into()))?;
439                        return interpreter_result_to_output(input.gas, result);
440                    }
441                };
442
443                // Route write selectors through the write module (payability checked per-method inside)
444                if staking::write::is_write_selector(selector) {
445                    let mut storage = PrecompileInputStakingStorage {
446                        internals: input.internals,
447                    };
448                    let caller = input.caller;
449                    let call_value = input.value;
450                    match staking::write::run_staking_write(
451                        input.data,
452                        input.gas,
453                        &mut storage,
454                        &caller,
455                        call_value,
456                    ) {
457                        Ok(result) => interpreter_result_to_output(input.gas, result),
458                        Err(e) => Err(PrecompileError::Other(e.into())),
459                    }
460                } else {
461                    // Read operations (payability checked per-method inside)
462                    let mut reader = PrecompileInputStakingStorage {
463                        internals: input.internals,
464                    };
465                    match staking::run_staking_with_reader(
466                        input.data,
467                        input.gas,
468                        &mut reader,
469                        input.value,
470                    ) {
471                        Ok(result) => interpreter_result_to_output(input.gas, result),
472                        Err(e) => Err(PrecompileError::Other(e.into())),
473                    }
474                }
475            },
476        ))
477    });
478}
479
480/// Convert an `InterpreterResult` to a `PrecompileOutput`.
481fn interpreter_result_to_output(
482    gas_limit: u64,
483    result: InterpreterResult,
484) -> Result<PrecompileOutput, PrecompileError> {
485    let gas_used = gas_limit.saturating_sub(result.gas.remaining());
486    if result.result == InstructionResult::Return {
487        Ok(PrecompileOutput::new(gas_used, result.output))
488    } else if result.result == InstructionResult::PrecompileOOG {
489        Err(PrecompileError::OutOfGas)
490    } else {
491        // Revert
492        Ok(PrecompileOutput::new_reverted(gas_used, result.output))
493    }
494}
495
496/// Storage implementation that uses `PrecompileInput.internals` for both reads and writes.
497struct PrecompileInputStakingStorage<'a> {
498    internals: alloy_evm::EvmInternals<'a>,
499}
500
501impl StorageReader for PrecompileInputStakingStorage<'_> {
502    fn sload(&mut self, key: U256) -> Result<U256, PrecompileError> {
503        self.internals
504            .sload(STAKING_ADDRESS, key)
505            .map(|r| r.data)
506            .map_err(|e| PrecompileError::Other(format!("Storage read failed: {e:?}").into()))
507    }
508}
509
510impl StakingStorage for PrecompileInputStakingStorage<'_> {
511    fn sstore(&mut self, key: U256, value: U256) -> Result<(), PrecompileError> {
512        self.internals
513            .sstore(STAKING_ADDRESS, key, value)
514            .map(|_| ())
515            .map_err(|e| PrecompileError::Other(format!("Storage write failed: {e:?}").into()))
516    }
517
518    fn transfer(
519        &mut self,
520        from: Address,
521        to: Address,
522        amount: U256,
523    ) -> Result<(), PrecompileError> {
524        if amount.is_zero() {
525            return Ok(());
526        }
527        match self.internals.transfer(from, to, amount) {
528            Ok(None) => Ok(()),
529            Ok(Some(e)) => Err(PrecompileError::Other(
530                format!("Transfer failed: {e:?}").into(),
531            )),
532            Err(e) => Err(PrecompileError::Other(
533                format!("Transfer error: {e:?}").into(),
534            )),
535        }
536    }
537
538    fn emit_log(&mut self, log: revm::primitives::Log) -> Result<(), PrecompileError> {
539        self.internals.log(log);
540        Ok(())
541    }
542}
543
544#[cfg(test)]
545mod tests {
546    use super::*;
547
548    #[test]
549    fn staking_precompile_is_available_on_all_monad_specs() {
550        for spec in [
551            MonadSpecId::MonadEight,
552            MonadSpecId::MonadNine,
553            MonadSpecId::MonadNext,
554        ] {
555            let precompiles = MonadPrecompilesMap::new_with_spec(spec);
556            let addresses = precompiles.addresses().collect::<Vec<_>>();
557
558            assert!(precompiles.contains(&STAKING_ADDRESS));
559            assert!(addresses.contains(&STAKING_ADDRESS));
560        }
561    }
562
563    #[test]
564    fn reserve_balance_precompile_is_gated_to_monad_nine_and_later() {
565        let monad_eight = MonadPrecompilesMap::new_with_spec(MonadSpecId::MonadEight);
566        let monad_nine = MonadPrecompilesMap::new_with_spec(MonadSpecId::MonadNine);
567        let monad_next = MonadPrecompilesMap::new_with_spec(MonadSpecId::MonadNext);
568
569        assert!(!monad_eight.contains(&RESERVE_BALANCE_ADDRESS));
570        assert!(!monad_eight
571            .addresses()
572            .any(|address| address == RESERVE_BALANCE_ADDRESS));
573
574        assert!(monad_nine.contains(&RESERVE_BALANCE_ADDRESS));
575        assert!(monad_nine
576            .addresses()
577            .any(|address| address == RESERVE_BALANCE_ADDRESS));
578
579        assert!(monad_next.contains(&RESERVE_BALANCE_ADDRESS));
580        assert!(monad_next
581            .addresses()
582            .any(|address| address == RESERVE_BALANCE_ADDRESS));
583    }
584
585    #[test]
586    fn set_spec_rebuilds_monad_only_precompile_set() {
587        let mut precompiles = MonadPrecompilesMap::new_with_spec(MonadSpecId::MonadEight);
588
589        assert!(!precompiles.contains(&RESERVE_BALANCE_ADDRESS));
590        assert!(
591            PrecompileProvider::<MonadContext<revm::database::EmptyDB>>::set_spec(
592                &mut precompiles,
593                MonadSpecId::MonadNine
594            )
595        );
596        assert!(precompiles.contains(&RESERVE_BALANCE_ADDRESS));
597        assert!(
598            !PrecompileProvider::<MonadContext<revm::database::EmptyDB>>::set_spec(
599                &mut precompiles,
600                MonadSpecId::MonadNine
601            )
602        );
603    }
604}