1use crate::TransactionType;
3use context_interface::{
4 either::Either,
5 transaction::{
6 AccessList, AccessListItem, Authorization, RecoveredAuthority, RecoveredAuthorization,
7 SignedAuthorization, Transaction,
8 },
9};
10use core::fmt::Debug;
11use database_interface::{BENCH_CALLER, BENCH_TARGET};
12use primitives::{eip7825, Address, Bytes, TxKind, B256, U256};
13use std::{vec, vec::Vec};
14
15#[derive(Clone, Debug, PartialEq, Eq)]
21#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
22pub struct TxEnv {
23 pub tx_type: u8,
25 pub caller: Address,
27 pub gas_limit: u64,
29 pub gas_price: u128,
33 pub kind: TxKind,
35 pub value: U256,
37 pub data: Bytes,
39
40 pub nonce: u64,
42
43 pub chain_id: Option<u64>,
49
50 pub access_list: AccessList,
56
57 pub gas_priority_fee: Option<u128>,
63
64 pub blob_hashes: Vec<B256>,
72
73 pub max_fee_per_blob_gas: u128,
79
80 pub authorization_list: Vec<Either<SignedAuthorization, RecoveredAuthorization>>,
89}
90
91impl Default for TxEnv {
92 fn default() -> Self {
93 Self::builder().build().unwrap()
94 }
95}
96
97#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
99#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
100pub enum DeriveTxTypeError {
101 MissingTargetForEip4844,
103 MissingTargetForEip7702,
105 MissingTargetForEip7873,
107}
108
109impl TxEnv {
110 pub fn new_bench() -> Self {
112 Self {
113 caller: BENCH_CALLER,
114 kind: TxKind::Call(BENCH_TARGET),
115 gas_limit: 1_000_000_000,
116 ..Default::default()
117 }
118 }
119
120 pub fn derive_tx_type(&mut self) -> Result<(), DeriveTxTypeError> {
123 if !self.access_list.0.is_empty() {
124 self.tx_type = TransactionType::Eip2930 as u8;
125 }
126
127 if self.gas_priority_fee.is_some() {
128 self.tx_type = TransactionType::Eip1559 as u8;
129 }
130
131 if !self.blob_hashes.is_empty() || self.max_fee_per_blob_gas > 0 {
132 if let TxKind::Call(_) = self.kind {
133 self.tx_type = TransactionType::Eip4844 as u8;
134 return Ok(());
135 } else {
136 return Err(DeriveTxTypeError::MissingTargetForEip4844);
137 }
138 }
139
140 if !self.authorization_list.is_empty() {
141 if let TxKind::Call(_) = self.kind {
142 self.tx_type = TransactionType::Eip7702 as u8;
143 return Ok(());
144 } else {
145 return Err(DeriveTxTypeError::MissingTargetForEip7702);
146 }
147 }
148 Ok(())
149 }
150
151 pub fn set_signed_authorization(&mut self, auth: Vec<SignedAuthorization>) {
153 self.authorization_list = auth.into_iter().map(Either::Left).collect();
154 }
155
156 pub fn set_recovered_authorization(&mut self, auth: Vec<RecoveredAuthorization>) {
158 self.authorization_list = auth.into_iter().map(Either::Right).collect();
159 }
160}
161
162impl Transaction for TxEnv {
163 type AccessListItem<'a> = &'a AccessListItem;
164 type Authorization<'a> = &'a Either<SignedAuthorization, RecoveredAuthorization>;
165
166 fn tx_type(&self) -> u8 {
167 self.tx_type
168 }
169
170 fn kind(&self) -> TxKind {
171 self.kind
172 }
173
174 fn caller(&self) -> Address {
175 self.caller
176 }
177
178 fn gas_limit(&self) -> u64 {
179 self.gas_limit
180 }
181
182 fn gas_price(&self) -> u128 {
183 self.gas_price
184 }
185
186 fn value(&self) -> U256 {
187 self.value
188 }
189
190 fn nonce(&self) -> u64 {
191 self.nonce
192 }
193
194 fn chain_id(&self) -> Option<u64> {
195 self.chain_id
196 }
197
198 fn access_list(&self) -> Option<impl Iterator<Item = Self::AccessListItem<'_>>> {
199 Some(self.access_list.0.iter())
200 }
201
202 fn max_fee_per_gas(&self) -> u128 {
203 self.gas_price
204 }
205
206 fn max_fee_per_blob_gas(&self) -> u128 {
207 self.max_fee_per_blob_gas
208 }
209
210 fn authorization_list_len(&self) -> usize {
211 self.authorization_list.len()
212 }
213
214 fn authorization_list(&self) -> impl Iterator<Item = Self::Authorization<'_>> {
215 self.authorization_list.iter()
216 }
217
218 fn input(&self) -> &Bytes {
219 &self.data
220 }
221
222 fn blob_versioned_hashes(&self) -> &[B256] {
223 &self.blob_hashes
224 }
225
226 fn max_priority_fee_per_gas(&self) -> Option<u128> {
227 self.gas_priority_fee
228 }
229}
230
231#[derive(Default, Debug)]
233pub struct TxEnvBuilder {
234 tx_type: Option<u8>,
235 caller: Address,
236 gas_limit: u64,
237 gas_price: u128,
238 kind: TxKind,
239 value: U256,
240 data: Bytes,
241 nonce: u64,
242 chain_id: Option<u64>,
243 access_list: AccessList,
244 gas_priority_fee: Option<u128>,
245 blob_hashes: Vec<B256>,
246 max_fee_per_blob_gas: u128,
247 authorization_list: Vec<Either<SignedAuthorization, RecoveredAuthorization>>,
248}
249
250impl TxEnvBuilder {
251 pub fn new() -> Self {
253 Self {
254 tx_type: None,
255 caller: Address::default(),
256 gas_limit: eip7825::TX_GAS_LIMIT_CAP,
257 gas_price: 0,
258 kind: TxKind::Call(Address::default()),
259 value: U256::ZERO,
260 data: Bytes::default(),
261 nonce: 0,
262 chain_id: Some(1), access_list: Default::default(),
264 gas_priority_fee: None,
265 blob_hashes: Vec::new(),
266 max_fee_per_blob_gas: 0,
267 authorization_list: Vec::new(),
268 }
269 }
270
271 pub fn tx_type(mut self, tx_type: Option<u8>) -> Self {
273 self.tx_type = tx_type;
274 self
275 }
276
277 pub fn caller(mut self, caller: Address) -> Self {
279 self.caller = caller;
280 self
281 }
282
283 pub fn gas_limit(mut self, gas_limit: u64) -> Self {
285 self.gas_limit = gas_limit;
286 self
287 }
288
289 pub fn max_fee_per_gas(mut self, max_fee_per_gas: u128) -> Self {
291 self.gas_price = max_fee_per_gas;
292 self
293 }
294
295 pub fn gas_price(mut self, gas_price: u128) -> Self {
297 self.gas_price = gas_price;
298 self
299 }
300
301 pub fn kind(mut self, kind: TxKind) -> Self {
303 self.kind = kind;
304 self
305 }
306
307 pub fn value(mut self, value: U256) -> Self {
309 self.value = value;
310 self
311 }
312
313 pub fn data(mut self, data: Bytes) -> Self {
315 self.data = data;
316 self
317 }
318
319 pub fn nonce(mut self, nonce: u64) -> Self {
321 self.nonce = nonce;
322 self
323 }
324
325 pub fn chain_id(mut self, chain_id: Option<u64>) -> Self {
327 self.chain_id = chain_id;
328 self
329 }
330
331 pub fn access_list(mut self, access_list: AccessList) -> Self {
333 self.access_list = access_list;
334 self
335 }
336
337 pub fn gas_priority_fee(mut self, gas_priority_fee: Option<u128>) -> Self {
339 self.gas_priority_fee = gas_priority_fee;
340 self
341 }
342
343 pub fn blob_hashes(mut self, blob_hashes: Vec<B256>) -> Self {
345 self.blob_hashes = blob_hashes;
346 self
347 }
348
349 pub fn max_fee_per_blob_gas(mut self, max_fee_per_blob_gas: u128) -> Self {
351 self.max_fee_per_blob_gas = max_fee_per_blob_gas;
352 self
353 }
354
355 pub fn authorization_list(
357 mut self,
358 authorization_list: Vec<Either<SignedAuthorization, RecoveredAuthorization>>,
359 ) -> Self {
360 self.authorization_list = authorization_list;
361 self
362 }
363
364 pub fn build_fill(mut self) -> TxEnv {
366 let tx_type_not_set = self.tx_type.is_some();
367 if let Some(tx_type) = self.tx_type {
368 match TransactionType::from(tx_type) {
369 TransactionType::Legacy => {
370 }
372 TransactionType::Eip2930 => {
373 }
375 TransactionType::Eip1559 => {
376 if self.gas_priority_fee.is_none() {
378 self.gas_priority_fee = Some(0);
379 }
380 }
381 TransactionType::Eip4844 => {
382 if self.gas_priority_fee.is_none() {
384 self.gas_priority_fee = Some(0);
385 }
386
387 if self.blob_hashes.is_empty() {
389 self.blob_hashes = vec![B256::default()];
390 }
391
392 if !self.kind.is_call() {
394 self.kind = TxKind::Call(Address::default());
395 }
396 }
397 TransactionType::Eip7702 => {
398 if self.gas_priority_fee.is_none() {
400 self.gas_priority_fee = Some(0);
401 }
402
403 if self.authorization_list.is_empty() {
405 self.authorization_list =
407 vec![Either::Right(RecoveredAuthorization::new_unchecked(
408 Authorization {
409 chain_id: U256::from(self.chain_id.unwrap_or(1)),
410 address: self.caller,
411 nonce: self.nonce,
412 },
413 RecoveredAuthority::Invalid,
414 ))];
415 }
416
417 if !self.kind.is_call() {
419 self.kind = TxKind::Call(Address::default());
420 }
421 }
422 TransactionType::Custom => {
423 }
425 }
426 }
427
428 let mut tx = TxEnv {
429 tx_type: self.tx_type.unwrap_or(0),
430 caller: self.caller,
431 gas_limit: self.gas_limit,
432 gas_price: self.gas_price,
433 kind: self.kind,
434 value: self.value,
435 data: self.data,
436 nonce: self.nonce,
437 chain_id: self.chain_id,
438 access_list: self.access_list,
439 gas_priority_fee: self.gas_priority_fee,
440 blob_hashes: self.blob_hashes,
441 max_fee_per_blob_gas: self.max_fee_per_blob_gas,
442 authorization_list: self.authorization_list,
443 };
444
445 if tx_type_not_set {
447 match tx.derive_tx_type() {
448 Ok(_) => {}
449 Err(DeriveTxTypeError::MissingTargetForEip4844) => {
450 tx.kind = TxKind::Call(Address::default());
451 }
452 Err(DeriveTxTypeError::MissingTargetForEip7702) => {
453 tx.kind = TxKind::Call(Address::default());
454 }
455 Err(DeriveTxTypeError::MissingTargetForEip7873) => {
456 tx.kind = TxKind::Call(Address::default());
457 }
458 }
459 }
460
461 tx
462 }
463
464 pub fn build(self) -> Result<TxEnv, TxEnvBuildError> {
467 if let Some(tx_type) = self.tx_type {
469 match TransactionType::from(tx_type) {
470 TransactionType::Legacy => {
471 }
473 TransactionType::Eip2930 => {
474 }
476 TransactionType::Eip1559 => {
477 if self.gas_priority_fee.is_none() {
479 return Err(TxEnvBuildError::MissingGasPriorityFeeForEip1559);
480 }
481 }
482 TransactionType::Eip4844 => {
483 if self.gas_priority_fee.is_none() {
485 return Err(TxEnvBuildError::MissingGasPriorityFeeForEip1559);
486 }
487
488 if self.blob_hashes.is_empty() {
490 return Err(TxEnvBuildError::MissingBlobHashesForEip4844);
491 }
492
493 if !self.kind.is_call() {
495 return Err(TxEnvBuildError::MissingTargetForEip4844);
496 }
497 }
498 TransactionType::Eip7702 => {
499 if self.gas_priority_fee.is_none() {
501 return Err(TxEnvBuildError::MissingGasPriorityFeeForEip1559);
502 }
503
504 if self.authorization_list.is_empty() {
506 return Err(TxEnvBuildError::MissingAuthorizationListForEip7702);
507 }
508
509 if !self.kind.is_call() {
511 return Err(DeriveTxTypeError::MissingTargetForEip4844.into());
512 }
513 }
514 _ => {
515 panic!()
516 }
517 }
518 }
519
520 let mut tx = TxEnv {
521 tx_type: self.tx_type.unwrap_or(0),
522 caller: self.caller,
523 gas_limit: self.gas_limit,
524 gas_price: self.gas_price,
525 kind: self.kind,
526 value: self.value,
527 data: self.data,
528 nonce: self.nonce,
529 chain_id: self.chain_id,
530 access_list: self.access_list,
531 gas_priority_fee: self.gas_priority_fee,
532 blob_hashes: self.blob_hashes,
533 max_fee_per_blob_gas: self.max_fee_per_blob_gas,
534 authorization_list: self.authorization_list,
535 };
536
537 tx.derive_tx_type()?;
539
540 Ok(tx)
541 }
542}
543
544#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
546#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
547pub enum TxEnvBuildError {
548 DeriveErr(DeriveTxTypeError),
550 MissingGasPriorityFeeForEip1559,
552 MissingBlobHashesForEip4844,
554 MissingAuthorizationListForEip7702,
556 MissingTargetForEip4844,
558}
559
560impl From<DeriveTxTypeError> for TxEnvBuildError {
561 fn from(error: DeriveTxTypeError) -> Self {
562 TxEnvBuildError::DeriveErr(error)
563 }
564}
565
566impl TxEnv {
567 pub fn builder() -> TxEnvBuilder {
569 TxEnvBuilder::new()
570 }
571
572 pub fn builder_for_bench() -> TxEnvBuilder {
574 TxEnv::new_bench().modify()
575 }
576
577 pub fn modify(self) -> TxEnvBuilder {
579 let TxEnv {
580 tx_type,
581 caller,
582 gas_limit,
583 gas_price,
584 kind,
585 value,
586 data,
587 nonce,
588 chain_id,
589 access_list,
590 gas_priority_fee,
591 blob_hashes,
592 max_fee_per_blob_gas,
593 authorization_list,
594 } = self;
595
596 TxEnvBuilder::new()
597 .tx_type(Some(tx_type))
598 .caller(caller)
599 .gas_limit(gas_limit)
600 .gas_price(gas_price)
601 .kind(kind)
602 .value(value)
603 .data(data)
604 .nonce(nonce)
605 .chain_id(chain_id)
606 .access_list(access_list)
607 .gas_priority_fee(gas_priority_fee)
608 .blob_hashes(blob_hashes)
609 .max_fee_per_blob_gas(max_fee_per_blob_gas)
610 .authorization_list(authorization_list)
611 }
612}
613
614#[cfg(test)]
615mod tests {
616 use super::*;
617
618 fn effective_gas_setup(
619 tx_type: TransactionType,
620 gas_price: u128,
621 gas_priority_fee: Option<u128>,
622 ) -> u128 {
623 let tx = TxEnv {
624 tx_type: tx_type as u8,
625 gas_price,
626 gas_priority_fee,
627 ..Default::default()
628 };
629 let base_fee = 100;
630 tx.effective_gas_price(base_fee)
631 }
632
633 #[test]
634 fn test_effective_gas_price() {
635 assert_eq!(90, effective_gas_setup(TransactionType::Legacy, 90, None));
636 assert_eq!(
637 90,
638 effective_gas_setup(TransactionType::Legacy, 90, Some(0))
639 );
640 assert_eq!(
641 90,
642 effective_gas_setup(TransactionType::Legacy, 90, Some(10))
643 );
644 assert_eq!(
645 120,
646 effective_gas_setup(TransactionType::Legacy, 120, Some(10))
647 );
648 assert_eq!(90, effective_gas_setup(TransactionType::Eip2930, 90, None));
649 assert_eq!(
650 90,
651 effective_gas_setup(TransactionType::Eip2930, 90, Some(0))
652 );
653 assert_eq!(
654 90,
655 effective_gas_setup(TransactionType::Eip2930, 90, Some(10))
656 );
657 assert_eq!(
658 120,
659 effective_gas_setup(TransactionType::Eip2930, 120, Some(10))
660 );
661 assert_eq!(90, effective_gas_setup(TransactionType::Eip1559, 90, None));
662 assert_eq!(
663 90,
664 effective_gas_setup(TransactionType::Eip1559, 90, Some(0))
665 );
666 assert_eq!(
667 90,
668 effective_gas_setup(TransactionType::Eip1559, 90, Some(10))
669 );
670 assert_eq!(
671 110,
672 effective_gas_setup(TransactionType::Eip1559, 120, Some(10))
673 );
674 assert_eq!(90, effective_gas_setup(TransactionType::Eip4844, 90, None));
675 assert_eq!(
676 90,
677 effective_gas_setup(TransactionType::Eip4844, 90, Some(0))
678 );
679 assert_eq!(
680 90,
681 effective_gas_setup(TransactionType::Eip4844, 90, Some(10))
682 );
683 assert_eq!(
684 110,
685 effective_gas_setup(TransactionType::Eip4844, 120, Some(10))
686 );
687 assert_eq!(90, effective_gas_setup(TransactionType::Eip7702, 90, None));
688 assert_eq!(
689 90,
690 effective_gas_setup(TransactionType::Eip7702, 90, Some(0))
691 );
692 assert_eq!(
693 90,
694 effective_gas_setup(TransactionType::Eip7702, 90, Some(10))
695 );
696 assert_eq!(
697 110,
698 effective_gas_setup(TransactionType::Eip7702, 120, Some(10))
699 );
700 }
701}