openzeppelin_monitor/models/blockchain/evm/
transaction.rs1use alloy::{
4 consensus::Transaction as AlloyConsensusTransaction,
5 primitives::{Address, Bytes, B256, U256, U64},
6 rpc::types::{AccessList, Index, Transaction as AlloyTransaction},
7};
8use serde::{Deserialize, Serialize};
9use std::{collections::HashMap, ops::Deref};
10
11#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)]
13pub struct BaseL2Transaction {
14 #[serde(
16 rename = "depositReceiptVersion",
17 default,
18 skip_serializing_if = "Option::is_none"
19 )]
20 pub deposit_receipt_version: Option<U64>,
21
22 #[serde(
24 rename = "sourceHash",
25 default,
26 skip_serializing_if = "Option::is_none"
27 )]
28 pub source_hash: Option<B256>,
29
30 #[serde(default, skip_serializing_if = "Option::is_none")]
32 pub mint: Option<U256>,
33
34 #[serde(rename = "yParity", default, skip_serializing_if = "Option::is_none")]
36 pub y_parity: Option<U64>,
37}
38
39#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)]
42pub struct BaseTransaction {
43 pub hash: B256,
45 pub nonce: U256,
47 #[serde(rename = "blockHash")]
49 pub block_hash: Option<B256>,
50 #[serde(rename = "blockNumber")]
52 pub block_number: Option<U64>,
53 #[serde(rename = "transactionIndex")]
55 pub transaction_index: Option<Index>,
56 #[serde(default, skip_serializing_if = "Option::is_none")]
58 pub from: Option<Address>,
59 pub to: Option<Address>,
61 pub value: U256,
63 #[serde(rename = "gasPrice")]
65 pub gas_price: Option<U256>,
66 pub gas: U256,
68 pub input: Bytes,
70 #[serde(default, skip_serializing_if = "Option::is_none")]
72 pub v: Option<U64>,
73 #[serde(default, skip_serializing_if = "Option::is_none")]
75 pub r: Option<U256>,
76 #[serde(default, skip_serializing_if = "Option::is_none")]
78 pub s: Option<U256>,
79 #[serde(default, skip_serializing_if = "Option::is_none")]
81 pub raw: Option<Bytes>,
82 #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")]
84 pub transaction_type: Option<U64>,
85 #[serde(
87 rename = "accessList",
88 default,
89 skip_serializing_if = "Option::is_none"
90 )]
91 pub access_list: Option<AccessList>,
92 #[serde(rename = "maxFeePerGas", skip_serializing_if = "Option::is_none")]
94 pub max_fee_per_gas: Option<U256>,
95 #[serde(
97 rename = "maxPriorityFeePerGas",
98 skip_serializing_if = "Option::is_none"
99 )]
100 pub max_priority_fee_per_gas: Option<U256>,
101
102 #[serde(flatten)]
104 pub l2: BaseL2Transaction,
105
106 #[serde(flatten)]
108 pub extra: HashMap<String, serde_json::Value>,
109}
110
111#[derive(Debug, Clone, Serialize, Deserialize, Default)]
116pub struct Transaction(pub BaseTransaction);
117
118impl Transaction {
119 pub fn value(&self) -> &U256 {
121 &self.0.value
122 }
123
124 pub fn sender(&self) -> Option<&Address> {
126 self.0.from.as_ref()
127 }
128
129 pub fn to(&self) -> Option<&Address> {
131 self.0.to.as_ref()
132 }
133
134 pub fn gas(&self) -> &U256 {
136 &self.0.gas
137 }
138
139 pub fn gas_price(&self) -> Option<&U256> {
141 self.0.gas_price.as_ref()
142 }
143
144 pub fn nonce(&self) -> &U256 {
146 &self.0.nonce
147 }
148
149 pub fn hash(&self) -> &B256 {
151 &self.0.hash
152 }
153}
154
155impl From<BaseTransaction> for Transaction {
156 fn from(tx: BaseTransaction) -> Self {
157 Self(tx)
158 }
159}
160
161impl From<AlloyTransaction> for Transaction {
162 fn from(tx: AlloyTransaction) -> Self {
163 let tx = BaseTransaction {
164 hash: *tx.inner.tx_hash(),
165 nonce: U256::from(tx.inner.nonce()),
166 block_hash: tx.block_hash,
167 block_number: tx.block_number.map(U64::from),
168 transaction_index: tx.transaction_index.map(|i| Index::from(i as usize)),
169 from: Some(tx.inner.signer()),
170 to: tx.inner.to(),
171 value: tx.inner.value(),
172 gas_price: tx.inner.gas_price().map(U256::from),
173 gas: U256::from(tx.inner.gas_limit()),
174 input: tx.inner.input().clone(),
175 v: Some(U64::from(u64::from(tx.inner.signature().v()))),
176 r: Some(U256::from(tx.inner.signature().r())),
177 s: Some(U256::from(tx.inner.signature().s())),
178 raw: None,
179 transaction_type: Some(U64::from(tx.inner.tx_type() as u64)),
180 access_list: tx.inner.access_list().cloned(),
181 max_fee_per_gas: Some(U256::from(tx.inner.max_fee_per_gas())),
182 max_priority_fee_per_gas: Some(U256::from(
183 tx.inner.max_priority_fee_per_gas().unwrap_or(0),
184 )),
185 l2: BaseL2Transaction {
186 deposit_receipt_version: None,
187 source_hash: None,
188 mint: None,
189 y_parity: None,
190 },
191 extra: HashMap::new(),
192 };
193 Self(tx)
194 }
195}
196
197impl Deref for Transaction {
198 type Target = BaseTransaction;
199
200 fn deref(&self) -> &Self::Target {
201 &self.0
202 }
203}
204
205#[cfg(test)]
206mod tests {
207 use super::*;
208 use crate::utils::tests::builders::evm::transaction::TransactionBuilder;
209 use alloy::primitives::{Address, B256, U256};
210
211 #[test]
212 fn test_value() {
213 let value = U256::from(100);
214 let tx = TransactionBuilder::new().value(value).build();
215 assert_eq!(*tx.value(), value);
216 }
217
218 #[test]
219 fn test_sender() {
220 let address = Address::with_last_byte(5);
221 let tx = TransactionBuilder::new().from(address).build();
222 assert_eq!(tx.sender(), Some(&address));
223 }
224
225 #[test]
226 fn test_recipient() {
227 let address = Address::with_last_byte(6);
228 let tx = TransactionBuilder::new().to(address).build();
229 assert_eq!(tx.to(), Some(&address));
230 }
231
232 #[test]
233 fn test_gas() {
234 let default_tx = TransactionBuilder::new().build(); assert_eq!(*default_tx.gas(), U256::from(21000));
236
237 let gas = U256::from(45000);
239 let tx = TransactionBuilder::new().gas_limit(gas).build();
240 assert_eq!(*tx.gas(), gas);
241 }
242
243 #[test]
244 fn test_gas_price() {
245 let gas_price = U256::from(20);
246 let tx = TransactionBuilder::new().gas_price(gas_price).build();
247 assert_eq!(tx.gas_price(), Some(&gas_price));
248 }
249
250 #[test]
251 fn test_nonce() {
252 let nonce = U256::from(2);
253 let tx = TransactionBuilder::new().nonce(nonce).build();
254 assert_eq!(*tx.nonce(), nonce);
255 }
256
257 #[test]
258 fn test_hash() {
259 let hash = B256::with_last_byte(1);
260 let tx = TransactionBuilder::new().hash(hash).build();
261 assert_eq!(*tx.hash(), hash);
262 }
263
264 #[test]
265 fn test_from_base_transaction() {
266 let base_tx = TransactionBuilder::new().build().0;
267 let tx: Transaction = base_tx.clone().into();
268 assert_eq!(tx.0, base_tx);
269 }
270
271 #[test]
272 fn test_deref() {
273 let base_tx = TransactionBuilder::new().build().0;
274 let tx = Transaction(base_tx.clone());
275 assert_eq!(*tx, base_tx);
276 }
277}