stratus/eth/primitives/
transaction_mined.rs1use 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#[derive(DebugAsJson, Clone, PartialEq, Eq, fake::Dummy, serde::Serialize, serde::Deserialize)]
26pub struct TransactionMined {
27 pub input: TransactionInput,
29
30 pub block_timestamp: UnixTime,
32
33 pub result: ExecutionResult,
35
36 pub output: Bytes,
38
39 pub gas: Gas,
41
42 pub deployed_contract_address: Option<Address>,
44
45 pub logs: Vec<LogMined>,
48
49 pub transaction_index: Index,
51
52 pub block_number: BlockNumber,
54
55 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 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
94impl 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(), 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}