Skip to main content

mega_evme/tx/
cmd.rs

1use std::time::Instant;
2
3use clap::Parser;
4use mega_evm::{
5    revm::{
6        context::result::ExecutionResult, context_interface::transaction::Transaction as _,
7        primitives::TxKind, DatabaseRef,
8    },
9    MegaTransaction, MegaTxType,
10};
11use tracing::{debug, info, trace, warn};
12
13use crate::common::{
14    load_hex, op_receipt_to_tx_receipt, print_execution_summary, print_execution_trace,
15    print_receipt, DecodedRawTx, EvmeError, EvmeOutcome, ExecutionSummary,
16};
17
18use super::Result;
19
20/// Run arbitrary transaction
21#[derive(Parser, Debug)]
22pub struct Cmd {
23    /// Raw EIP-2718 encoded transaction (hex). When provided, used as the base
24    /// transaction with CLI flags serving as overrides.
25    #[arg(value_name = "RAW_TX")]
26    pub raw: Option<String>,
27
28    // Shared argument groups
29    /// Transaction configuration
30    #[command(flatten)]
31    pub tx_args: crate::run::TxArgs,
32
33    /// Pre-execution state configuration
34    #[command(flatten)]
35    pub prestate_args: crate::run::PreStateArgs,
36
37    /// RPC configuration (used when --fork is enabled)
38    #[command(flatten)]
39    pub rpc_args: crate::run::RpcArgs,
40
41    /// Environment configuration
42    #[command(flatten)]
43    pub env_args: crate::run::EnvArgs,
44
45    /// State dump configuration
46    #[command(flatten)]
47    pub dump_args: crate::run::StateDumpArgs,
48
49    /// Trace configuration
50    #[command(flatten)]
51    pub trace_args: crate::run::TraceArgs,
52
53    /// Output format configuration
54    #[command(flatten)]
55    pub output_args: crate::run::OutputArgs,
56}
57
58impl Cmd {
59    /// Execute the tx command
60    pub async fn run(&self) -> Result<()> {
61        let chain_id = self.env_args.chain.chain_id;
62        let spec = self.env_args.spec_id()?;
63
64        // Step 1: Create transaction
65        info!("Creating transaction");
66        let tx = if let Some(ref raw) = self.raw {
67            let raw_bytes = load_hex(Some(raw.clone()), None)?.unwrap_or_default();
68            let decoded = DecodedRawTx::from_raw(raw_bytes)?.override_tx_env(&self.tx_args)?;
69            if decoded.tx_env.chain_id != Some(chain_id) {
70                warn!(
71                    chain_id,
72                    decoded_chain_id = decoded.tx_env.chain_id,
73                    "Raw transaction chain_id does not match the configured chain_id"
74                );
75            }
76            decoded.into_tx()
77        } else {
78            self.tx_args.create_tx(chain_id)?
79        };
80
81        debug!(
82            tx_type = tx.base.tx_type,
83            gas_limit = tx.base.gas_limit,
84            value = %tx.base.value,
85            "Transaction created"
86        );
87
88        // Step 2: Setup initial state and environment
89        let sender = tx.base.caller;
90        info!("Setting up initial state");
91        let (mut state, cache_store) =
92            self.prestate_args.create_initial_state(&sender, &self.rpc_args).await?;
93        debug!(sender = %sender, "State initialized");
94
95        state.deploy_system_contracts(spec);
96        debug!(spec = ?spec, "System contracts deployed");
97
98        let pre_execution_nonce = state.basic_ref(sender)?.map(|acc| acc.nonce).unwrap_or(0);
99        debug!(nonce = pre_execution_nonce, "Pre-execution nonce");
100
101        // Step 3: Execute transaction
102        info!("Executing transaction");
103        let evm_context = self.env_args.create_evm_context(&mut state)?;
104        let start = Instant::now();
105        let (exec_result, evm_state, trace_data) =
106            self.trace_args.execute_transaction(evm_context, tx.clone())?;
107        let exec_time = start.elapsed();
108
109        // Log execution result
110        match &exec_result {
111            ExecutionResult::Success { gas_used, .. } => {
112                info!(gas_used, "Execution succeeded");
113            }
114            ExecutionResult::Revert { gas_used, .. } => {
115                warn!(gas_used, "Execution reverted");
116            }
117            ExecutionResult::Halt { reason, gas_used } => {
118                warn!(?reason, gas_used, "Execution halted");
119            }
120        }
121
122        let outcome = EvmeOutcome {
123            pre_execution_nonce,
124            exec_result,
125            state: evm_state,
126            exec_time,
127            trace_data,
128        };
129
130        // Step 4: Output results (including state dump if requested)
131        trace!("Writing output results");
132        self.output_results(&outcome, &tx)?;
133
134        // Step 5: Persist the RPC cache (clean-exit only).
135        cache_store.persist()?;
136
137        Ok(())
138    }
139
140    /// Output execution results
141    fn output_results(&self, outcome: &EvmeOutcome, tx: &MegaTransaction) -> Result<()> {
142        let tx_type = MegaTxType::try_from(tx.base.tx_type)
143            .map_err(|_| EvmeError::UnsupportedTxType(tx.base.tx_type))?;
144        let sender = tx.base.caller;
145        let is_create = tx.base.kind == TxKind::Create;
146        let receiver = match tx.base.kind {
147            TxKind::Call(addr) => Some(addr),
148            TxKind::Create => None,
149        };
150        let effective_gas_price = tx.effective_gas_price(self.env_args.block.block_basefee as u128);
151
152        // Create transaction receipt
153        let op_receipt = outcome.to_op_receipt(tx_type, outcome.pre_execution_nonce);
154
155        // Determine contract address for CREATE transactions
156        let contract_address = (is_create && op_receipt.is_success())
157            .then(|| sender.create(outcome.pre_execution_nonce));
158
159        let receipt = op_receipt_to_tx_receipt(
160            &op_receipt,
161            self.env_args.block.block_number,
162            self.env_args.block.block_timestamp,
163            sender,
164            receiver,
165            contract_address,
166            effective_gas_price,
167            outcome.exec_result.gas_used(),
168            None,
169            None,
170            0,
171        );
172
173        if self.output_args.json {
174            let mut summary = ExecutionSummary::from_result(&outcome.exec_result, contract_address);
175            summary.fill_trace_and_dump(outcome, &self.trace_args, &self.dump_args)?;
176            summary.receipt =
177                Some(serde_json::to_value(&receipt).expect("failed to serialize receipt"));
178            println!(
179                "{}",
180                serde_json::to_string_pretty(&summary).expect("failed to serialize output")
181            );
182        } else {
183            // Human-readable summary
184            print_execution_summary(&outcome.exec_result, contract_address, outcome.exec_time);
185
186            print_receipt(&receipt);
187
188            print_execution_trace(
189                outcome.trace_data.as_deref(),
190                self.trace_args.trace_output_file.as_deref(),
191            )?;
192
193            if self.dump_args.dump {
194                self.dump_args.dump_evm_state(&outcome.state)?;
195            }
196        }
197
198        Ok(())
199    }
200}