openzeppelin_monitor/models/blockchain/evm/
transaction.rsuse std::{collections::HashMap, ops::Deref};
use serde::{Deserialize, Serialize};
use alloy::{
consensus::Transaction as AlloyConsensusTransaction,
primitives::{Address, Bytes, B256, U256, U64},
rpc::types::{AccessList, Index, Transaction as AlloyTransaction},
};
#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)]
pub struct BaseL2Transaction {
#[serde(
rename = "depositReceiptVersion",
default,
skip_serializing_if = "Option::is_none"
)]
pub deposit_receipt_version: Option<U64>,
#[serde(
rename = "sourceHash",
default,
skip_serializing_if = "Option::is_none"
)]
pub source_hash: Option<B256>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub mint: Option<U256>,
#[serde(rename = "yParity", default, skip_serializing_if = "Option::is_none")]
pub y_parity: Option<U64>,
}
#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)]
pub struct BaseTransaction {
pub hash: B256,
pub nonce: U256,
#[serde(rename = "blockHash")]
pub block_hash: Option<B256>,
#[serde(rename = "blockNumber")]
pub block_number: Option<U64>,
#[serde(rename = "transactionIndex")]
pub transaction_index: Option<Index>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub from: Option<Address>,
pub to: Option<Address>,
pub value: U256,
#[serde(rename = "gasPrice")]
pub gas_price: Option<U256>,
pub gas: U256,
pub input: Bytes,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub v: Option<U64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub r: Option<U256>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub s: Option<U256>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub raw: Option<Bytes>,
#[serde(rename = "type", default, skip_serializing_if = "Option::is_none")]
pub transaction_type: Option<U64>,
#[serde(
rename = "accessList",
default,
skip_serializing_if = "Option::is_none"
)]
pub access_list: Option<AccessList>,
#[serde(rename = "maxFeePerGas", skip_serializing_if = "Option::is_none")]
pub max_fee_per_gas: Option<U256>,
#[serde(
rename = "maxPriorityFeePerGas",
skip_serializing_if = "Option::is_none"
)]
pub max_priority_fee_per_gas: Option<U256>,
#[serde(flatten)]
pub l2: BaseL2Transaction,
#[serde(flatten)]
pub extra: HashMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Transaction(pub BaseTransaction);
impl Transaction {
pub fn value(&self) -> &U256 {
&self.0.value
}
pub fn sender(&self) -> Option<&Address> {
self.0.from.as_ref()
}
pub fn to(&self) -> Option<&Address> {
self.0.to.as_ref()
}
pub fn gas(&self) -> &U256 {
&self.0.gas
}
pub fn gas_price(&self) -> Option<&U256> {
self.0.gas_price.as_ref()
}
pub fn nonce(&self) -> &U256 {
&self.0.nonce
}
pub fn hash(&self) -> &B256 {
&self.0.hash
}
}
impl From<BaseTransaction> for Transaction {
fn from(tx: BaseTransaction) -> Self {
Self(tx)
}
}
impl From<AlloyTransaction> for Transaction {
fn from(tx: AlloyTransaction) -> Self {
let tx = BaseTransaction {
hash: *tx.inner.tx_hash(),
nonce: U256::from(tx.inner.nonce()),
block_hash: tx.block_hash,
block_number: tx.block_number.map(U64::from),
transaction_index: tx.transaction_index.map(|i| Index::from(i as usize)),
from: Some(tx.inner.signer()),
to: tx.inner.to(),
value: tx.inner.value(),
gas_price: tx.inner.gas_price().map(U256::from),
gas: U256::from(tx.inner.gas_limit()),
input: tx.inner.input().clone(),
v: Some(U64::from(u64::from(tx.inner.signature().v()))),
r: Some(U256::from(tx.inner.signature().r())),
s: Some(U256::from(tx.inner.signature().s())),
raw: None,
transaction_type: Some(U64::from(tx.inner.tx_type() as u64)),
access_list: tx.inner.access_list().cloned(),
max_fee_per_gas: Some(U256::from(tx.inner.max_fee_per_gas())),
max_priority_fee_per_gas: Some(U256::from(
tx.inner.max_priority_fee_per_gas().unwrap_or(0),
)),
l2: BaseL2Transaction {
deposit_receipt_version: None,
source_hash: None,
mint: None,
y_parity: None,
},
extra: HashMap::new(),
};
Self(tx)
}
}
impl Deref for Transaction {
type Target = BaseTransaction;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloy::{
primitives::{Address, Bytes, B256, U256, U64},
rpc::types::Index,
};
fn create_test_transaction() -> BaseTransaction {
BaseTransaction {
hash: B256::with_last_byte(1),
nonce: U256::from(2),
block_hash: Some(B256::with_last_byte(3)),
block_number: Some(U64::from(4)),
transaction_index: Some(Index::from(0)),
from: Some(Address::with_last_byte(5)),
to: Some(Address::with_last_byte(6)),
value: U256::from(100),
gas_price: Some(U256::from(20)),
gas: U256::from(21000),
input: Bytes::default(),
v: None,
r: None,
s: None,
raw: None,
transaction_type: None,
access_list: None,
max_priority_fee_per_gas: None,
max_fee_per_gas: None,
l2: BaseL2Transaction {
deposit_receipt_version: None,
source_hash: None,
mint: None,
y_parity: None,
},
extra: HashMap::new(),
}
}
#[test]
fn test_value() {
let tx = Transaction(create_test_transaction());
assert_eq!(*tx.value(), U256::from(100));
}
#[test]
fn test_sender() {
let tx = Transaction(create_test_transaction());
assert_eq!(tx.sender(), Some(&Address::with_last_byte(5)));
}
#[test]
fn test_recipient() {
let tx = Transaction(create_test_transaction());
assert_eq!(tx.to(), Some(&Address::with_last_byte(6)));
}
#[test]
fn test_gas() {
let tx = Transaction(create_test_transaction());
assert_eq!(*tx.gas(), U256::from(21000));
}
#[test]
fn test_gas_price() {
let tx = Transaction(create_test_transaction());
assert_eq!(tx.gas_price(), Some(&U256::from(20)));
}
#[test]
fn test_nonce() {
let tx = Transaction(create_test_transaction());
assert_eq!(*tx.nonce(), U256::from(2));
}
#[test]
fn test_hash() {
let tx = Transaction(create_test_transaction());
assert_eq!(*tx.hash(), B256::with_last_byte(1));
}
#[test]
fn test_from_base_transaction() {
let base_tx = create_test_transaction();
let tx: Transaction = base_tx.clone().into();
assert_eq!(tx.0, base_tx);
}
#[test]
fn test_deref() {
let base_tx = create_test_transaction();
let tx = Transaction(base_tx.clone());
assert_eq!(*tx, base_tx);
}
}