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