alloy_consensus/transaction/
eip1559.rs1use crate::{SignableTransaction, Transaction, TxType};
2use alloy_eips::{eip2930::AccessList, eip7702::SignedAuthorization, Typed2718};
3use alloy_primitives::{Bytes, ChainId, PrimitiveSignature as Signature, TxKind, B256, U256};
4use alloy_rlp::{BufMut, Decodable, Encodable};
5use core::mem;
6
7use super::{RlpEcdsaDecodableTx, RlpEcdsaEncodableTx};
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#[doc(alias = "Eip1559Transaction", alias = "TransactionEip1559", alias = "Eip1559Tx")]
15pub struct TxEip1559 {
16 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
18 pub chain_id: ChainId,
19 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
21 pub nonce: u64,
22 #[cfg_attr(
28 feature = "serde",
29 serde(with = "alloy_serde::quantity", rename = "gas", alias = "gasLimit")
30 )]
31 pub gas_limit: u64,
32 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
44 pub max_fee_per_gas: u128,
45 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
53 pub max_priority_fee_per_gas: u128,
54 #[cfg_attr(feature = "serde", serde(default))]
57 pub to: TxKind,
58 pub value: U256,
63 pub access_list: AccessList,
69 pub input: Bytes,
75}
76
77impl TxEip1559 {
78 #[doc(alias = "transaction_type")]
80 pub const fn tx_type() -> TxType {
81 TxType::Eip1559
82 }
83
84 #[inline]
87 pub fn size(&self) -> usize {
88 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() }
98}
99
100impl RlpEcdsaEncodableTx for TxEip1559 {
101 const DEFAULT_TX_TYPE: u8 = { Self::tx_type() as u8 };
102
103 fn rlp_encoded_fields_length(&self) -> usize {
105 self.chain_id.length()
106 + self.nonce.length()
107 + self.max_priority_fee_per_gas.length()
108 + self.max_fee_per_gas.length()
109 + self.gas_limit.length()
110 + self.to.length()
111 + self.value.length()
112 + self.input.0.length()
113 + self.access_list.length()
114 }
115
116 fn rlp_encode_fields(&self, out: &mut dyn alloy_rlp::BufMut) {
119 self.chain_id.encode(out);
120 self.nonce.encode(out);
121 self.max_priority_fee_per_gas.encode(out);
122 self.max_fee_per_gas.encode(out);
123 self.gas_limit.encode(out);
124 self.to.encode(out);
125 self.value.encode(out);
126 self.input.0.encode(out);
127 self.access_list.encode(out);
128 }
129}
130
131impl RlpEcdsaDecodableTx for TxEip1559 {
132 fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
147 Ok(Self {
148 chain_id: Decodable::decode(buf)?,
149 nonce: Decodable::decode(buf)?,
150 max_priority_fee_per_gas: Decodable::decode(buf)?,
151 max_fee_per_gas: Decodable::decode(buf)?,
152 gas_limit: Decodable::decode(buf)?,
153 to: Decodable::decode(buf)?,
154 value: Decodable::decode(buf)?,
155 input: Decodable::decode(buf)?,
156 access_list: Decodable::decode(buf)?,
157 })
158 }
159}
160
161impl Transaction for TxEip1559 {
162 #[inline]
163 fn chain_id(&self) -> Option<ChainId> {
164 Some(self.chain_id)
165 }
166
167 #[inline]
168 fn nonce(&self) -> u64 {
169 self.nonce
170 }
171
172 #[inline]
173 fn gas_limit(&self) -> u64 {
174 self.gas_limit
175 }
176
177 #[inline]
178 fn gas_price(&self) -> Option<u128> {
179 None
180 }
181
182 #[inline]
183 fn max_fee_per_gas(&self) -> u128 {
184 self.max_fee_per_gas
185 }
186
187 #[inline]
188 fn max_priority_fee_per_gas(&self) -> Option<u128> {
189 Some(self.max_priority_fee_per_gas)
190 }
191
192 #[inline]
193 fn max_fee_per_blob_gas(&self) -> Option<u128> {
194 None
195 }
196
197 #[inline]
198 fn priority_fee_or_price(&self) -> u128 {
199 self.max_priority_fee_per_gas
200 }
201
202 fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
203 base_fee.map_or(self.max_fee_per_gas, |base_fee| {
204 let tip = self.max_fee_per_gas.saturating_sub(base_fee as u128);
207 if tip > self.max_priority_fee_per_gas {
208 self.max_priority_fee_per_gas + base_fee as u128
209 } else {
210 self.max_fee_per_gas
212 }
213 })
214 }
215
216 #[inline]
217 fn is_dynamic_fee(&self) -> bool {
218 true
219 }
220
221 #[inline]
222 fn kind(&self) -> TxKind {
223 self.to
224 }
225
226 #[inline]
227 fn is_create(&self) -> bool {
228 self.to.is_create()
229 }
230
231 #[inline]
232 fn value(&self) -> U256 {
233 self.value
234 }
235
236 #[inline]
237 fn input(&self) -> &Bytes {
238 &self.input
239 }
240
241 #[inline]
242 fn access_list(&self) -> Option<&AccessList> {
243 Some(&self.access_list)
244 }
245
246 #[inline]
247 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
248 None
249 }
250
251 #[inline]
252 fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
253 None
254 }
255}
256
257impl Typed2718 for TxEip1559 {
258 fn ty(&self) -> u8 {
259 TxType::Eip1559 as u8
260 }
261}
262
263impl SignableTransaction<Signature> for TxEip1559 {
264 fn set_chain_id(&mut self, chain_id: ChainId) {
265 self.chain_id = chain_id;
266 }
267
268 fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut) {
269 out.put_u8(Self::tx_type() as u8);
270 self.encode(out)
271 }
272
273 fn payload_len_for_signature(&self) -> usize {
274 self.length() + 1
275 }
276}
277
278impl Encodable for TxEip1559 {
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 TxEip1559 {
289 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
290 Self::rlp_decode(buf)
291 }
292}
293
294#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
296pub(super) mod serde_bincode_compat {
297 use alloc::borrow::Cow;
298 use alloy_eips::eip2930::AccessList;
299 use alloy_primitives::{Bytes, ChainId, TxKind, U256};
300 use serde::{Deserialize, Deserializer, Serialize, Serializer};
301 use serde_with::{DeserializeAs, SerializeAs};
302
303 #[derive(Debug, Serialize, Deserialize)]
319 pub struct TxEip1559<'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 #[serde(default)]
326 to: TxKind,
327 value: U256,
328 access_list: Cow<'a, AccessList>,
329 input: Cow<'a, Bytes>,
330 }
331
332 impl<'a> From<&'a super::TxEip1559> for TxEip1559<'a> {
333 fn from(value: &'a super::TxEip1559) -> 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 input: Cow::Borrowed(&value.input),
344 }
345 }
346 }
347
348 impl<'a> From<TxEip1559<'a>> for super::TxEip1559 {
349 fn from(value: TxEip1559<'a>) -> Self {
350 Self {
351 chain_id: value.chain_id,
352 nonce: value.nonce,
353 gas_limit: value.gas_limit,
354 max_fee_per_gas: value.max_fee_per_gas,
355 max_priority_fee_per_gas: value.max_priority_fee_per_gas,
356 to: value.to,
357 value: value.value,
358 access_list: value.access_list.into_owned(),
359 input: value.input.into_owned(),
360 }
361 }
362 }
363
364 impl SerializeAs<super::TxEip1559> for TxEip1559<'_> {
365 fn serialize_as<S>(source: &super::TxEip1559, serializer: S) -> Result<S::Ok, S::Error>
366 where
367 S: Serializer,
368 {
369 TxEip1559::from(source).serialize(serializer)
370 }
371 }
372
373 impl<'de> DeserializeAs<'de, super::TxEip1559> for TxEip1559<'de> {
374 fn deserialize_as<D>(deserializer: D) -> Result<super::TxEip1559, D::Error>
375 where
376 D: Deserializer<'de>,
377 {
378 TxEip1559::deserialize(deserializer).map(Into::into)
379 }
380 }
381
382 #[cfg(test)]
383 mod tests {
384 use arbitrary::Arbitrary;
385 use rand::Rng;
386 use serde::{Deserialize, Serialize};
387 use serde_with::serde_as;
388
389 use super::super::{serde_bincode_compat, TxEip1559};
390
391 #[test]
392 fn test_tx_eip1559_bincode_roundtrip() {
393 #[serde_as]
394 #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
395 struct Data {
396 #[serde_as(as = "serde_bincode_compat::TxEip1559")]
397 transaction: TxEip1559,
398 }
399
400 let mut bytes = [0u8; 1024];
401 rand::thread_rng().fill(bytes.as_mut_slice());
402 let data = Data {
403 transaction: TxEip1559::arbitrary(&mut arbitrary::Unstructured::new(&bytes))
404 .unwrap(),
405 };
406
407 let encoded = bincode::serialize(&data).unwrap();
408 let decoded: Data = bincode::deserialize(&encoded).unwrap();
409 assert_eq!(decoded, data);
410 }
411 }
412}
413
414#[cfg(all(test, feature = "k256"))]
415mod tests {
416 use super::TxEip1559;
417 use crate::{
418 transaction::{RlpEcdsaDecodableTx, RlpEcdsaEncodableTx},
419 SignableTransaction,
420 };
421 use alloy_eips::eip2930::AccessList;
422 use alloy_primitives::{
423 address, b256, hex, Address, PrimitiveSignature as Signature, B256, U256,
424 };
425
426 #[test]
427 fn recover_signer_eip1559() {
428 let signer: Address = address!("dd6b8b3dc6b7ad97db52f08a275ff4483e024cea");
429 let hash: B256 = b256!("0ec0b6a2df4d87424e5f6ad2a654e27aaeb7dac20ae9e8385cc09087ad532ee0");
430
431 let tx = TxEip1559 {
432 chain_id: 1,
433 nonce: 0x42,
434 gas_limit: 44386,
435 to: address!("6069a6c32cf691f5982febae4faf8a6f3ab2f0f6").into(),
436 value: U256::from(0_u64),
437 input: hex!("a22cb4650000000000000000000000005eee75727d804a2b13038928d36f8b188945a57a0000000000000000000000000000000000000000000000000000000000000000").into(),
438 max_fee_per_gas: 0x4a817c800,
439 max_priority_fee_per_gas: 0x3b9aca00,
440 access_list: AccessList::default(),
441 };
442
443 let sig = Signature::from_scalars_and_parity(
444 b256!("840cfc572845f5786e702984c2a582528cad4b49b2a10b9db1be7fca90058565"),
445 b256!("25e7109ceb98168d95b09b18bbf6b685130e0562f233877d492b94eee0c5b6d1"),
446 false,
447 );
448
449 assert_eq!(
450 tx.signature_hash(),
451 hex!("0d5688ac3897124635b6cf1bc0e29d6dfebceebdc10a54d74f2ef8b56535b682")
452 );
453
454 let signed_tx = tx.into_signed(sig);
455 assert_eq!(*signed_tx.hash(), hash, "Expected same hash");
456 assert_eq!(signed_tx.recover_signer().unwrap(), signer, "Recovering signer should pass.");
457 }
458
459 #[test]
460 fn encode_decode_eip1559() {
461 let hash: B256 = b256!("0ec0b6a2df4d87424e5f6ad2a654e27aaeb7dac20ae9e8385cc09087ad532ee0");
462
463 let tx = TxEip1559 {
464 chain_id: 1,
465 nonce: 0x42,
466 gas_limit: 44386,
467 to: address!("6069a6c32cf691f5982febae4faf8a6f3ab2f0f6").into(),
468 value: U256::from(0_u64),
469 input: hex!("a22cb4650000000000000000000000005eee75727d804a2b13038928d36f8b188945a57a0000000000000000000000000000000000000000000000000000000000000000").into(),
470 max_fee_per_gas: 0x4a817c800,
471 max_priority_fee_per_gas: 0x3b9aca00,
472 access_list: AccessList::default(),
473 };
474
475 let sig = Signature::from_scalars_and_parity(
476 b256!("840cfc572845f5786e702984c2a582528cad4b49b2a10b9db1be7fca90058565"),
477 b256!("25e7109ceb98168d95b09b18bbf6b685130e0562f233877d492b94eee0c5b6d1"),
478 false,
479 );
480
481 let mut buf = vec![];
482 tx.rlp_encode_signed(&sig, &mut buf);
483 let decoded = TxEip1559::rlp_decode_signed(&mut &buf[..]).unwrap();
484 assert_eq!(decoded, tx.into_signed(sig));
485 assert_eq!(*decoded.hash(), hash);
486 }
487}