Skip to main content

mega_evme/common/
env.rs

1//! Environment configuration for mega-evme
2
3use std::str::FromStr;
4
5use alloy_primitives::{Address, B256, U256};
6use clap::{Args, Parser};
7use std::convert::Infallible;
8
9use mega_evm::{
10    alloy_evm::Database,
11    revm::{
12        context::{block::BlockEnv, cfg::CfgEnv},
13        primitives::eip4844,
14    },
15    MegaContext, MegaSpecId, TestExternalEnvs,
16};
17
18use crate::hasher::AHashBucketHasher;
19
20/// External environment type for mega-evme using the real AHash-based SALT bucket hasher.
21pub type EvmeExternalEnvs = TestExternalEnvs<Infallible, AHashBucketHasher>;
22use tracing::{debug, trace};
23
24use super::{EvmeError, Result};
25
26/// Chain configuration arguments (spec and chain ID)
27#[derive(Args, Debug, Clone)]
28#[command(next_help_heading = "Chain Options")]
29pub struct ChainArgs {
30    /// Name of spec to use, possible values: `MiniRex`, `Equivalence`, `Rex`, `Rex1`, `Rex2`,
31    /// `Rex3`, `Rex4`, `Rex5`
32    #[arg(long = "spec", default_value = "Rex5")]
33    pub spec: String,
34
35    /// `ChainID` to use
36    #[arg(long = "chain-id", visible_aliases = ["chainid"], default_value = "6342")]
37    pub chain_id: u64,
38}
39
40impl ChainArgs {
41    /// Gets the spec ID from the spec name
42    pub fn spec_id(&self) -> Result<MegaSpecId> {
43        MegaSpecId::from_str(&self.spec)
44            .map_err(|e| EvmeError::InvalidInput(format!("Invalid spec name: {:?}", e)))
45    }
46
47    /// Creates [`CfgEnv`].
48    pub fn create_cfg_env(&self) -> Result<CfgEnv<MegaSpecId>> {
49        let mut cfg = CfgEnv::default();
50        cfg.chain_id = self.chain_id;
51        cfg.spec = self.spec_id()?;
52        debug!(cfg = ?cfg, "Evm CfgEnv created");
53        Ok(cfg)
54    }
55}
56
57/// Block environment configuration arguments
58#[derive(Args, Debug, Clone)]
59#[command(next_help_heading = "Block Options")]
60pub struct BlockEnvArgs {
61    /// Block number
62    #[arg(long = "block.number", default_value = "1")]
63    pub block_number: u64,
64
65    /// Block coinbase/beneficiary address
66    #[arg(long = "block.coinbase", visible_aliases = ["block.beneficiary"], default_value = "0x0000000000000000000000000000000000000000")]
67    pub block_coinbase: Address,
68
69    /// Block timestamp
70    #[arg(long = "block.timestamp", default_value = "1")]
71    pub block_timestamp: u64,
72
73    /// Block gas limit
74    #[arg(long = "block.gaslimit", visible_aliases = ["block.gas-limit", "block.gas"], default_value = "10000000000")]
75    pub block_gas_limit: u64,
76
77    /// Block base fee per gas (EIP-1559)
78    #[arg(long = "block.basefee", visible_aliases = ["block.base-fee"], default_value = "0")]
79    pub block_basefee: u64,
80
81    /// Block difficulty
82    #[arg(long = "block.difficulty", default_value = "0")]
83    pub block_difficulty: U256,
84
85    /// Block prevrandao (replaces difficulty post-merge). Required for post-merge blocks.
86    #[arg(
87        long = "block.prevrandao",
88        visible_aliases = ["block.random"],
89        default_value = "0x0000000000000000000000000000000000000000000000000000000000000000"
90    )]
91    pub block_prevrandao: B256,
92
93    /// Excess blob gas for EIP-4844. Required for Cancun and later forks.
94    #[arg(long = "block.blobexcessgas", visible_aliases = ["block.blob-excess-gas"], default_value = "0")]
95    pub block_blob_excess_gas: Option<u64>,
96}
97
98impl BlockEnvArgs {
99    /// Creates [`BlockEnv`].
100    pub fn create_block_env(&self) -> Result<BlockEnv> {
101        let mut block = BlockEnv {
102            number: U256::from(self.block_number),
103            beneficiary: self.block_coinbase,
104            timestamp: U256::from(self.block_timestamp),
105            gas_limit: self.block_gas_limit,
106            basefee: self.block_basefee,
107            difficulty: self.block_difficulty,
108            prevrandao: Some(self.block_prevrandao),
109            blob_excess_gas_and_price: None,
110        };
111
112        // Set blob excess gas if provided
113        if let Some(excess_gas) = self.block_blob_excess_gas {
114            block.set_blob_excess_gas_and_price(
115                excess_gas,
116                eip4844::BLOB_BASE_FEE_UPDATE_FRACTION_CANCUN,
117            );
118        }
119        debug!(block = ?block, "Evm BlockEnv created");
120
121        Ok(block)
122    }
123}
124
125/// External environment configuration arguments (SALT bucket capacity)
126#[derive(Args, Debug, Clone)]
127#[command(next_help_heading = "External Environment Options")]
128pub struct ExtEnvArgs {
129    /// Bucket capacity configuration in format "`bucket_id:capacity`"
130    /// Can be specified multiple times for different buckets.
131    /// Example: --bucket-capacity 123:1000000 --bucket-capacity 456:2000000
132    #[arg(long = "bucket-capacity", value_name = "BUCKET_ID:CAPACITY")]
133    pub bucket_capacity: Vec<String>,
134}
135
136impl ExtEnvArgs {
137    /// Creates [`EvmeExternalEnvs`].
138    pub fn create_external_envs(&self) -> Result<EvmeExternalEnvs> {
139        let mut external_envs = EvmeExternalEnvs::new();
140
141        // Parse and configure bucket capacities
142        for bucket_capacity_str in &self.bucket_capacity {
143            let (bucket_id, capacity) = parse_bucket_capacity(bucket_capacity_str)?;
144            external_envs = external_envs.with_bucket_capacity(bucket_id, capacity);
145        }
146        debug!(external_envs = ?external_envs, "Evm EvmeExternalEnvs created");
147
148        Ok(external_envs)
149    }
150}
151
152/// Environment configuration arguments (chain config, block env, SALT bucket capacity)
153#[derive(Parser, Debug, Clone)]
154pub struct EnvArgs {
155    /// Chain configuration
156    #[command(flatten)]
157    pub chain: ChainArgs,
158
159    /// Block environment configuration
160    #[command(flatten)]
161    pub block: BlockEnvArgs,
162
163    /// External environment configuration
164    #[command(flatten)]
165    pub ext: ExtEnvArgs,
166}
167
168impl EnvArgs {
169    /// Gets the spec ID from the spec name
170    pub fn spec_id(&self) -> Result<MegaSpecId> {
171        self.chain.spec_id()
172    }
173
174    /// Creates [`CfgEnv`].
175    pub fn create_cfg_env(&self) -> Result<CfgEnv<MegaSpecId>> {
176        self.chain.create_cfg_env()
177    }
178
179    /// Creates [`BlockEnv`].
180    pub fn create_block_env(&self) -> Result<BlockEnv> {
181        self.block.create_block_env()
182    }
183
184    /// Creates [`EvmeExternalEnvs`].
185    pub fn create_external_envs(&self) -> Result<EvmeExternalEnvs> {
186        self.ext.create_external_envs()
187    }
188
189    /// Creates a [`MegaContext`] with all environment configurations.
190    ///
191    /// The `system_address` defaults to `MEGA_SYSTEM_ADDRESS`. For `run`/`tx` modes this is
192    /// correct: these paths don't go through the block executor and don't resolve from
193    /// `SequencerRegistry`. If fork-state simulation with a changed sequencer is needed,
194    /// a `--system-address` CLI override can be added in the future.
195    pub fn create_evm_context<DB: Database>(
196        &self,
197        db: DB,
198    ) -> Result<MegaContext<DB, EvmeExternalEnvs>> {
199        let cfg = self.create_cfg_env()?;
200        let block = self.create_block_env()?;
201        let external_envs = self.create_external_envs()?;
202
203        Ok(MegaContext::new(db, cfg.spec)
204            .with_cfg(cfg)
205            .with_block(block)
206            .with_external_envs(external_envs.into()))
207    }
208}
209
210/// Parse bucket capacity string in format "`bucket_id:capacity`"
211/// Returns (`bucket_id`, capacity) tuple
212pub fn parse_bucket_capacity(s: &str) -> Result<(u32, u64)> {
213    let parts: Vec<&str> = s.split(':').collect();
214    if parts.len() != 2 {
215        return Err(EvmeError::InvalidInput(format!(
216            "Invalid bucket capacity format: '{}'. Expected format: 'bucket_id:capacity'",
217            s
218        )));
219    }
220
221    let bucket_id = parts[0]
222        .parse::<u32>()
223        .map_err(|e| EvmeError::InvalidInput(format!("Invalid bucket ID '{}': {}", parts[0], e)))?;
224
225    let capacity = parts[1]
226        .parse::<u64>()
227        .map_err(|e| EvmeError::InvalidInput(format!("Invalid capacity '{}': {}", parts[1], e)))?;
228
229    trace!(string = %s, bucket_id = %bucket_id, capacity = %capacity, "Parsed bucket capacity");
230    Ok((bucket_id, capacity))
231}