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