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 primitives::{Address, Bytes, TxKind, B256, U256};
12use std::{vec, vec::Vec};
13
14#[derive(Clone, Debug, PartialEq, Eq)]
20#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
21pub struct TxEnv {
22 pub tx_type: u8,
24 pub caller: Address,
26 pub gas_limit: u64,
28 pub gas_price: u128,
32 pub kind: TxKind,
34 pub value: U256,
36 pub data: Bytes,
38
39 pub nonce: u64,
41
42 pub chain_id: Option<u64>,
50
51 pub access_list: AccessList,
57
58 pub gas_priority_fee: Option<u128>,
64
65 pub blob_hashes: Vec<B256>,
73
74 pub max_fee_per_blob_gas: u128,
80
81 pub authorization_list: Vec<Either<SignedAuthorization, RecoveredAuthorization>>,
90 }
96
97impl Default for TxEnv {
98 fn default() -> Self {
99 Self::builder().build().unwrap()
100 }
101}
102
103#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
105#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
106pub enum DeriveTxTypeError {
107 MissingTargetForEip4844,
109 MissingTargetForEip7702,
111 MissingTargetForEip7873,
113}
114
115impl TxEnv {
116 pub fn derive_tx_type(&mut self) -> Result<(), DeriveTxTypeError> {
119 if !self.access_list.0.is_empty() {
120 self.tx_type = TransactionType::Eip2930 as u8;
121 }
122
123 if self.gas_priority_fee.is_some() {
124 self.tx_type = TransactionType::Eip1559 as u8;
125 }
126
127 if !self.blob_hashes.is_empty() || self.max_fee_per_blob_gas > 0 {
128 if let TxKind::Call(_) = self.kind {
129 self.tx_type = TransactionType::Eip4844 as u8;
130 return Ok(());
131 } else {
132 return Err(DeriveTxTypeError::MissingTargetForEip4844);
133 }
134 }
135
136 if !self.authorization_list.is_empty() {
137 if let TxKind::Call(_) = self.kind {
138 self.tx_type = TransactionType::Eip7702 as u8;
139 return Ok(());
140 } else {
141 return Err(DeriveTxTypeError::MissingTargetForEip7702);
142 }
143 }
144
145 Ok(())
156 }
157
158 pub fn set_signed_authorization(&mut self, auth: Vec<SignedAuthorization>) {
160 self.authorization_list = auth.into_iter().map(Either::Left).collect();
161 }
162
163 pub fn set_recovered_authorization(&mut self, auth: Vec<RecoveredAuthorization>) {
165 self.authorization_list = auth.into_iter().map(Either::Right).collect();
166 }
167}
168
169impl Transaction for TxEnv {
170 type AccessListItem<'a> = &'a AccessListItem;
171 type Authorization<'a> = &'a Either<SignedAuthorization, RecoveredAuthorization>;
172
173 fn tx_type(&self) -> u8 {
174 self.tx_type
175 }
176
177 fn kind(&self) -> TxKind {
178 self.kind
179 }
180
181 fn caller(&self) -> Address {
182 self.caller
183 }
184
185 fn gas_limit(&self) -> u64 {
186 self.gas_limit
187 }
188
189 fn gas_price(&self) -> u128 {
190 self.gas_price
191 }
192
193 fn value(&self) -> U256 {
194 self.value
195 }
196
197 fn nonce(&self) -> u64 {
198 self.nonce
199 }
200
201 fn chain_id(&self) -> Option<u64> {
202 self.chain_id
203 }
204
205 fn access_list(&self) -> Option<impl Iterator<Item = Self::AccessListItem<'_>>> {
206 Some(self.access_list.0.iter())
207 }
208
209 fn max_fee_per_gas(&self) -> u128 {
210 self.gas_price
211 }
212
213 fn max_fee_per_blob_gas(&self) -> u128 {
214 self.max_fee_per_blob_gas
215 }
216
217 fn authorization_list_len(&self) -> usize {
218 self.authorization_list.len()
219 }
220
221 fn authorization_list(&self) -> impl Iterator<Item = Self::Authorization<'_>> {
222 self.authorization_list.iter()
223 }
224
225 fn input(&self) -> &Bytes {
226 &self.data
227 }
228
229 fn blob_versioned_hashes(&self) -> &[B256] {
230 &self.blob_hashes
231 }
232
233 fn max_priority_fee_per_gas(&self) -> Option<u128> {
234 self.gas_priority_fee
235 }
236
237 }
242
243#[derive(Default, Debug)]
245pub struct TxEnvBuilder {
246 tx_type: Option<u8>,
247 caller: Address,
248 gas_limit: u64,
249 gas_price: u128,
250 kind: TxKind,
251 value: U256,
252 data: Bytes,
253 nonce: u64,
254 chain_id: Option<u64>,
255 access_list: AccessList,
256 gas_priority_fee: Option<u128>,
257 blob_hashes: Vec<B256>,
258 max_fee_per_blob_gas: u128,
259 authorization_list: Vec<Either<SignedAuthorization, RecoveredAuthorization>>,
260}
261
262impl TxEnvBuilder {
263 pub fn new() -> Self {
265 Self {
266 tx_type: None,
267 caller: Address::default(),
268 gas_limit: 30_000_000,
269 gas_price: 0,
270 kind: TxKind::Call(Address::default()),
271 value: U256::ZERO,
272 data: Bytes::default(),
273 nonce: 0,
274 chain_id: Some(1), access_list: Default::default(),
276 gas_priority_fee: None,
277 blob_hashes: Vec::new(),
278 max_fee_per_blob_gas: 0,
279 authorization_list: Vec::new(),
280 }
281 }
282
283 pub fn tx_type(mut self, tx_type: Option<u8>) -> Self {
285 self.tx_type = tx_type;
286 self
287 }
288
289 pub fn caller(mut self, caller: Address) -> Self {
291 self.caller = caller;
292 self
293 }
294
295 pub fn gas_limit(mut self, gas_limit: u64) -> Self {
297 self.gas_limit = gas_limit;
298 self
299 }
300
301 pub fn max_fee_per_gas(mut self, max_fee_per_gas: u128) -> Self {
303 self.gas_price = max_fee_per_gas;
304 self
305 }
306
307 pub fn gas_price(mut self, gas_price: u128) -> Self {
309 self.gas_price = gas_price;
310 self
311 }
312
313 pub fn kind(mut self, kind: TxKind) -> Self {
315 self.kind = kind;
316 self
317 }
318
319 pub fn value(mut self, value: U256) -> Self {
321 self.value = value;
322 self
323 }
324
325 pub fn data(mut self, data: Bytes) -> Self {
327 self.data = data;
328 self
329 }
330
331 pub fn nonce(mut self, nonce: u64) -> Self {
333 self.nonce = nonce;
334 self
335 }
336
337 pub fn chain_id(mut self, chain_id: Option<u64>) -> Self {
339 self.chain_id = chain_id;
340 self
341 }
342
343 pub fn access_list(mut self, access_list: AccessList) -> Self {
345 self.access_list = access_list;
346 self
347 }
348
349 pub fn gas_priority_fee(mut self, gas_priority_fee: Option<u128>) -> Self {
351 self.gas_priority_fee = gas_priority_fee;
352 self
353 }
354
355 pub fn blob_hashes(mut self, blob_hashes: Vec<B256>) -> Self {
357 self.blob_hashes = blob_hashes;
358 self
359 }
360
361 pub fn max_fee_per_blob_gas(mut self, max_fee_per_blob_gas: u128) -> Self {
363 self.max_fee_per_blob_gas = max_fee_per_blob_gas;
364 self
365 }
366
367 pub fn authorization_list(
369 mut self,
370 authorization_list: Vec<Either<SignedAuthorization, RecoveredAuthorization>>,
371 ) -> Self {
372 self.authorization_list = authorization_list;
373 self
374 }
375
376 pub fn build_fill(mut self) -> TxEnv {
378 let tx_type_not_set = self.tx_type.is_some();
379 if let Some(tx_type) = self.tx_type {
380 match TransactionType::from(tx_type) {
381 TransactionType::Legacy => {
382 }
384 TransactionType::Eip2930 => {
385 }
387 TransactionType::Eip1559 => {
388 if self.gas_priority_fee.is_none() {
390 self.gas_priority_fee = Some(0);
391 }
392 }
393 TransactionType::Eip4844 => {
394 if self.gas_priority_fee.is_none() {
396 self.gas_priority_fee = Some(0);
397 }
398
399 if self.blob_hashes.is_empty() {
401 self.blob_hashes = vec![B256::default()];
402 }
403
404 if !self.kind.is_call() {
406 self.kind = TxKind::Call(Address::default());
407 }
408 }
409 TransactionType::Eip7702 => {
410 if self.gas_priority_fee.is_none() {
412 self.gas_priority_fee = Some(0);
413 }
414
415 if self.authorization_list.is_empty() {
417 self.authorization_list =
419 vec![Either::Right(RecoveredAuthorization::new_unchecked(
420 Authorization {
421 chain_id: U256::from(self.chain_id.unwrap_or(1)),
422 address: self.caller,
423 nonce: self.nonce,
424 },
425 RecoveredAuthority::Invalid,
426 ))];
427 }
428
429 if !self.kind.is_call() {
431 self.kind = TxKind::Call(Address::default());
432 }
433 }
434 TransactionType::Custom => {
435 }
437 }
438 }
439
440 let mut tx = TxEnv {
441 tx_type: self.tx_type.unwrap_or(0),
442 caller: self.caller,
443 gas_limit: self.gas_limit,
444 gas_price: self.gas_price,
445 kind: self.kind,
446 value: self.value,
447 data: self.data,
448 nonce: self.nonce,
449 chain_id: self.chain_id,
450 access_list: self.access_list,
451 gas_priority_fee: self.gas_priority_fee,
452 blob_hashes: self.blob_hashes,
453 max_fee_per_blob_gas: self.max_fee_per_blob_gas,
454 authorization_list: self.authorization_list,
455 };
456
457 if tx_type_not_set {
459 match tx.derive_tx_type() {
460 Ok(_) => {}
461 Err(DeriveTxTypeError::MissingTargetForEip4844) => {
462 tx.kind = TxKind::Call(Address::default());
463 }
464 Err(DeriveTxTypeError::MissingTargetForEip7702) => {
465 tx.kind = TxKind::Call(Address::default());
466 }
467 Err(DeriveTxTypeError::MissingTargetForEip7873) => {
468 tx.kind = TxKind::Call(Address::default());
469 }
470 }
471 }
472
473 tx
474 }
475
476 pub fn build(self) -> Result<TxEnv, TxEnvBuildError> {
479 if let Some(tx_type) = self.tx_type {
481 match TransactionType::from(tx_type) {
482 TransactionType::Legacy => {
483 }
485 TransactionType::Eip2930 => {
486 }
488 TransactionType::Eip1559 => {
489 if self.gas_priority_fee.is_none() {
491 return Err(TxEnvBuildError::MissingGasPriorityFeeForEip1559);
492 }
493 }
494 TransactionType::Eip4844 => {
495 if self.gas_priority_fee.is_none() {
497 return Err(TxEnvBuildError::MissingGasPriorityFeeForEip1559);
498 }
499
500 if self.blob_hashes.is_empty() {
502 return Err(TxEnvBuildError::MissingBlobHashesForEip4844);
503 }
504
505 if !self.kind.is_call() {
507 return Err(TxEnvBuildError::MissingTargetForEip4844);
508 }
509 }
510 TransactionType::Eip7702 => {
511 if self.gas_priority_fee.is_none() {
513 return Err(TxEnvBuildError::MissingGasPriorityFeeForEip1559);
514 }
515
516 if self.authorization_list.is_empty() {
518 return Err(TxEnvBuildError::MissingAuthorizationListForEip7702);
519 }
520
521 if !self.kind.is_call() {
523 return Err(DeriveTxTypeError::MissingTargetForEip4844.into());
524 }
525 }
526 _ => {
527 panic!()
528 }
529 }
530 }
531
532 let mut tx = TxEnv {
533 tx_type: self.tx_type.unwrap_or(0),
534 caller: self.caller,
535 gas_limit: self.gas_limit,
536 gas_price: self.gas_price,
537 kind: self.kind,
538 value: self.value,
539 data: self.data,
540 nonce: self.nonce,
541 chain_id: self.chain_id,
542 access_list: self.access_list,
543 gas_priority_fee: self.gas_priority_fee,
544 blob_hashes: self.blob_hashes,
545 max_fee_per_blob_gas: self.max_fee_per_blob_gas,
546 authorization_list: self.authorization_list,
547 };
548
549 tx.derive_tx_type()?;
551
552 Ok(tx)
553 }
554}
555
556#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
558#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
559pub enum TxEnvBuildError {
560 DeriveErr(DeriveTxTypeError),
562 MissingGasPriorityFeeForEip1559,
564 MissingBlobHashesForEip4844,
566 MissingAuthorizationListForEip7702,
568 MissingTargetForEip4844,
570}
571
572impl From<DeriveTxTypeError> for TxEnvBuildError {
573 fn from(error: DeriveTxTypeError) -> Self {
574 TxEnvBuildError::DeriveErr(error)
575 }
576}
577
578impl TxEnv {
579 pub fn builder() -> TxEnvBuilder {
581 TxEnvBuilder::new()
582 }
583}
584
585#[cfg(test)]
586mod tests {
587 use super::*;
588
589 fn effective_gas_setup(
590 tx_type: TransactionType,
591 gas_price: u128,
592 gas_priority_fee: Option<u128>,
593 ) -> u128 {
594 let tx = TxEnv {
595 tx_type: tx_type as u8,
596 gas_price,
597 gas_priority_fee,
598 ..Default::default()
599 };
600 let base_fee = 100;
601 tx.effective_gas_price(base_fee)
602 }
603
604 #[test]
605 fn test_effective_gas_price() {
606 assert_eq!(90, effective_gas_setup(TransactionType::Legacy, 90, None));
607 assert_eq!(
608 90,
609 effective_gas_setup(TransactionType::Legacy, 90, Some(0))
610 );
611 assert_eq!(
612 90,
613 effective_gas_setup(TransactionType::Legacy, 90, Some(10))
614 );
615 assert_eq!(
616 120,
617 effective_gas_setup(TransactionType::Legacy, 120, Some(10))
618 );
619 assert_eq!(90, effective_gas_setup(TransactionType::Eip2930, 90, None));
620 assert_eq!(
621 90,
622 effective_gas_setup(TransactionType::Eip2930, 90, Some(0))
623 );
624 assert_eq!(
625 90,
626 effective_gas_setup(TransactionType::Eip2930, 90, Some(10))
627 );
628 assert_eq!(
629 120,
630 effective_gas_setup(TransactionType::Eip2930, 120, Some(10))
631 );
632 assert_eq!(90, effective_gas_setup(TransactionType::Eip1559, 90, None));
633 assert_eq!(
634 90,
635 effective_gas_setup(TransactionType::Eip1559, 90, Some(0))
636 );
637 assert_eq!(
638 90,
639 effective_gas_setup(TransactionType::Eip1559, 90, Some(10))
640 );
641 assert_eq!(
642 110,
643 effective_gas_setup(TransactionType::Eip1559, 120, Some(10))
644 );
645 assert_eq!(90, effective_gas_setup(TransactionType::Eip4844, 90, None));
646 assert_eq!(
647 90,
648 effective_gas_setup(TransactionType::Eip4844, 90, Some(0))
649 );
650 assert_eq!(
651 90,
652 effective_gas_setup(TransactionType::Eip4844, 90, Some(10))
653 );
654 assert_eq!(
655 110,
656 effective_gas_setup(TransactionType::Eip4844, 120, Some(10))
657 );
658 assert_eq!(90, effective_gas_setup(TransactionType::Eip7702, 90, None));
659 assert_eq!(
660 90,
661 effective_gas_setup(TransactionType::Eip7702, 90, Some(0))
662 );
663 assert_eq!(
664 90,
665 effective_gas_setup(TransactionType::Eip7702, 90, Some(10))
666 );
667 assert_eq!(
668 110,
669 effective_gas_setup(TransactionType::Eip7702, 120, Some(10))
670 );
671 }
672}