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