use crate::TransactionType;
use context_interface::{
either::Either,
transaction::{
AccessList, AccessListItem, Authorization, RecoveredAuthority, RecoveredAuthorization,
SignedAuthorization, Transaction,
},
};
use core::fmt::Debug;
use database_interface::{BENCH_CALLER, BENCH_TARGET};
use primitives::{eip7825, Address, Bytes, TxKind, B256, U256};
use std::{vec, vec::Vec};
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TxEnv {
pub tx_type: u8,
pub caller: Address,
pub gas_limit: u64,
pub gas_price: u128,
pub kind: TxKind,
pub value: U256,
pub data: Bytes,
pub nonce: u64,
pub chain_id: Option<u64>,
pub access_list: AccessList,
pub gas_priority_fee: Option<u128>,
pub blob_hashes: Vec<B256>,
pub max_fee_per_blob_gas: u128,
pub authorization_list: Vec<Either<SignedAuthorization, RecoveredAuthorization>>,
}
impl Default for TxEnv {
fn default() -> Self {
Self::builder().build().unwrap()
}
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum DeriveTxTypeError {
MissingTargetForEip4844,
MissingTargetForEip7702,
MissingTargetForEip7873,
}
impl core::fmt::Display for DeriveTxTypeError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let s = match self {
Self::MissingTargetForEip4844 => "missing target for EIP-4844",
Self::MissingTargetForEip7702 => "missing target for EIP-7702",
Self::MissingTargetForEip7873 => "missing target for EIP-7873",
};
f.write_str(s)
}
}
impl core::error::Error for DeriveTxTypeError {}
impl TxEnv {
pub fn new_bench() -> Self {
Self {
caller: BENCH_CALLER,
kind: TxKind::Call(BENCH_TARGET),
gas_limit: 1_000_000_000,
..Default::default()
}
}
pub fn derive_tx_type(&mut self) -> Result<(), DeriveTxTypeError> {
if !self.access_list.0.is_empty() {
self.tx_type = TransactionType::Eip2930 as u8;
}
if self.gas_priority_fee.is_some() {
self.tx_type = TransactionType::Eip1559 as u8;
}
if !self.blob_hashes.is_empty() || self.max_fee_per_blob_gas > 0 {
if let TxKind::Call(_) = self.kind {
self.tx_type = TransactionType::Eip4844 as u8;
return Ok(());
} else {
return Err(DeriveTxTypeError::MissingTargetForEip4844);
}
}
if !self.authorization_list.is_empty() {
if let TxKind::Call(_) = self.kind {
self.tx_type = TransactionType::Eip7702 as u8;
return Ok(());
} else {
return Err(DeriveTxTypeError::MissingTargetForEip7702);
}
}
Ok(())
}
pub fn set_signed_authorization(&mut self, auth: Vec<SignedAuthorization>) {
self.authorization_list = auth.into_iter().map(Either::Left).collect();
}
pub fn set_recovered_authorization(&mut self, auth: Vec<RecoveredAuthorization>) {
self.authorization_list = auth.into_iter().map(Either::Right).collect();
}
}
impl Transaction for TxEnv {
type AccessListItem<'a> = &'a AccessListItem;
type Authorization<'a> = &'a Either<SignedAuthorization, RecoveredAuthorization>;
fn tx_type(&self) -> u8 {
self.tx_type
}
fn kind(&self) -> TxKind {
self.kind
}
fn caller(&self) -> Address {
self.caller
}
fn gas_limit(&self) -> u64 {
self.gas_limit
}
fn gas_price(&self) -> u128 {
self.gas_price
}
fn value(&self) -> U256 {
self.value
}
fn nonce(&self) -> u64 {
self.nonce
}
fn chain_id(&self) -> Option<u64> {
self.chain_id
}
fn access_list(&self) -> Option<impl Iterator<Item = Self::AccessListItem<'_>>> {
Some(self.access_list.0.iter())
}
fn max_fee_per_gas(&self) -> u128 {
self.gas_price
}
fn max_fee_per_blob_gas(&self) -> u128 {
self.max_fee_per_blob_gas
}
fn authorization_list_len(&self) -> usize {
self.authorization_list.len()
}
fn authorization_list(&self) -> impl Iterator<Item = Self::Authorization<'_>> {
self.authorization_list.iter()
}
fn input(&self) -> &Bytes {
&self.data
}
fn blob_versioned_hashes(&self) -> &[B256] {
&self.blob_hashes
}
fn max_priority_fee_per_gas(&self) -> Option<u128> {
self.gas_priority_fee
}
}
#[derive(Default, Debug)]
pub struct TxEnvBuilder {
tx_type: Option<u8>,
caller: Address,
gas_limit: u64,
gas_price: u128,
kind: TxKind,
value: U256,
data: Bytes,
nonce: u64,
chain_id: Option<u64>,
access_list: AccessList,
gas_priority_fee: Option<u128>,
blob_hashes: Vec<B256>,
max_fee_per_blob_gas: u128,
authorization_list: Vec<Either<SignedAuthorization, RecoveredAuthorization>>,
}
impl TxEnvBuilder {
pub fn new() -> Self {
Self {
tx_type: None,
caller: Address::default(),
gas_limit: eip7825::TX_GAS_LIMIT_CAP,
gas_price: 0,
kind: TxKind::Call(Address::default()),
value: U256::ZERO,
data: Bytes::default(),
nonce: 0,
chain_id: Some(1), access_list: Default::default(),
gas_priority_fee: None,
blob_hashes: Vec::new(),
max_fee_per_blob_gas: 0,
authorization_list: Vec::new(),
}
}
pub fn tx_type(mut self, tx_type: Option<u8>) -> Self {
self.tx_type = tx_type;
self
}
pub fn get_tx_type(&self) -> Option<u8> {
self.tx_type
}
pub fn caller(mut self, caller: Address) -> Self {
self.caller = caller;
self
}
pub fn gas_limit(mut self, gas_limit: u64) -> Self {
self.gas_limit = gas_limit;
self
}
pub fn max_fee_per_gas(mut self, max_fee_per_gas: u128) -> Self {
self.gas_price = max_fee_per_gas;
self
}
pub fn gas_price(mut self, gas_price: u128) -> Self {
self.gas_price = gas_price;
self
}
pub fn kind(mut self, kind: TxKind) -> Self {
self.kind = kind;
self
}
pub fn call(mut self, target: Address) -> Self {
self.kind = TxKind::Call(target);
self
}
pub fn create(mut self) -> Self {
self.kind = TxKind::Create;
self
}
pub fn to(self, target: Address) -> Self {
self.call(target)
}
pub fn value(mut self, value: U256) -> Self {
self.value = value;
self
}
pub fn data(mut self, data: Bytes) -> Self {
self.data = data;
self
}
pub fn nonce(mut self, nonce: u64) -> Self {
self.nonce = nonce;
self
}
pub fn chain_id(mut self, chain_id: Option<u64>) -> Self {
self.chain_id = chain_id;
self
}
pub fn access_list(mut self, access_list: AccessList) -> Self {
self.access_list = access_list;
self
}
pub fn gas_priority_fee(mut self, gas_priority_fee: Option<u128>) -> Self {
self.gas_priority_fee = gas_priority_fee;
self
}
pub fn blob_hashes(mut self, blob_hashes: Vec<B256>) -> Self {
self.blob_hashes = blob_hashes;
self
}
pub fn max_fee_per_blob_gas(mut self, max_fee_per_blob_gas: u128) -> Self {
self.max_fee_per_blob_gas = max_fee_per_blob_gas;
self
}
pub fn authorization_list(
mut self,
authorization_list: Vec<Either<SignedAuthorization, RecoveredAuthorization>>,
) -> Self {
self.authorization_list = authorization_list;
self
}
pub fn authorization_list_signed(mut self, auth: Vec<SignedAuthorization>) -> Self {
self.authorization_list = auth.into_iter().map(Either::Left).collect();
self
}
pub fn authorization_list_recovered(mut self, auth: Vec<RecoveredAuthorization>) -> Self {
self.authorization_list = auth.into_iter().map(Either::Right).collect();
self
}
pub fn build_fill(mut self) -> TxEnv {
if let Some(tx_type) = self.tx_type {
match TransactionType::from(tx_type) {
TransactionType::Legacy => {
}
TransactionType::Eip2930 => {
}
TransactionType::Eip1559 => {
if self.gas_priority_fee.is_none() {
self.gas_priority_fee = Some(0);
}
}
TransactionType::Eip4844 => {
if self.gas_priority_fee.is_none() {
self.gas_priority_fee = Some(0);
}
if self.blob_hashes.is_empty() {
self.blob_hashes = vec![B256::default()];
}
if !self.kind.is_call() {
self.kind = TxKind::Call(Address::default());
}
}
TransactionType::Eip7702 => {
if self.gas_priority_fee.is_none() {
self.gas_priority_fee = Some(0);
}
if self.authorization_list.is_empty() {
self.authorization_list =
vec![Either::Right(RecoveredAuthorization::new_unchecked(
Authorization {
chain_id: U256::from(self.chain_id.unwrap_or(1)),
address: self.caller,
nonce: self.nonce,
},
RecoveredAuthority::Invalid,
))];
}
if !self.kind.is_call() {
self.kind = TxKind::Call(Address::default());
}
}
TransactionType::Custom => {
}
}
}
let mut tx = TxEnv {
tx_type: self.tx_type.unwrap_or(0),
caller: self.caller,
gas_limit: self.gas_limit,
gas_price: self.gas_price,
kind: self.kind,
value: self.value,
data: self.data,
nonce: self.nonce,
chain_id: self.chain_id,
access_list: self.access_list,
gas_priority_fee: self.gas_priority_fee,
blob_hashes: self.blob_hashes,
max_fee_per_blob_gas: self.max_fee_per_blob_gas,
authorization_list: self.authorization_list,
};
if self.tx_type.is_none() {
match tx.derive_tx_type() {
Ok(_) => {}
Err(DeriveTxTypeError::MissingTargetForEip4844) => {
tx.kind = TxKind::Call(Address::default());
}
Err(DeriveTxTypeError::MissingTargetForEip7702) => {
tx.kind = TxKind::Call(Address::default());
}
Err(DeriveTxTypeError::MissingTargetForEip7873) => {
tx.kind = TxKind::Call(Address::default());
}
}
}
tx
}
pub fn build(self) -> Result<TxEnv, TxEnvBuildError> {
if let Some(tx_type) = self.tx_type {
match TransactionType::from(tx_type) {
TransactionType::Legacy => {
}
TransactionType::Eip2930 => {
}
TransactionType::Eip1559 => {
if self.gas_priority_fee.is_none() {
return Err(TxEnvBuildError::MissingGasPriorityFeeForEip1559);
}
}
TransactionType::Eip4844 => {
if self.gas_priority_fee.is_none() {
return Err(TxEnvBuildError::MissingGasPriorityFeeForEip1559);
}
if self.blob_hashes.is_empty() {
return Err(TxEnvBuildError::MissingBlobHashesForEip4844);
}
if !self.kind.is_call() {
return Err(TxEnvBuildError::MissingTargetForEip4844);
}
}
TransactionType::Eip7702 => {
if self.gas_priority_fee.is_none() {
return Err(TxEnvBuildError::MissingGasPriorityFeeForEip1559);
}
if self.authorization_list.is_empty() {
return Err(TxEnvBuildError::MissingAuthorizationListForEip7702);
}
if !self.kind.is_call() {
return Err(DeriveTxTypeError::MissingTargetForEip7702.into());
}
}
TransactionType::Custom => {
}
}
}
let mut tx = TxEnv {
tx_type: self.tx_type.unwrap_or(0),
caller: self.caller,
gas_limit: self.gas_limit,
gas_price: self.gas_price,
kind: self.kind,
value: self.value,
data: self.data,
nonce: self.nonce,
chain_id: self.chain_id,
access_list: self.access_list,
gas_priority_fee: self.gas_priority_fee,
blob_hashes: self.blob_hashes,
max_fee_per_blob_gas: self.max_fee_per_blob_gas,
authorization_list: self.authorization_list,
};
if self.tx_type.is_none() {
tx.derive_tx_type()?;
}
Ok(tx)
}
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum TxEnvBuildError {
DeriveErr(DeriveTxTypeError),
MissingGasPriorityFeeForEip1559,
MissingBlobHashesForEip4844,
MissingAuthorizationListForEip7702,
MissingTargetForEip4844,
}
impl core::fmt::Display for TxEnvBuildError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::DeriveErr(err) => write!(f, "derive tx type error: {err}"),
Self::MissingGasPriorityFeeForEip1559 => {
f.write_str("missing gas priority fee for EIP-1559")
}
Self::MissingBlobHashesForEip4844 => f.write_str("missing blob hashes for EIP-4844"),
Self::MissingAuthorizationListForEip7702 => {
f.write_str("missing authorization list for EIP-7702")
}
Self::MissingTargetForEip4844 => f.write_str("missing target for EIP-4844"),
}
}
}
impl core::error::Error for TxEnvBuildError {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
match self {
Self::DeriveErr(err) => Some(err),
_ => None,
}
}
}
impl From<DeriveTxTypeError> for TxEnvBuildError {
fn from(error: DeriveTxTypeError) -> Self {
TxEnvBuildError::DeriveErr(error)
}
}
impl TxEnv {
pub fn builder() -> TxEnvBuilder {
TxEnvBuilder::new()
}
pub fn builder_for_bench() -> TxEnvBuilder {
TxEnv::new_bench().modify()
}
pub fn modify(self) -> TxEnvBuilder {
let TxEnv {
tx_type,
caller,
gas_limit,
gas_price,
kind,
value,
data,
nonce,
chain_id,
access_list,
gas_priority_fee,
blob_hashes,
max_fee_per_blob_gas,
authorization_list,
} = self;
TxEnvBuilder::new()
.tx_type(Some(tx_type))
.caller(caller)
.gas_limit(gas_limit)
.gas_price(gas_price)
.kind(kind)
.value(value)
.data(data)
.nonce(nonce)
.chain_id(chain_id)
.access_list(access_list)
.gas_priority_fee(gas_priority_fee)
.blob_hashes(blob_hashes)
.max_fee_per_blob_gas(max_fee_per_blob_gas)
.authorization_list(authorization_list)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn effective_gas_setup(
tx_type: TransactionType,
gas_price: u128,
gas_priority_fee: Option<u128>,
) -> u128 {
let tx = TxEnv {
tx_type: tx_type as u8,
gas_price,
gas_priority_fee,
..Default::default()
};
let base_fee = 100;
tx.effective_gas_price(base_fee)
}
#[test]
fn test_tx_env_builder_build_valid_legacy() {
let tx = TxEnvBuilder::new()
.tx_type(Some(0))
.caller(Address::from([1u8; 20]))
.gas_limit(21000)
.gas_price(20)
.kind(TxKind::Call(Address::from([2u8; 20])))
.value(U256::from(100))
.data(Bytes::from(vec![0x01, 0x02]))
.nonce(5)
.chain_id(Some(1))
.build()
.unwrap();
assert_eq!(tx.kind, TxKind::Call(Address::from([2u8; 20])));
assert_eq!(tx.caller, Address::from([1u8; 20]));
assert_eq!(tx.gas_limit, 21000);
assert_eq!(tx.gas_price, 20);
assert_eq!(tx.value, U256::from(100));
assert_eq!(tx.data, Bytes::from(vec![0x01, 0x02]));
assert_eq!(tx.nonce, 5);
assert_eq!(tx.chain_id, Some(1));
assert_eq!(tx.tx_type, TransactionType::Legacy);
}
#[test]
fn test_tx_env_builder_build_valid_eip2930() {
let access_list = AccessList(vec![AccessListItem {
address: Address::from([3u8; 20]),
storage_keys: vec![B256::from([4u8; 32])],
}]);
let tx = TxEnvBuilder::new()
.tx_type(Some(1))
.caller(Address::from([1u8; 20]))
.gas_limit(50000)
.gas_price(25)
.kind(TxKind::Call(Address::from([2u8; 20])))
.access_list(access_list.clone())
.build()
.unwrap();
assert_eq!(tx.tx_type, TransactionType::Eip2930);
assert_eq!(tx.access_list, access_list);
}
#[test]
fn test_tx_env_builder_build_valid_eip1559() {
let tx = TxEnvBuilder::new()
.tx_type(Some(2))
.caller(Address::from([1u8; 20]))
.gas_limit(50000)
.gas_price(30)
.gas_priority_fee(Some(10))
.kind(TxKind::Call(Address::from([2u8; 20])))
.build()
.unwrap();
assert_eq!(tx.tx_type, TransactionType::Eip1559);
assert_eq!(tx.gas_priority_fee, Some(10));
}
#[test]
fn test_tx_env_builder_build_valid_eip4844() {
let blob_hashes = vec![B256::from([5u8; 32]), B256::from([6u8; 32])];
let tx = TxEnvBuilder::new()
.tx_type(Some(3))
.caller(Address::from([1u8; 20]))
.gas_limit(50000)
.gas_price(30)
.gas_priority_fee(Some(10))
.kind(TxKind::Call(Address::from([2u8; 20])))
.blob_hashes(blob_hashes.clone())
.max_fee_per_blob_gas(100)
.build()
.unwrap();
assert_eq!(tx.tx_type, TransactionType::Eip4844);
assert_eq!(tx.blob_hashes, blob_hashes);
assert_eq!(tx.max_fee_per_blob_gas, 100);
}
#[test]
fn test_tx_env_builder_build_valid_eip7702() {
let auth = RecoveredAuthorization::new_unchecked(
Authorization {
chain_id: U256::from(1),
nonce: 0,
address: Address::default(),
},
RecoveredAuthority::Valid(Address::default()),
);
let auth_list = vec![Either::Right(auth)];
let tx = TxEnvBuilder::new()
.tx_type(Some(4))
.caller(Address::from([1u8; 20]))
.gas_limit(50000)
.gas_price(30)
.gas_priority_fee(Some(10))
.kind(TxKind::Call(Address::from([2u8; 20])))
.authorization_list(auth_list.clone())
.build()
.unwrap();
assert_eq!(tx.tx_type, TransactionType::Eip7702);
assert_eq!(tx.authorization_list.len(), 1);
}
#[test]
fn test_tx_env_builder_build_create_transaction() {
let bytecode = Bytes::from(vec![0x60, 0x80, 0x60, 0x40]);
let tx = TxEnvBuilder::new()
.kind(TxKind::Create)
.data(bytecode.clone())
.gas_limit(100000)
.gas_price(20)
.build()
.unwrap();
assert_eq!(tx.kind, TxKind::Create);
assert_eq!(tx.data, bytecode);
}
#[test]
fn test_tx_env_builder_build_errors_eip1559_missing_priority_fee() {
let result = TxEnvBuilder::new()
.tx_type(Some(2))
.caller(Address::from([1u8; 20]))
.gas_limit(50000)
.gas_price(30)
.kind(TxKind::Call(Address::from([2u8; 20])))
.build();
assert!(matches!(
result,
Err(TxEnvBuildError::MissingGasPriorityFeeForEip1559)
));
}
#[test]
fn test_tx_env_builder_build_errors_eip4844_missing_blob_hashes() {
let result = TxEnvBuilder::new()
.tx_type(Some(3))
.gas_priority_fee(Some(10))
.kind(TxKind::Call(Address::from([2u8; 20])))
.build();
assert!(matches!(
result,
Err(TxEnvBuildError::MissingBlobHashesForEip4844)
));
}
#[test]
fn test_tx_env_builder_build_errors_eip4844_not_call() {
let result = TxEnvBuilder::new()
.tx_type(Some(3))
.gas_priority_fee(Some(10))
.blob_hashes(vec![B256::from([5u8; 32])])
.kind(TxKind::Create)
.build();
assert!(matches!(
result,
Err(TxEnvBuildError::MissingTargetForEip4844)
));
}
#[test]
fn test_tx_env_builder_build_errors_eip7702_missing_auth_list() {
let result = TxEnvBuilder::new()
.tx_type(Some(4))
.gas_priority_fee(Some(10))
.kind(TxKind::Call(Address::from([2u8; 20])))
.build();
assert!(matches!(
result,
Err(TxEnvBuildError::MissingAuthorizationListForEip7702)
));
}
#[test]
fn test_tx_env_builder_build_errors_eip7702_not_call() {
let auth = RecoveredAuthorization::new_unchecked(
Authorization {
chain_id: U256::from(1),
nonce: 0,
address: Address::default(),
},
RecoveredAuthority::Valid(Address::default()),
);
let result = TxEnvBuilder::new()
.tx_type(Some(4))
.gas_priority_fee(Some(10))
.authorization_list(vec![Either::Right(auth)])
.kind(TxKind::Create)
.build();
assert!(matches!(result, Err(TxEnvBuildError::DeriveErr(_))));
}
#[test]
fn test_tx_env_builder_build_fill_legacy() {
let tx = TxEnvBuilder::new()
.caller(Address::from([1u8; 20]))
.gas_limit(21000)
.gas_price(20)
.kind(TxKind::Call(Address::from([2u8; 20])))
.build_fill();
assert_eq!(tx.tx_type, TransactionType::Legacy);
assert_eq!(tx.gas_priority_fee, None);
}
#[test]
fn test_tx_env_builder_build_fill_eip1559_missing_priority_fee() {
let tx = TxEnvBuilder::new()
.tx_type(Some(2))
.caller(Address::from([1u8; 20]))
.gas_limit(50000)
.gas_price(30)
.kind(TxKind::Call(Address::from([2u8; 20])))
.build_fill();
assert_eq!(tx.tx_type, TransactionType::Eip1559);
assert_eq!(tx.gas_priority_fee, Some(0));
}
#[test]
fn test_tx_env_builder_build_fill_eip4844_missing_blob_hashes() {
let tx = TxEnvBuilder::new()
.tx_type(Some(3))
.gas_priority_fee(Some(10))
.kind(TxKind::Call(Address::from([2u8; 20])))
.build_fill();
assert_eq!(tx.tx_type, TransactionType::Eip4844);
assert_eq!(tx.blob_hashes.len(), 1);
assert_eq!(tx.blob_hashes[0], B256::default());
}
#[test]
fn test_tx_env_builder_build_fill_eip4844_create_to_call() {
let tx = TxEnvBuilder::new()
.tx_type(Some(3))
.gas_priority_fee(Some(10))
.blob_hashes(vec![B256::from([5u8; 32])])
.kind(TxKind::Create)
.build_fill();
assert_eq!(tx.tx_type, TransactionType::Eip4844);
assert_eq!(tx.kind, TxKind::Call(Address::default()));
}
#[test]
fn test_tx_env_builder_build_fill_eip7702_missing_auth_list() {
let tx = TxEnvBuilder::new()
.tx_type(Some(4))
.gas_priority_fee(Some(10))
.kind(TxKind::Call(Address::from([2u8; 20])))
.build_fill();
assert_eq!(tx.tx_type, TransactionType::Eip7702);
assert_eq!(tx.authorization_list.len(), 1);
}
#[test]
fn test_tx_env_builder_build_fill_eip7702_create_to_call() {
let auth = RecoveredAuthorization::new_unchecked(
Authorization {
chain_id: U256::from(1),
nonce: 0,
address: Address::default(),
},
RecoveredAuthority::Valid(Address::default()),
);
let tx = TxEnvBuilder::new()
.tx_type(Some(4))
.gas_priority_fee(Some(10))
.authorization_list(vec![Either::Right(auth)])
.kind(TxKind::Create)
.build_fill();
assert_eq!(tx.tx_type, TransactionType::Eip7702);
assert_eq!(tx.kind, TxKind::Call(Address::default()));
}
#[test]
fn test_tx_env_builder_derive_tx_type_legacy() {
let tx = TxEnvBuilder::new()
.caller(Address::from([1u8; 20]))
.gas_limit(21000)
.gas_price(20)
.build()
.unwrap();
assert_eq!(tx.tx_type, TransactionType::Legacy);
}
#[test]
fn test_tx_env_builder_derive_tx_type_eip2930() {
let access_list = AccessList(vec![AccessListItem {
address: Address::from([3u8; 20]),
storage_keys: vec![B256::from([4u8; 32])],
}]);
let tx = TxEnvBuilder::new()
.caller(Address::from([1u8; 20]))
.access_list(access_list)
.build()
.unwrap();
assert_eq!(tx.tx_type, TransactionType::Eip2930);
}
#[test]
fn test_tx_env_builder_derive_tx_type_eip1559() {
let tx = TxEnvBuilder::new()
.caller(Address::from([1u8; 20]))
.gas_priority_fee(Some(10))
.build()
.unwrap();
assert_eq!(tx.tx_type, TransactionType::Eip1559);
}
#[test]
fn test_tx_env_builder_derive_tx_type_eip4844() {
let tx = TxEnvBuilder::new()
.caller(Address::from([1u8; 20]))
.gas_priority_fee(Some(10))
.blob_hashes(vec![B256::from([5u8; 32])])
.kind(TxKind::Call(Address::from([2u8; 20])))
.build()
.unwrap();
assert_eq!(tx.tx_type, TransactionType::Eip4844);
}
#[test]
fn test_tx_env_builder_derive_tx_type_eip7702() {
let auth = RecoveredAuthorization::new_unchecked(
Authorization {
chain_id: U256::from(1),
nonce: 0,
address: Address::default(),
},
RecoveredAuthority::Valid(Address::default()),
);
let tx = TxEnvBuilder::new()
.caller(Address::from([1u8; 20]))
.gas_priority_fee(Some(10))
.authorization_list(vec![Either::Right(auth)])
.kind(TxKind::Call(Address::from([2u8; 20])))
.build()
.unwrap();
assert_eq!(tx.tx_type, TransactionType::Eip7702);
}
#[test]
fn test_tx_env_builder_custom_tx_type() {
let tx = TxEnvBuilder::new()
.tx_type(Some(0xFF))
.caller(Address::from([1u8; 20]))
.build()
.unwrap();
assert_eq!(tx.tx_type, TransactionType::Custom);
}
#[test]
fn test_tx_env_builder_chain_methods() {
let tx = TxEnvBuilder::new()
.caller(Address::from([1u8; 20]))
.gas_limit(50000)
.gas_price(25)
.kind(TxKind::Call(Address::from([2u8; 20])))
.value(U256::from(1000))
.data(Bytes::from(vec![0x12, 0x34]))
.nonce(10)
.chain_id(Some(5))
.access_list(AccessList(vec![AccessListItem {
address: Address::from([3u8; 20]),
storage_keys: vec![],
}]))
.gas_priority_fee(Some(5))
.blob_hashes(vec![B256::from([7u8; 32])])
.max_fee_per_blob_gas(200)
.build_fill();
assert_eq!(tx.caller, Address::from([1u8; 20]));
assert_eq!(tx.gas_limit, 50000);
assert_eq!(tx.gas_price, 25);
assert_eq!(tx.kind, TxKind::Call(Address::from([2u8; 20])));
assert_eq!(tx.value, U256::from(1000));
assert_eq!(tx.data, Bytes::from(vec![0x12, 0x34]));
assert_eq!(tx.nonce, 10);
assert_eq!(tx.chain_id, Some(5));
assert_eq!(tx.access_list.len(), 1);
assert_eq!(tx.gas_priority_fee, Some(5));
assert_eq!(tx.blob_hashes.len(), 1);
assert_eq!(tx.max_fee_per_blob_gas, 200);
}
#[test]
fn test_effective_gas_price() {
assert_eq!(90, effective_gas_setup(TransactionType::Legacy, 90, None));
assert_eq!(
90,
effective_gas_setup(TransactionType::Legacy, 90, Some(0))
);
assert_eq!(
90,
effective_gas_setup(TransactionType::Legacy, 90, Some(10))
);
assert_eq!(
120,
effective_gas_setup(TransactionType::Legacy, 120, Some(10))
);
assert_eq!(90, effective_gas_setup(TransactionType::Eip2930, 90, None));
assert_eq!(
90,
effective_gas_setup(TransactionType::Eip2930, 90, Some(0))
);
assert_eq!(
90,
effective_gas_setup(TransactionType::Eip2930, 90, Some(10))
);
assert_eq!(
120,
effective_gas_setup(TransactionType::Eip2930, 120, Some(10))
);
assert_eq!(90, effective_gas_setup(TransactionType::Eip1559, 90, None));
assert_eq!(
90,
effective_gas_setup(TransactionType::Eip1559, 90, Some(0))
);
assert_eq!(
90,
effective_gas_setup(TransactionType::Eip1559, 90, Some(10))
);
assert_eq!(
110,
effective_gas_setup(TransactionType::Eip1559, 120, Some(10))
);
assert_eq!(90, effective_gas_setup(TransactionType::Eip4844, 90, None));
assert_eq!(
90,
effective_gas_setup(TransactionType::Eip4844, 90, Some(0))
);
assert_eq!(
90,
effective_gas_setup(TransactionType::Eip4844, 90, Some(10))
);
assert_eq!(
110,
effective_gas_setup(TransactionType::Eip4844, 120, Some(10))
);
assert_eq!(90, effective_gas_setup(TransactionType::Eip7702, 90, None));
assert_eq!(
90,
effective_gas_setup(TransactionType::Eip7702, 90, Some(0))
);
assert_eq!(
90,
effective_gas_setup(TransactionType::Eip7702, 90, Some(10))
);
assert_eq!(
110,
effective_gas_setup(TransactionType::Eip7702, 120, Some(10))
);
}
}