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