stratus/eth/primitives/
transaction_mined.rs

1use alloy_consensus::Eip658Value;
2use alloy_consensus::Receipt;
3use alloy_consensus::ReceiptEnvelope;
4use alloy_consensus::ReceiptWithBloom;
5use display_json::DebugAsJson;
6use itertools::Itertools;
7
8use crate::alias::AlloyReceipt;
9use crate::alias::AlloyTransaction;
10use crate::eth::primitives::Address;
11use crate::eth::primitives::BlockNumber;
12use crate::eth::primitives::Bytes;
13use crate::eth::primitives::ExecutionResult;
14use crate::eth::primitives::Gas;
15use crate::eth::primitives::Hash;
16use crate::eth::primitives::Index;
17use crate::eth::primitives::LogMined;
18use crate::eth::primitives::TransactionInput;
19use crate::eth::primitives::UnixTime;
20use crate::eth::primitives::logs_bloom::LogsBloom;
21use crate::ext::OptionExt;
22use crate::ext::RuintExt;
23
24/// Transaction that was executed by the EVM and added to a block.
25#[derive(DebugAsJson, Clone, PartialEq, Eq, fake::Dummy, serde::Serialize, serde::Deserialize)]
26pub struct TransactionMined {
27    /// Transaction input received through RPC.
28    pub input: TransactionInput,
29
30    /// Assumed block timestamp during the execution.
31    pub block_timestamp: UnixTime,
32
33    /// Status of the execution.
34    pub result: ExecutionResult,
35
36    /// Output returned by the function execution (can be the function output or an exception).
37    pub output: Bytes,
38
39    /// Consumed gas.
40    pub gas: Gas,
41
42    /// The contract address if the executed transaction deploys a contract.
43    pub deployed_contract_address: Option<Address>,
44
45    /// TODO: either remove logs from EvmExecution or remove them here
46    /// Logs added to the block.
47    pub logs: Vec<LogMined>,
48
49    /// Position of the transaction inside the block.
50    pub transaction_index: Index,
51
52    /// Block number where the transaction was mined.
53    pub block_number: BlockNumber,
54
55    /// Block hash where the transaction was mined.
56    pub block_hash: Hash,
57}
58
59impl PartialOrd for TransactionMined {
60    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
61        Some(self.cmp(other))
62    }
63}
64
65impl Ord for TransactionMined {
66    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
67        (self.block_number, self.transaction_index).cmp(&(other.block_number, other.transaction_index))
68    }
69}
70
71impl TransactionMined {
72    /// Check if the current transaction was completed normally.
73    pub fn is_success(&self) -> bool {
74        self.result.is_success()
75    }
76
77    fn compute_bloom(&self) -> LogsBloom {
78        let mut bloom = LogsBloom::default();
79        for log_mined in self.logs.iter() {
80            bloom.accrue_log(&(log_mined.log));
81        }
82        bloom
83    }
84
85    pub fn contract_address(&self) -> Option<Address> {
86        if let Some(contract_address) = &self.deployed_contract_address {
87            return Some(contract_address.to_owned());
88        }
89
90        None
91    }
92}
93
94// -----------------------------------------------------------------------------
95// Conversions: Self -> Other
96// -----------------------------------------------------------------------------
97
98impl From<TransactionMined> for AlloyTransaction {
99    fn from(value: TransactionMined) -> Self {
100        let gas_price = value.input.gas_price;
101        let tx = AlloyTransaction::from(value.input);
102
103        Self {
104            inner: tx.inner,
105            block_hash: Some(value.block_hash.into()),
106            block_number: Some(value.block_number.as_u64()),
107            transaction_index: Some(value.transaction_index.into()),
108            effective_gas_price: Some(gas_price),
109        }
110    }
111}
112
113impl From<TransactionMined> for AlloyReceipt {
114    fn from(value: TransactionMined) -> Self {
115        let receipt = Receipt {
116            status: Eip658Value::Eip658(value.is_success()),
117            cumulative_gas_used: value.gas.into(), // TODO: implement cumulative gas used correctly
118            logs: value.logs.clone().into_iter().map_into().collect(),
119        };
120
121        let receipt_with_bloom = ReceiptWithBloom {
122            receipt,
123            logs_bloom: value.compute_bloom().into(),
124        };
125
126        let inner = match value.input.tx_type.map(|tx| tx.as_u64()) {
127            Some(1) => ReceiptEnvelope::Eip2930(receipt_with_bloom),
128            Some(2) => ReceiptEnvelope::Eip1559(receipt_with_bloom),
129            Some(3) => ReceiptEnvelope::Eip4844(receipt_with_bloom),
130            Some(4) => ReceiptEnvelope::Eip7702(receipt_with_bloom),
131            _ => ReceiptEnvelope::Legacy(receipt_with_bloom),
132        };
133
134        Self {
135            inner,
136            transaction_hash: value.input.hash.into(),
137            transaction_index: Some(value.transaction_index.into()),
138            block_hash: Some(value.block_hash.into()),
139            block_number: Some(value.block_number.as_u64()),
140            gas_used: value.gas.into(),
141            effective_gas_price: value.input.gas_price,
142            blob_gas_used: None,
143            blob_gas_price: None,
144            from: value.input.signer.into(),
145            to: value.input.to.map_into(),
146            contract_address: value.contract_address().map_into(),
147        }
148    }
149}
150
151#[cfg(test)]
152mod tests {
153    use fake::Fake;
154    use fake::Faker;
155    use rand::Rng;
156
157    use super::*;
158
159    fn create_tx(transaction_index: u64, block_number: u64) -> TransactionMined {
160        TransactionMined {
161            logs: vec![],
162            transaction_index: transaction_index.into(),
163            block_number: block_number.into(),
164            block_hash: Hash::default(),
165            ..Faker.fake()
166        }
167    }
168
169    fn is_sorted<T: Ord>(vec: &[T]) -> bool {
170        vec.windows(2).all(|w| w[0] <= w[1])
171    }
172
173    #[test]
174    fn sort_transactions() {
175        let mut rng = rand::rng();
176        let v = (0..1000)
177            .map(|_| create_tx(rng.random_range(0..100), rng.random_range(0..100)))
178            .sorted()
179            .map(|tx| (tx.block_number.as_u64(), tx.transaction_index.0))
180            .collect_vec();
181        assert!(is_sorted(&v));
182    }
183}