alloy_consensus/transaction/
eip1559.rs1use super::{RlpEcdsaDecodableTx, RlpEcdsaEncodableTx};
2use crate::{SignableTransaction, Transaction, TxType};
3use alloy_eips::{
4 eip2718::IsTyped2718, eip2930::AccessList, eip7702::SignedAuthorization, Typed2718,
5};
6use alloy_primitives::{Bytes, ChainId, Signature, TxKind, B256, U256};
7use alloy_rlp::{BufMut, Decodable, Encodable};
8
9#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
11#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
12#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
13#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
14#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
15#[doc(alias = "Eip1559Transaction", alias = "TransactionEip1559", alias = "Eip1559Tx")]
16pub struct TxEip1559 {
17 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
19 pub chain_id: ChainId,
20 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
22 pub nonce: u64,
23 #[cfg_attr(
29 feature = "serde",
30 serde(with = "alloy_serde::quantity", rename = "gas", alias = "gasLimit")
31 )]
32 pub gas_limit: u64,
33 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
43 pub max_fee_per_gas: u128,
44 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
52 pub max_priority_fee_per_gas: u128,
53 #[cfg_attr(feature = "serde", serde(default))]
56 pub to: TxKind,
57 pub value: U256,
62 #[cfg_attr(feature = "serde", serde(deserialize_with = "alloy_serde::null_as_default"))]
72 pub access_list: AccessList,
73 pub input: Bytes,
79}
80
81impl TxEip1559 {
82 #[doc(alias = "transaction_type")]
84 pub const fn tx_type() -> TxType {
85 TxType::Eip1559
86 }
87
88 #[inline]
91 pub fn size(&self) -> usize {
92 size_of::<Self>() + self.access_list.size() + self.input.len()
93 }
94}
95
96impl RlpEcdsaEncodableTx for TxEip1559 {
97 fn rlp_encoded_fields_length(&self) -> usize {
99 self.chain_id.length()
100 + self.nonce.length()
101 + self.max_priority_fee_per_gas.length()
102 + self.max_fee_per_gas.length()
103 + self.gas_limit.length()
104 + self.to.length()
105 + self.value.length()
106 + self.input.0.length()
107 + self.access_list.length()
108 }
109
110 fn rlp_encode_fields(&self, out: &mut dyn alloy_rlp::BufMut) {
113 self.chain_id.encode(out);
114 self.nonce.encode(out);
115 self.max_priority_fee_per_gas.encode(out);
116 self.max_fee_per_gas.encode(out);
117 self.gas_limit.encode(out);
118 self.to.encode(out);
119 self.value.encode(out);
120 self.input.0.encode(out);
121 self.access_list.encode(out);
122 }
123}
124
125impl RlpEcdsaDecodableTx for TxEip1559 {
126 const DEFAULT_TX_TYPE: u8 = { Self::tx_type() as u8 };
127
128 fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
143 Ok(Self {
144 chain_id: Decodable::decode(buf)?,
145 nonce: Decodable::decode(buf)?,
146 max_priority_fee_per_gas: Decodable::decode(buf)?,
147 max_fee_per_gas: Decodable::decode(buf)?,
148 gas_limit: Decodable::decode(buf)?,
149 to: Decodable::decode(buf)?,
150 value: Decodable::decode(buf)?,
151 input: Decodable::decode(buf)?,
152 access_list: Decodable::decode(buf)?,
153 })
154 }
155}
156
157impl Transaction for TxEip1559 {
158 #[inline]
159 fn chain_id(&self) -> Option<ChainId> {
160 Some(self.chain_id)
161 }
162
163 #[inline]
164 fn nonce(&self) -> u64 {
165 self.nonce
166 }
167
168 #[inline]
169 fn gas_limit(&self) -> u64 {
170 self.gas_limit
171 }
172
173 #[inline]
174 fn gas_price(&self) -> Option<u128> {
175 None
176 }
177
178 #[inline]
179 fn max_fee_per_gas(&self) -> u128 {
180 self.max_fee_per_gas
181 }
182
183 #[inline]
184 fn max_priority_fee_per_gas(&self) -> Option<u128> {
185 Some(self.max_priority_fee_per_gas)
186 }
187
188 #[inline]
189 fn max_fee_per_blob_gas(&self) -> Option<u128> {
190 None
191 }
192
193 #[inline]
194 fn priority_fee_or_price(&self) -> u128 {
195 self.max_priority_fee_per_gas
196 }
197
198 fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
199 alloy_eips::eip1559::calc_effective_gas_price(
200 self.max_fee_per_gas,
201 self.max_priority_fee_per_gas,
202 base_fee,
203 )
204 }
205
206 #[inline]
207 fn is_dynamic_fee(&self) -> bool {
208 true
209 }
210
211 #[inline]
212 fn kind(&self) -> TxKind {
213 self.to
214 }
215
216 #[inline]
217 fn is_create(&self) -> bool {
218 self.to.is_create()
219 }
220
221 #[inline]
222 fn value(&self) -> U256 {
223 self.value
224 }
225
226 #[inline]
227 fn input(&self) -> &Bytes {
228 &self.input
229 }
230
231 #[inline]
232 fn access_list(&self) -> Option<&AccessList> {
233 Some(&self.access_list)
234 }
235
236 #[inline]
237 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
238 None
239 }
240
241 #[inline]
242 fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
243 None
244 }
245}
246
247impl Typed2718 for TxEip1559 {
248 fn ty(&self) -> u8 {
249 TxType::Eip1559 as u8
250 }
251}
252
253impl IsTyped2718 for TxEip1559 {
254 fn is_type(type_id: u8) -> bool {
255 matches!(type_id, 0x02)
256 }
257}
258
259impl SignableTransaction<Signature> for TxEip1559 {
260 fn set_chain_id(&mut self, chain_id: ChainId) {
261 self.chain_id = chain_id;
262 }
263
264 fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut) {
265 out.put_u8(Self::tx_type() as u8);
266 self.encode(out)
267 }
268
269 fn payload_len_for_signature(&self) -> usize {
270 self.length() + 1
271 }
272}
273
274impl Encodable for TxEip1559 {
275 fn encode(&self, out: &mut dyn BufMut) {
276 self.rlp_encode(out);
277 }
278
279 fn length(&self) -> usize {
280 self.rlp_encoded_length()
281 }
282}
283
284impl Decodable for TxEip1559 {
285 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
286 Self::rlp_decode(buf)
287 }
288}
289
290#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
292pub(super) mod serde_bincode_compat {
293 use alloc::borrow::Cow;
294 use alloy_eips::eip2930::AccessList;
295 use alloy_primitives::{Bytes, ChainId, TxKind, U256};
296 use serde::{Deserialize, Deserializer, Serialize, Serializer};
297 use serde_with::{DeserializeAs, SerializeAs};
298
299 #[derive(Debug, Serialize, Deserialize)]
315 pub struct TxEip1559<'a> {
316 chain_id: ChainId,
317 nonce: u64,
318 gas_limit: u64,
319 max_fee_per_gas: u128,
320 max_priority_fee_per_gas: u128,
321 #[serde(default)]
322 to: TxKind,
323 value: U256,
324 access_list: Cow<'a, AccessList>,
325 input: Cow<'a, Bytes>,
326 }
327
328 impl<'a> From<&'a super::TxEip1559> for TxEip1559<'a> {
329 fn from(value: &'a super::TxEip1559) -> Self {
330 Self {
331 chain_id: value.chain_id,
332 nonce: value.nonce,
333 gas_limit: value.gas_limit,
334 max_fee_per_gas: value.max_fee_per_gas,
335 max_priority_fee_per_gas: value.max_priority_fee_per_gas,
336 to: value.to,
337 value: value.value,
338 access_list: Cow::Borrowed(&value.access_list),
339 input: Cow::Borrowed(&value.input),
340 }
341 }
342 }
343
344 impl<'a> From<TxEip1559<'a>> for super::TxEip1559 {
345 fn from(value: TxEip1559<'a>) -> Self {
346 Self {
347 chain_id: value.chain_id,
348 nonce: value.nonce,
349 gas_limit: value.gas_limit,
350 max_fee_per_gas: value.max_fee_per_gas,
351 max_priority_fee_per_gas: value.max_priority_fee_per_gas,
352 to: value.to,
353 value: value.value,
354 access_list: value.access_list.into_owned(),
355 input: value.input.into_owned(),
356 }
357 }
358 }
359
360 impl SerializeAs<super::TxEip1559> for TxEip1559<'_> {
361 fn serialize_as<S>(source: &super::TxEip1559, serializer: S) -> Result<S::Ok, S::Error>
362 where
363 S: Serializer,
364 {
365 TxEip1559::from(source).serialize(serializer)
366 }
367 }
368
369 impl<'de> DeserializeAs<'de, super::TxEip1559> for TxEip1559<'de> {
370 fn deserialize_as<D>(deserializer: D) -> Result<super::TxEip1559, D::Error>
371 where
372 D: Deserializer<'de>,
373 {
374 TxEip1559::deserialize(deserializer).map(Into::into)
375 }
376 }
377
378 #[cfg(test)]
379 mod tests {
380 use arbitrary::Arbitrary;
381 use bincode::config;
382 use rand::Rng;
383 use serde::{Deserialize, Serialize};
384 use serde_with::serde_as;
385
386 use super::super::{serde_bincode_compat, TxEip1559};
387
388 #[test]
389 fn test_tx_eip1559_bincode_roundtrip() {
390 #[serde_as]
391 #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
392 struct Data {
393 #[serde_as(as = "serde_bincode_compat::TxEip1559")]
394 transaction: TxEip1559,
395 }
396
397 let mut bytes = [0u8; 1024];
398 rand::thread_rng().fill(bytes.as_mut_slice());
399 let data = Data {
400 transaction: TxEip1559::arbitrary(&mut arbitrary::Unstructured::new(&bytes))
401 .unwrap(),
402 };
403
404 let encoded = bincode::serde::encode_to_vec(&data, config::legacy()).unwrap();
405 let (decoded, _) =
406 bincode::serde::decode_from_slice::<Data, _>(&encoded, config::legacy()).unwrap();
407 assert_eq!(decoded, data);
408 }
409 }
410}
411
412#[cfg(all(test, feature = "k256"))]
413mod tests {
414 use super::TxEip1559;
415 use crate::{
416 transaction::{RlpEcdsaDecodableTx, RlpEcdsaEncodableTx},
417 SignableTransaction,
418 };
419 use alloy_eips::eip2930::AccessList;
420 use alloy_primitives::{address, b256, hex, Address, Signature, B256, U256};
421
422 #[test]
423 fn recover_signer_eip1559() {
424 let signer: Address = address!("dd6b8b3dc6b7ad97db52f08a275ff4483e024cea");
425 let hash: B256 = b256!("0ec0b6a2df4d87424e5f6ad2a654e27aaeb7dac20ae9e8385cc09087ad532ee0");
426
427 let tx = TxEip1559 {
428 chain_id: 1,
429 nonce: 0x42,
430 gas_limit: 44386,
431 to: address!("6069a6c32cf691f5982febae4faf8a6f3ab2f0f6").into(),
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 };
438
439 let sig = Signature::from_scalars_and_parity(
440 b256!("840cfc572845f5786e702984c2a582528cad4b49b2a10b9db1be7fca90058565"),
441 b256!("25e7109ceb98168d95b09b18bbf6b685130e0562f233877d492b94eee0c5b6d1"),
442 false,
443 );
444
445 assert_eq!(
446 tx.signature_hash(),
447 hex!("0d5688ac3897124635b6cf1bc0e29d6dfebceebdc10a54d74f2ef8b56535b682")
448 );
449
450 let signed_tx = tx.into_signed(sig);
451 assert_eq!(*signed_tx.hash(), hash, "Expected same hash");
452 assert_eq!(signed_tx.recover_signer().unwrap(), signer, "Recovering signer should pass.");
453 }
454
455 #[test]
456 fn encode_decode_eip1559() {
457 let hash: B256 = b256!("0ec0b6a2df4d87424e5f6ad2a654e27aaeb7dac20ae9e8385cc09087ad532ee0");
458
459 let tx = TxEip1559 {
460 chain_id: 1,
461 nonce: 0x42,
462 gas_limit: 44386,
463 to: address!("6069a6c32cf691f5982febae4faf8a6f3ab2f0f6").into(),
464 value: U256::from(0_u64),
465 input: hex!("a22cb4650000000000000000000000005eee75727d804a2b13038928d36f8b188945a57a0000000000000000000000000000000000000000000000000000000000000000").into(),
466 max_fee_per_gas: 0x4a817c800,
467 max_priority_fee_per_gas: 0x3b9aca00,
468 access_list: AccessList::default(),
469 };
470
471 let sig = Signature::from_scalars_and_parity(
472 b256!("840cfc572845f5786e702984c2a582528cad4b49b2a10b9db1be7fca90058565"),
473 b256!("25e7109ceb98168d95b09b18bbf6b685130e0562f233877d492b94eee0c5b6d1"),
474 false,
475 );
476
477 let mut buf = vec![];
478 tx.rlp_encode_signed(&sig, &mut buf);
479 let decoded = TxEip1559::rlp_decode_signed(&mut &buf[..]).unwrap();
480 assert_eq!(decoded, tx.into_signed(sig));
481 assert_eq!(*decoded.hash(), hash);
482 }
483
484 #[test]
485 fn json_decode_eip1559_null_access_list() {
486 let hash: B256 = b256!("0ec0b6a2df4d87424e5f6ad2a654e27aaeb7dac20ae9e8385cc09087ad532ee0");
487
488 let tx_json = r#"
489 {
490 "chainId": "0x1",
491 "nonce": "0x42",
492 "gas": "0xad62",
493 "to": "0x6069a6c32cf691f5982febae4faf8a6f3ab2f0f6",
494 "value": "0x0",
495 "input": "0xa22cb4650000000000000000000000005eee75727d804a2b13038928d36f8b188945a57a0000000000000000000000000000000000000000000000000000000000000000",
496 "maxFeePerGas": "0x4a817c800",
497 "maxPriorityFeePerGas": "0x3b9aca00",
498 "accessList": null
499 }
500 "#;
501 let tx: TxEip1559 = serde_json::from_str(tx_json).unwrap();
503 assert_eq!(tx.access_list, AccessList::default());
504
505 let sig = Signature::from_scalars_and_parity(
506 b256!("840cfc572845f5786e702984c2a582528cad4b49b2a10b9db1be7fca90058565"),
507 b256!("25e7109ceb98168d95b09b18bbf6b685130e0562f233877d492b94eee0c5b6d1"),
508 false,
509 );
510
511 let mut buf = vec![];
512 tx.rlp_encode_signed(&sig, &mut buf);
513 let decoded = TxEip1559::rlp_decode_signed(&mut &buf[..]).unwrap();
514 assert_eq!(decoded, tx.into_signed(sig));
515 assert_eq!(*decoded.hash(), hash);
516 }
517}