stratus/eth/executor/
evm_input.rs

1use alloy_consensus::Transaction;
2use alloy_rpc_types_trace::geth::GethDebugTracingOptions;
3use display_json::DebugAsJson;
4
5use crate::eth::primitives::Address;
6use crate::eth::primitives::Block;
7use crate::eth::primitives::BlockNumber;
8use crate::eth::primitives::Bytes;
9use crate::eth::primitives::CallInput;
10use crate::eth::primitives::ChainId;
11use crate::eth::primitives::ExternalReceipt;
12use crate::eth::primitives::ExternalTransaction;
13use crate::eth::primitives::Gas;
14use crate::eth::primitives::Hash;
15use crate::eth::primitives::Nonce;
16use crate::eth::primitives::PendingBlockHeader;
17use crate::eth::primitives::PointInTime;
18use crate::eth::primitives::TransactionInput;
19use crate::eth::primitives::TransactionMined;
20use crate::eth::primitives::TransactionStage;
21use crate::eth::primitives::UnixTime;
22use crate::eth::primitives::Wei;
23use crate::eth::storage::ReadKind;
24use crate::eth::storage::TxCount;
25use crate::ext::OptionExt;
26use crate::ext::not;
27use crate::if_else;
28
29/// EVM input data. Usually derived from a transaction or call.
30#[derive(DebugAsJson, Clone, Default, serde::Serialize, serde::Deserialize, fake::Dummy, PartialEq)]
31pub struct EvmInput {
32    /// Operation party address.
33    ///
34    /// It can be:
35    /// * Transaction signer when executing an `eth_sendRawTransaction`.
36    /// * Placeholder when performing an `eth_call`
37    /// * Not specified when performing an `eth_call`
38    pub from: Address,
39
40    /// Operation counterparty address.
41    ///
42    /// It can be:
43    /// * Contract address when performing a function call.
44    /// * Destination account address when transfering funds.
45    /// * Not specified when deploying a contract.
46    pub to: Option<Address>,
47
48    /// Transfered amount from party to counterparty.
49    ///
50    /// Present only in native token transfers. When calling a contract function, the value is usually zero.
51    pub value: Wei,
52
53    /// Operation data.
54    ///
55    /// It can be:
56    /// * Function ID and parameters when performing a contract function call.
57    /// * Not specified when transfering funds.
58    /// * Contract bytecode when deploying a contract.
59    pub data: Bytes,
60
61    /// Operation party nonce.
62    ///
63    /// It can be:
64    /// * Required when executing an `eth_sendRawTransaction`.
65    /// * Not specified when performing an `eth_call`.
66    pub nonce: Option<Nonce>,
67
68    /// Max gas consumption allowed for the transaction.
69    pub gas_limit: Gas,
70
71    /// Gas price paid by each unit of gas consumed by the transaction.
72    pub gas_price: u128,
73
74    /// Number of the block where the transaction will be or was included.
75    pub block_number: BlockNumber,
76
77    /// Timestamp of the block where the transaction will be or was included.
78    pub block_timestamp: UnixTime,
79
80    /// Point-in-time from where accounts and slots will be read.
81    pub point_in_time: PointInTime,
82
83    /// ID of the blockchain where the transaction will be or was included.
84    ///
85    /// If not specified, it will not be validated.
86    pub chain_id: Option<ChainId>,
87
88    pub kind: ReadKind,
89}
90
91impl EvmInput {
92    /// Creates from a transaction that was sent directly to Stratus with `eth_sendRawTransaction`.
93    pub fn from_eth_transaction(input: &TransactionInput, pending_header: &PendingBlockHeader) -> Self {
94        Self {
95            from: input.signer,
96            to: input.to,
97            value: input.value,
98            data: input.input.clone(),
99            gas_limit: Gas::MAX,
100            gas_price: 0,
101            nonce: Some(input.nonce),
102            block_number: pending_header.number,
103            block_timestamp: *pending_header.timestamp,
104            point_in_time: PointInTime::Pending,
105            chain_id: input.chain_id,
106            kind: ReadKind::Transaction,
107        }
108    }
109
110    /// Creates from a call that was sent directly to Stratus with `eth_call` or `eth_estimateGas` for a pending block.
111    pub fn from_pending_block(input: CallInput, pending_header: PendingBlockHeader, tx_count: TxCount) -> Self {
112        Self {
113            from: input.from.unwrap_or(Address::ZERO),
114            to: input.to.map_into(),
115            value: input.value,
116            data: input.data,
117            gas_limit: Gas::MAX,
118            gas_price: 0,
119            nonce: None,
120            block_number: pending_header.number,
121            block_timestamp: *pending_header.timestamp,
122            point_in_time: PointInTime::Pending,
123            chain_id: None,
124            kind: ReadKind::Call((pending_header.number, tx_count)),
125        }
126    }
127
128    /// Creates from a call that was sent directly to Stratus with `eth_call` or `eth_estimateGas` for a mined block.
129    pub fn from_mined_block(input: CallInput, block: Block, point_in_time: PointInTime) -> Self {
130        Self {
131            from: input.from.unwrap_or(Address::ZERO),
132            to: input.to.map_into(),
133            value: input.value,
134            data: input.data,
135            gas_limit: Gas::MAX,
136            gas_price: 0,
137            nonce: None,
138            block_number: block.number(),
139            block_timestamp: block.header.timestamp,
140            point_in_time,
141            chain_id: None,
142            kind: ReadKind::Call((block.number(), TxCount::Full)),
143        }
144    }
145
146    /// Creates a transaction that was executed in an external blockchain and imported to Stratus.
147    ///
148    /// Successful external transactions executes with max gas and zero gas price to ensure we will have the same execution result.
149    pub fn from_external(tx: &ExternalTransaction, receipt: &ExternalReceipt, block_number: BlockNumber, block_timestamp: UnixTime) -> anyhow::Result<Self> {
150        Ok(Self {
151            from: tx.inner.signer().into(),
152            to: tx.inner.to().map_into(),
153            value: tx.inner.value().into(),
154            data: tx.inner.input().clone().into(),
155            nonce: Some(tx.inner.nonce().into()),
156            gas_limit: if_else!(receipt.is_success(), Gas::MAX, tx.inner.gas_limit().into()),
157            gas_price: if_else!(receipt.is_success(), 0, tx.inner.gas_price().map_into().unwrap_or(0)),
158            point_in_time: PointInTime::Pending,
159            block_number,
160            block_timestamp,
161            chain_id: tx.inner.chain_id().map(Into::into),
162            kind: ReadKind::Transaction,
163        })
164    }
165
166    /// Checks if the input is a contract call.
167    ///
168    /// It is when there is a `to` address and the `data` field is also populated.
169    pub fn is_contract_call(&self) -> bool {
170        self.to.is_some() && not(self.data.is_empty())
171    }
172}
173
174impl PartialEq<(&TransactionInput, &PendingBlockHeader)> for EvmInput {
175    fn eq(&self, other: &(&TransactionInput, &PendingBlockHeader)) -> bool {
176        self.block_number == other.1.number
177            && self.block_timestamp == *other.1.timestamp
178            && self.chain_id == other.0.chain_id
179            && self.data == other.0.input
180            && self.from == other.0.signer
181            && self.nonce.is_some_and(|inner| inner == other.0.nonce)
182            && self.value == other.0.value
183            && self.to == other.0.to
184    }
185}
186
187impl From<TransactionMined> for EvmInput {
188    fn from(value: TransactionMined) -> Self {
189        Self {
190            from: value.input.signer,
191            to: value.input.to,
192            value: value.input.value,
193            data: value.input.input,
194            nonce: Some(value.input.nonce),
195            gas_limit: value.input.gas_limit,
196            gas_price: value.input.gas_price,
197            block_number: value.block_number,
198            block_timestamp: value.block_timestamp,
199            point_in_time: PointInTime::MinedPast(value.block_number),
200            chain_id: value.input.chain_id,
201            kind: ReadKind::Transaction,
202        }
203    }
204}
205
206impl TryFrom<TransactionStage> for EvmInput {
207    type Error = anyhow::Error;
208
209    fn try_from(value: TransactionStage) -> Result<Self, Self::Error> {
210        match value {
211            TransactionStage::Executed(tx) => Ok(tx.evm_input),
212            TransactionStage::Mined(tx) => Ok(tx.into()),
213        }
214    }
215}
216
217pub struct InspectorInput {
218    pub tx_hash: Hash,
219    pub opts: GethDebugTracingOptions,
220    pub trace_unsuccessful_only: bool,
221}