Skip to main content

mega_evme/common/
tx_override.rs

1//! Transaction override support for mega-evme replay command.
2//!
3//! This module provides the ability to override transaction fields when replaying
4//! transactions from RPC.
5
6use std::cell::RefCell;
7
8use alloy_primitives::{Address, Bytes, U256};
9use clap::Args;
10use mega_evm::{
11    alloy_evm::{IntoTxEnv, RecoveredTx},
12    MegaTransaction,
13};
14
15use super::{load_hex, parse_ether_value, Result};
16
17// Thread-local storage for input override (Bytes is not Copy, so we can't store it in TxOverrides)
18thread_local! {
19    static INPUT_OVERRIDE: RefCell<Option<Bytes>> = const { RefCell::new(None) };
20}
21
22/// Transaction override arguments for the replay command.
23#[derive(Args, Debug, Clone, Default)]
24#[command(next_help_heading = "Transaction Override Options")]
25pub struct TxOverrideArgs {
26    /// Override transaction gas limit
27    #[arg(long = "override.gas-limit", visible_aliases = ["override.gaslimit"], value_name = "GAS")]
28    pub gas_limit: Option<u64>,
29
30    /// Override transaction value.
31    /// VALUE can be: plain number (wei), or number with suffix (ether, gwei, wei).
32    /// Examples: `--override.value 1ether`, `--override.value 100gwei`
33    #[arg(long = "override.value", value_name = "VALUE")]
34    pub value: Option<String>,
35
36    /// Override transaction input data (hex string)
37    #[arg(long = "override.input", visible_aliases = ["override.data"], value_name = "HEX")]
38    pub input: Option<String>,
39
40    /// Override transaction input data from file (hex content)
41    #[arg(long = "override.input-file", visible_aliases = ["override.data-file"], value_name = "FILE")]
42    pub input_file: Option<String>,
43}
44
45impl TxOverrideArgs {
46    /// Returns true if any override is set.
47    pub fn has_overrides(&self) -> bool {
48        self.gas_limit.is_some() ||
49            self.value.is_some() ||
50            self.input.is_some() ||
51            self.input_file.is_some()
52    }
53
54    /// Wraps a transaction with overrides.
55    pub fn wrap<T: Copy>(&self, tx: T) -> Result<OverriddenTx<T>> {
56        // Parse and store input override in thread-local if present
57        let has_input_override =
58            if let Some(bytes) = load_hex(self.input.clone(), self.input_file.clone())? {
59                INPUT_OVERRIDE.with(|cell| cell.borrow_mut().replace(bytes));
60                true
61            } else {
62                INPUT_OVERRIDE.with(|cell| cell.borrow_mut().take());
63                false
64            };
65
66        Ok(OverriddenTx {
67            inner: tx,
68            overrides: TxOverrides {
69                gas_limit: self.gas_limit,
70                value: self.value.as_deref().map(parse_ether_value).transpose()?,
71                has_input_override,
72            },
73        })
74    }
75}
76
77/// Parsed transaction overrides.
78///
79/// All fields must be `Copy` because `OverriddenTx<T>` must implement `Copy`
80/// (required by block executor's `run_transaction`). The input override is stored
81/// in a thread-local (`INPUT_OVERRIDE`) since `Bytes` is not `Copy`.
82#[derive(Debug, Clone, Copy, Default)]
83pub struct TxOverrides {
84    /// Override for gas limit.
85    pub gas_limit: Option<u64>,
86    /// Override for value.
87    pub value: Option<U256>,
88    /// Whether input data should be overridden (actual data in thread-local).
89    pub has_input_override: bool,
90}
91
92impl TxOverrides {
93    /// Apply overrides to a [`MegaTransaction`].
94    pub fn apply(&self, tx: &mut MegaTransaction) {
95        if let Some(gas_limit) = self.gas_limit {
96            tx.base.gas_limit = gas_limit;
97        }
98        if let Some(value) = self.value {
99            tx.base.value = value;
100        }
101        if self.has_input_override {
102            if let Some(input) = INPUT_OVERRIDE.with(|cell| cell.borrow().clone()) {
103                tx.base.data = input;
104            }
105        }
106    }
107}
108
109/// A wrapper that applies overrides when converting to `TxEnv`.
110///
111/// This wrapper implements all the required traits by delegating to the inner
112/// transaction, but intercepts `IntoTxEnv` to apply overrides.
113#[derive(Debug, Clone, Copy)]
114pub struct OverriddenTx<T: Copy> {
115    inner: T,
116    overrides: TxOverrides,
117}
118
119impl<T: Copy> OverriddenTx<T> {
120    /// Get a reference to the inner transaction.
121    pub fn inner(&self) -> &T {
122        &self.inner
123    }
124}
125
126// Implement IntoTxEnv - this is where we apply the overrides
127impl<T: IntoTxEnv<MegaTransaction> + Copy> IntoTxEnv<MegaTransaction> for OverriddenTx<T> {
128    fn into_tx_env(self) -> MegaTransaction {
129        let mut tx = self.inner.into_tx_env();
130        self.overrides.apply(&mut tx);
131        tx
132    }
133}
134
135// Delegate RecoveredTx to inner
136impl<Tx, T: RecoveredTx<Tx> + Copy> RecoveredTx<Tx> for OverriddenTx<T> {
137    fn tx(&self) -> &Tx {
138        self.inner.tx()
139    }
140
141    fn signer(&self) -> &Address {
142        self.inner.signer()
143    }
144}