1use crate::{SignableTransaction, Transaction, TxType};
2use alloc::vec::Vec;
3use alloy_eips::{
4 eip2718::IsTyped2718,
5 eip2930::AccessList,
6 eip7702::{constants::EIP7702_TX_TYPE_ID, SignedAuthorization},
7 Typed2718,
8};
9use alloy_primitives::{Address, Bytes, ChainId, Signature, TxKind, B256, U256};
10use alloy_rlp::{BufMut, Decodable, Encodable};
11use core::mem;
12
13use super::{RlpEcdsaDecodableTx, RlpEcdsaEncodableTx};
14
15#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
17#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
18#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
19#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
20#[doc(alias = "Eip7702Transaction", alias = "TransactionEip7702", alias = "Eip7702Tx")]
21pub struct TxEip7702 {
22 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
24 pub chain_id: ChainId,
25 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
27 pub nonce: u64,
28 #[cfg_attr(
34 feature = "serde",
35 serde(with = "alloy_serde::quantity", rename = "gas", alias = "gasLimit")
36 )]
37 pub gas_limit: u64,
38 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
50 pub max_fee_per_gas: u128,
51 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
59 pub max_priority_fee_per_gas: u128,
60 pub to: Address,
62 pub value: U256,
67 pub access_list: AccessList,
73 pub authorization_list: Vec<SignedAuthorization>,
77 pub input: Bytes,
80}
81
82impl TxEip7702 {
83 #[doc(alias = "transaction_type")]
85 pub const fn tx_type() -> TxType {
86 TxType::Eip7702
87 }
88
89 #[inline]
91 pub fn size(&self) -> usize {
92 mem::size_of::<ChainId>() + mem::size_of::<u64>() + mem::size_of::<u64>() + mem::size_of::<u128>() + mem::size_of::<u128>() + mem::size_of::<Address>() + mem::size_of::<U256>() + self.access_list.size() + self.input.len() + self.authorization_list.capacity() * mem::size_of::<SignedAuthorization>()
102 }
104}
105
106impl RlpEcdsaEncodableTx for TxEip7702 {
107 #[doc(hidden)]
109 fn rlp_encoded_fields_length(&self) -> usize {
110 self.chain_id.length()
111 + self.nonce.length()
112 + self.max_priority_fee_per_gas.length()
113 + self.max_fee_per_gas.length()
114 + self.gas_limit.length()
115 + self.to.length()
116 + self.value.length()
117 + self.input.0.length()
118 + self.access_list.length()
119 + self.authorization_list.length()
120 }
121
122 fn rlp_encode_fields(&self, out: &mut dyn alloy_rlp::BufMut) {
123 self.chain_id.encode(out);
124 self.nonce.encode(out);
125 self.max_priority_fee_per_gas.encode(out);
126 self.max_fee_per_gas.encode(out);
127 self.gas_limit.encode(out);
128 self.to.encode(out);
129 self.value.encode(out);
130 self.input.0.encode(out);
131 self.access_list.encode(out);
132 self.authorization_list.encode(out);
133 }
134}
135
136impl RlpEcdsaDecodableTx for TxEip7702 {
137 const DEFAULT_TX_TYPE: u8 = { Self::tx_type() as u8 };
138
139 fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
140 Ok(Self {
141 chain_id: Decodable::decode(buf)?,
142 nonce: Decodable::decode(buf)?,
143 max_priority_fee_per_gas: Decodable::decode(buf)?,
144 max_fee_per_gas: Decodable::decode(buf)?,
145 gas_limit: Decodable::decode(buf)?,
146 to: Decodable::decode(buf)?,
147 value: Decodable::decode(buf)?,
148 input: Decodable::decode(buf)?,
149 access_list: Decodable::decode(buf)?,
150 authorization_list: Decodable::decode(buf)?,
151 })
152 }
153}
154
155impl Transaction for TxEip7702 {
156 #[inline]
157 fn chain_id(&self) -> Option<ChainId> {
158 Some(self.chain_id)
159 }
160
161 #[inline]
162 fn nonce(&self) -> u64 {
163 self.nonce
164 }
165
166 #[inline]
167 fn gas_limit(&self) -> u64 {
168 self.gas_limit
169 }
170
171 #[inline]
172 fn gas_price(&self) -> Option<u128> {
173 None
174 }
175
176 #[inline]
177 fn max_fee_per_gas(&self) -> u128 {
178 self.max_fee_per_gas
179 }
180
181 #[inline]
182 fn max_priority_fee_per_gas(&self) -> Option<u128> {
183 Some(self.max_priority_fee_per_gas)
184 }
185
186 #[inline]
187 fn max_fee_per_blob_gas(&self) -> Option<u128> {
188 None
189 }
190
191 #[inline]
192 fn priority_fee_or_price(&self) -> u128 {
193 self.max_priority_fee_per_gas
194 }
195
196 fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
197 base_fee.map_or(self.max_fee_per_gas, |base_fee| {
198 let tip = self.max_fee_per_gas.saturating_sub(base_fee as u128);
201 if tip > self.max_priority_fee_per_gas {
202 self.max_priority_fee_per_gas + base_fee as u128
203 } else {
204 self.max_fee_per_gas
206 }
207 })
208 }
209
210 #[inline]
211 fn is_dynamic_fee(&self) -> bool {
212 true
213 }
214
215 #[inline]
216 fn kind(&self) -> TxKind {
217 self.to.into()
218 }
219
220 #[inline]
221 fn is_create(&self) -> bool {
222 false
223 }
224
225 #[inline]
226 fn value(&self) -> U256 {
227 self.value
228 }
229
230 #[inline]
231 fn input(&self) -> &Bytes {
232 &self.input
233 }
234
235 #[inline]
236 fn access_list(&self) -> Option<&AccessList> {
237 Some(&self.access_list)
238 }
239
240 #[inline]
241 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
242 None
243 }
244
245 #[inline]
246 fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
247 Some(&self.authorization_list)
248 }
249}
250
251impl SignableTransaction<Signature> for TxEip7702 {
252 fn set_chain_id(&mut self, chain_id: ChainId) {
253 self.chain_id = chain_id;
254 }
255
256 fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut) {
257 out.put_u8(EIP7702_TX_TYPE_ID);
258 self.encode(out)
259 }
260
261 fn payload_len_for_signature(&self) -> usize {
262 self.length() + 1
263 }
264}
265
266impl Typed2718 for TxEip7702 {
267 fn ty(&self) -> u8 {
268 TxType::Eip7702 as u8
269 }
270}
271
272impl IsTyped2718 for TxEip7702 {
273 fn is_type(type_id: u8) -> bool {
274 matches!(type_id, 0x04)
275 }
276}
277
278impl Encodable for TxEip7702 {
279 fn encode(&self, out: &mut dyn BufMut) {
280 self.rlp_encode(out);
281 }
282
283 fn length(&self) -> usize {
284 self.rlp_encoded_length()
285 }
286}
287
288impl Decodable for TxEip7702 {
289 fn decode(data: &mut &[u8]) -> alloy_rlp::Result<Self> {
290 Self::rlp_decode(data)
291 }
292}
293
294#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
296pub(super) mod serde_bincode_compat {
297 use alloc::{borrow::Cow, vec::Vec};
298 use alloy_eips::{eip2930::AccessList, eip7702::serde_bincode_compat::SignedAuthorization};
299 use alloy_primitives::{Address, Bytes, ChainId, U256};
300 use serde::{Deserialize, Deserializer, Serialize, Serializer};
301 use serde_with::{DeserializeAs, SerializeAs};
302
303 #[derive(Debug, Serialize, Deserialize)]
319 pub struct TxEip7702<'a> {
320 chain_id: ChainId,
321 nonce: u64,
322 gas_limit: u64,
323 max_fee_per_gas: u128,
324 max_priority_fee_per_gas: u128,
325 to: Address,
326 value: U256,
327 access_list: Cow<'a, AccessList>,
328 authorization_list: Vec<SignedAuthorization<'a>>,
329 input: Cow<'a, Bytes>,
330 }
331
332 impl<'a> From<&'a super::TxEip7702> for TxEip7702<'a> {
333 fn from(value: &'a super::TxEip7702) -> Self {
334 Self {
335 chain_id: value.chain_id,
336 nonce: value.nonce,
337 gas_limit: value.gas_limit,
338 max_fee_per_gas: value.max_fee_per_gas,
339 max_priority_fee_per_gas: value.max_priority_fee_per_gas,
340 to: value.to,
341 value: value.value,
342 access_list: Cow::Borrowed(&value.access_list),
343 authorization_list: value.authorization_list.iter().map(Into::into).collect(),
344 input: Cow::Borrowed(&value.input),
345 }
346 }
347 }
348
349 impl<'a> From<TxEip7702<'a>> for super::TxEip7702 {
350 fn from(value: TxEip7702<'a>) -> Self {
351 Self {
352 chain_id: value.chain_id,
353 nonce: value.nonce,
354 gas_limit: value.gas_limit,
355 max_fee_per_gas: value.max_fee_per_gas,
356 max_priority_fee_per_gas: value.max_priority_fee_per_gas,
357 to: value.to,
358 value: value.value,
359 access_list: value.access_list.into_owned(),
360 authorization_list: value.authorization_list.into_iter().map(Into::into).collect(),
361 input: value.input.into_owned(),
362 }
363 }
364 }
365
366 impl SerializeAs<super::TxEip7702> for TxEip7702<'_> {
367 fn serialize_as<S>(source: &super::TxEip7702, serializer: S) -> Result<S::Ok, S::Error>
368 where
369 S: Serializer,
370 {
371 TxEip7702::from(source).serialize(serializer)
372 }
373 }
374
375 impl<'de> DeserializeAs<'de, super::TxEip7702> for TxEip7702<'de> {
376 fn deserialize_as<D>(deserializer: D) -> Result<super::TxEip7702, D::Error>
377 where
378 D: Deserializer<'de>,
379 {
380 TxEip7702::deserialize(deserializer).map(Into::into)
381 }
382 }
383
384 #[cfg(test)]
385 mod tests {
386 use arbitrary::Arbitrary;
387 use bincode::config;
388 use rand::Rng;
389 use serde::{Deserialize, Serialize};
390 use serde_with::serde_as;
391
392 use super::super::{serde_bincode_compat, TxEip7702};
393
394 #[test]
395 fn test_tx_eip7702_bincode_roundtrip() {
396 #[serde_as]
397 #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
398 struct Data {
399 #[serde_as(as = "serde_bincode_compat::TxEip7702")]
400 transaction: TxEip7702,
401 }
402
403 let mut bytes = [0u8; 1024];
404 rand::thread_rng().fill(bytes.as_mut_slice());
405 let data = Data {
406 transaction: TxEip7702::arbitrary(&mut arbitrary::Unstructured::new(&bytes))
407 .unwrap(),
408 };
409
410 let encoded = bincode::serde::encode_to_vec(&data, config::legacy()).unwrap();
411 let (decoded, _) =
412 bincode::serde::decode_from_slice::<Data, _>(&encoded, config::legacy()).unwrap();
413 assert_eq!(decoded, data);
414 }
415 }
416}
417
418#[cfg(all(test, feature = "k256"))]
419mod tests {
420 use super::*;
421 use crate::SignableTransaction;
422 use alloy_eips::eip2930::AccessList;
423 use alloy_primitives::{address, b256, hex, Address, Signature, U256};
424
425 #[test]
426 fn encode_decode_eip7702() {
427 let tx = TxEip7702 {
428 chain_id: 1,
429 nonce: 0x42,
430 gas_limit: 44386,
431 to: address!("6069a6c32cf691f5982febae4faf8a6f3ab2f0f6"),
432 value: U256::from(0_u64),
433 input: hex!("a22cb4650000000000000000000000005eee75727d804a2b13038928d36f8b188945a57a0000000000000000000000000000000000000000000000000000000000000000").into(),
434 max_fee_per_gas: 0x4a817c800,
435 max_priority_fee_per_gas: 0x3b9aca00,
436 access_list: AccessList::default(),
437 authorization_list: vec![],
438 };
439
440 let sig = Signature::from_scalars_and_parity(
441 b256!("840cfc572845f5786e702984c2a582528cad4b49b2a10b9db1be7fca90058565"),
442 b256!("25e7109ceb98168d95b09b18bbf6b685130e0562f233877d492b94eee0c5b6d1"),
443 false,
444 );
445
446 let mut buf = vec![];
447 tx.rlp_encode_signed(&sig, &mut buf);
448 let decoded = TxEip7702::rlp_decode_signed(&mut &buf[..]).unwrap();
449 assert_eq!(decoded, tx.into_signed(sig));
450 }
451
452 #[test]
453 fn test_decode_create() {
454 let tx = TxEip7702 {
456 chain_id: 1u64,
457 nonce: 0,
458 max_fee_per_gas: 0x4a817c800,
459 max_priority_fee_per_gas: 0x3b9aca00,
460 gas_limit: 2,
461 to: Address::default(),
462 value: U256::ZERO,
463 input: vec![1, 2].into(),
464 access_list: Default::default(),
465 authorization_list: Default::default(),
466 };
467 let sig = Signature::from_scalars_and_parity(
468 b256!("840cfc572845f5786e702984c2a582528cad4b49b2a10b9db1be7fca90058565"),
469 b256!("25e7109ceb98168d95b09b18bbf6b685130e0562f233877d492b94eee0c5b6d1"),
470 false,
471 );
472 let mut buf = vec![];
473 tx.rlp_encode_signed(&sig, &mut buf);
474 let decoded = TxEip7702::rlp_decode_signed(&mut &buf[..]).unwrap();
475 assert_eq!(decoded, tx.into_signed(sig));
476 }
477
478 #[test]
479 fn test_decode_call() {
480 let tx = TxEip7702 {
481 chain_id: 1u64,
482 nonce: 0,
483 max_fee_per_gas: 0x4a817c800,
484 max_priority_fee_per_gas: 0x3b9aca00,
485 gas_limit: 2,
486 to: Address::default(),
487 value: U256::ZERO,
488 input: vec![1, 2].into(),
489 access_list: Default::default(),
490 authorization_list: Default::default(),
491 };
492
493 let sig = Signature::from_scalars_and_parity(
494 b256!("840cfc572845f5786e702984c2a582528cad4b49b2a10b9db1be7fca90058565"),
495 b256!("25e7109ceb98168d95b09b18bbf6b685130e0562f233877d492b94eee0c5b6d1"),
496 false,
497 );
498
499 let mut buf = vec![];
500 tx.rlp_encode_signed(&sig, &mut buf);
501 let decoded = TxEip7702::rlp_decode_signed(&mut &buf[..]).unwrap();
502 assert_eq!(decoded, tx.into_signed(sig));
503 }
504}