use std::time::Instant;
use clap::Parser;
use mega_evm::{
revm::{
context::result::ExecutionResult, context_interface::transaction::Transaction as _,
primitives::TxKind, DatabaseRef,
},
MegaTransaction, MegaTxType,
};
use tracing::{debug, info, trace, warn};
use crate::common::{
load_hex, op_receipt_to_tx_receipt, print_execution_summary, print_execution_trace,
print_receipt, DecodedRawTx, EvmeError, EvmeOutcome, ExecutionSummary,
};
use super::Result;
#[derive(Parser, Debug)]
pub struct Cmd {
#[arg(value_name = "RAW_TX")]
pub raw: Option<String>,
#[command(flatten)]
pub tx_args: crate::run::TxArgs,
#[command(flatten)]
pub prestate_args: crate::run::PreStateArgs,
#[command(flatten)]
pub rpc_args: crate::run::RpcArgs,
#[command(flatten)]
pub env_args: crate::run::EnvArgs,
#[command(flatten)]
pub dump_args: crate::run::StateDumpArgs,
#[command(flatten)]
pub trace_args: crate::run::TraceArgs,
#[command(flatten)]
pub output_args: crate::run::OutputArgs,
}
impl Cmd {
pub async fn run(&self) -> Result<()> {
let chain_id = self.env_args.chain.chain_id;
let spec = self.env_args.spec_id()?;
info!("Creating transaction");
let tx = if let Some(ref raw) = self.raw {
let raw_bytes = load_hex(Some(raw.clone()), None)?.unwrap_or_default();
let decoded = DecodedRawTx::from_raw(raw_bytes)?.override_tx_env(&self.tx_args)?;
if decoded.tx_env.chain_id != Some(chain_id) {
warn!(
chain_id,
decoded_chain_id = decoded.tx_env.chain_id,
"Raw transaction chain_id does not match the configured chain_id"
);
}
decoded.into_tx()
} else {
self.tx_args.create_tx(chain_id)?
};
debug!(
tx_type = tx.base.tx_type,
gas_limit = tx.base.gas_limit,
value = %tx.base.value,
"Transaction created"
);
let sender = tx.base.caller;
info!("Setting up initial state");
let (mut state, cache_store) =
self.prestate_args.create_initial_state(&sender, &self.rpc_args).await?;
debug!(sender = %sender, "State initialized");
state.deploy_system_contracts(spec);
debug!(spec = ?spec, "System contracts deployed");
let pre_execution_nonce = state.basic_ref(sender)?.map(|acc| acc.nonce).unwrap_or(0);
debug!(nonce = pre_execution_nonce, "Pre-execution nonce");
info!("Executing transaction");
let evm_context = self.env_args.create_evm_context(&mut state)?;
let start = Instant::now();
let (exec_result, evm_state, trace_data) =
self.trace_args.execute_transaction(evm_context, tx.clone())?;
let exec_time = start.elapsed();
match &exec_result {
ExecutionResult::Success { gas_used, .. } => {
info!(gas_used, "Execution succeeded");
}
ExecutionResult::Revert { gas_used, .. } => {
warn!(gas_used, "Execution reverted");
}
ExecutionResult::Halt { reason, gas_used } => {
warn!(?reason, gas_used, "Execution halted");
}
}
let outcome = EvmeOutcome {
pre_execution_nonce,
exec_result,
state: evm_state,
exec_time,
trace_data,
};
trace!("Writing output results");
self.output_results(&outcome, &tx)?;
cache_store.persist()?;
Ok(())
}
fn output_results(&self, outcome: &EvmeOutcome, tx: &MegaTransaction) -> Result<()> {
let tx_type = MegaTxType::try_from(tx.base.tx_type)
.map_err(|_| EvmeError::UnsupportedTxType(tx.base.tx_type))?;
let sender = tx.base.caller;
let is_create = tx.base.kind == TxKind::Create;
let receiver = match tx.base.kind {
TxKind::Call(addr) => Some(addr),
TxKind::Create => None,
};
let effective_gas_price = tx.effective_gas_price(self.env_args.block.block_basefee as u128);
let op_receipt = outcome.to_op_receipt(tx_type, outcome.pre_execution_nonce);
let contract_address = (is_create && op_receipt.is_success())
.then(|| sender.create(outcome.pre_execution_nonce));
let receipt = op_receipt_to_tx_receipt(
&op_receipt,
self.env_args.block.block_number,
self.env_args.block.block_timestamp,
sender,
receiver,
contract_address,
effective_gas_price,
outcome.exec_result.gas_used(),
None,
None,
0,
);
if self.output_args.json {
let mut summary = ExecutionSummary::from_result(&outcome.exec_result, contract_address);
summary.fill_trace_and_dump(outcome, &self.trace_args, &self.dump_args)?;
summary.receipt =
Some(serde_json::to_value(&receipt).expect("failed to serialize receipt"));
println!(
"{}",
serde_json::to_string_pretty(&summary).expect("failed to serialize output")
);
} else {
print_execution_summary(&outcome.exec_result, contract_address, outcome.exec_time);
print_receipt(&receipt);
print_execution_trace(
outcome.trace_data.as_deref(),
self.trace_args.trace_output_file.as_deref(),
)?;
if self.dump_args.dump {
self.dump_args.dump_evm_state(&outcome.state)?;
}
}
Ok(())
}
}