referrerpolicy=no-referrer-when-downgrade

pallet_revive/evm/api/
transaction.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10//  http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18#![allow(missing_docs)]
19
20use super::{
21	Byte, Bytes, TYPE_EIP1559, TYPE_EIP2930, TYPE_EIP4844, TYPE_EIP7702, TYPE_LEGACY, TypeEip1559,
22	TypeEip2930, TypeEip4844, TypeEip7702, TypeLegacy,
23};
24use alloc::vec::Vec;
25use codec::{Decode, DecodeWithMemTracking, Encode};
26use derive_more::{From, TryInto};
27use ethereum_types::*;
28use scale_info::TypeInfo;
29use serde::{Deserialize, Deserializer, Serialize};
30
31/// Transaction object generic to all types
32#[derive(
33	Debug, Default, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, Eq, PartialEq,
34)]
35#[serde(rename_all = "camelCase")]
36pub struct GenericTransaction {
37	/// accessList
38	/// EIP-2930 access list
39	#[serde(skip_serializing_if = "Option::is_none")]
40	pub access_list: Option<AccessList>,
41	/// authorizationList
42	/// List of account code authorizations (EIP-7702)
43	#[serde(default, skip_serializing_if = "Vec::is_empty")]
44	pub authorization_list: Vec<AuthorizationListEntry>,
45	/// blobVersionedHashes
46	/// List of versioned blob hashes associated with the transaction's EIP-4844 data blobs.
47	#[serde(default)]
48	pub blob_versioned_hashes: Vec<H256>,
49	/// blobs
50	/// Raw blob data.
51	#[serde(default, skip_serializing_if = "Vec::is_empty")]
52	pub blobs: Vec<Bytes>,
53	/// chainId
54	/// Chain ID that this transaction is valid on.
55	#[serde(skip_serializing_if = "Option::is_none")]
56	pub chain_id: Option<U256>,
57	/// from address
58	#[serde(skip_serializing_if = "Option::is_none")]
59	pub from: Option<Address>,
60	/// gas limit
61	#[serde(skip_serializing_if = "Option::is_none")]
62	pub gas: Option<U256>,
63	/// gas price
64	/// The gas price willing to be paid by the sender in wei
65	#[serde(skip_serializing_if = "Option::is_none")]
66	pub gas_price: Option<U256>,
67	/// input data
68	#[serde(flatten, deserialize_with = "deserialize_input_or_data")]
69	pub input: InputOrData,
70	/// max fee per blob gas
71	/// The maximum total fee per gas the sender is willing to pay for blob gas in wei
72	#[serde(skip_serializing_if = "Option::is_none")]
73	pub max_fee_per_blob_gas: Option<U256>,
74	/// max fee per gas
75	/// The maximum total fee per gas the sender is willing to pay (includes the network / base fee
76	/// and miner / priority fee) in wei
77	#[serde(skip_serializing_if = "Option::is_none")]
78	pub max_fee_per_gas: Option<U256>,
79	/// max priority fee per gas
80	/// Maximum fee per gas the sender is willing to pay to miners in wei
81	#[serde(skip_serializing_if = "Option::is_none")]
82	pub max_priority_fee_per_gas: Option<U256>,
83	/// nonce
84	#[serde(skip_serializing_if = "Option::is_none")]
85	pub nonce: Option<U256>,
86	/// to address
87	pub to: Option<Address>,
88	/// type
89	#[serde(skip_serializing_if = "Option::is_none")]
90	pub r#type: Option<Byte>,
91	/// value
92	#[serde(skip_serializing_if = "Option::is_none")]
93	pub value: Option<U256>,
94}
95
96impl GenericTransaction {
97	/// Create a new [`GenericTransaction`] from a signed transaction.
98	pub fn from_signed(tx: TransactionSigned, base_gas_price: U256, from: Option<H160>) -> Self {
99		Self::from_unsigned(tx.into(), base_gas_price, from)
100	}
101
102	/// Returns `true` when the transaction's payload fields look like those of a simple value
103	/// transfer: empty calldata, no access list, no EIP-7702 authorization list, no EIP-4844 blob
104	/// payload, and no blob gas fee. The destination address is validated separately by the caller.
105	pub fn has_simple_transfer_fields(&self) -> bool {
106		self.input.is_empty() &&
107			self.access_list.as_ref().is_none_or(|list| list.is_empty()) &&
108			self.authorization_list.is_empty() &&
109			self.blob_versioned_hashes.is_empty() &&
110			self.blobs.is_empty() &&
111			self.max_fee_per_blob_gas.is_none()
112	}
113
114	/// The gas price that is actually paid (including priority fee).
115	pub fn effective_gas_price(&self, base_gas_price: U256) -> Option<U256> {
116		let effective_gas_price = if let Some(prio_price) = self.max_priority_fee_per_gas {
117			let max_price = self.max_fee_per_gas?;
118			Some(max_price.min(base_gas_price.saturating_add(prio_price)))
119		} else {
120			self.gas_price
121		};
122
123		// we do not implement priority fee as it does not map to tip well
124		// hence the effective gas price cannot be higher than the base price
125		effective_gas_price.map(|e| e.min(base_gas_price))
126	}
127
128	/// Create a new [`GenericTransaction`] from a unsigned transaction.
129	pub fn from_unsigned(
130		tx: TransactionUnsigned,
131		base_gas_price: U256,
132		from: Option<H160>,
133	) -> Self {
134		use TransactionUnsigned::*;
135		let mut tx = match tx {
136			TransactionLegacyUnsigned(tx) => GenericTransaction {
137				from,
138				r#type: Some(tx.r#type.as_byte()),
139				chain_id: tx.chain_id,
140				input: tx.input.into(),
141				nonce: Some(tx.nonce),
142				value: Some(tx.value),
143				to: tx.to,
144				gas: Some(tx.gas),
145				gas_price: Some(tx.gas_price),
146				..Default::default()
147			},
148			Transaction4844Unsigned(tx) => GenericTransaction {
149				from,
150				r#type: Some(tx.r#type.as_byte()),
151				chain_id: Some(tx.chain_id),
152				input: tx.input.into(),
153				nonce: Some(tx.nonce),
154				value: Some(tx.value),
155				to: Some(tx.to),
156				gas: Some(tx.gas),
157				access_list: Some(tx.access_list),
158				blob_versioned_hashes: tx.blob_versioned_hashes,
159				max_fee_per_blob_gas: Some(tx.max_fee_per_blob_gas),
160				max_fee_per_gas: Some(tx.max_fee_per_gas),
161				max_priority_fee_per_gas: Some(tx.max_priority_fee_per_gas),
162				..Default::default()
163			},
164			Transaction1559Unsigned(tx) => GenericTransaction {
165				from,
166				r#type: Some(tx.r#type.as_byte()),
167				chain_id: Some(tx.chain_id),
168				input: tx.input.into(),
169				nonce: Some(tx.nonce),
170				value: Some(tx.value),
171				to: tx.to,
172				gas: Some(tx.gas),
173				access_list: Some(tx.access_list),
174				max_fee_per_gas: Some(tx.max_fee_per_gas),
175				max_priority_fee_per_gas: Some(tx.max_priority_fee_per_gas),
176				..Default::default()
177			},
178			Transaction2930Unsigned(tx) => GenericTransaction {
179				from,
180				r#type: Some(tx.r#type.as_byte()),
181				chain_id: Some(tx.chain_id),
182				input: tx.input.into(),
183				nonce: Some(tx.nonce),
184				value: Some(tx.value),
185				to: tx.to,
186				gas: Some(tx.gas),
187				gas_price: Some(tx.gas_price),
188				access_list: Some(tx.access_list),
189				..Default::default()
190			},
191			Transaction7702Unsigned(tx) => GenericTransaction {
192				from,
193				r#type: Some(tx.r#type.as_byte()),
194				chain_id: Some(tx.chain_id),
195				input: tx.input.into(),
196				nonce: Some(tx.nonce),
197				value: Some(tx.value),
198				to: Some(tx.to),
199				gas: Some(tx.gas),
200				access_list: Some(tx.access_list),
201				authorization_list: tx.authorization_list,
202				max_fee_per_gas: Some(tx.max_fee_per_gas),
203				max_priority_fee_per_gas: Some(tx.max_priority_fee_per_gas),
204				..Default::default()
205			},
206		};
207		tx.gas_price = tx.effective_gas_price(base_gas_price);
208		tx
209	}
210
211	/// Convert to a [`TransactionUnsigned`].
212	pub fn try_into_unsigned(self) -> Result<TransactionUnsigned, ()> {
213		match self.r#type.unwrap_or_default().0 {
214			TYPE_LEGACY => Ok(TransactionLegacyUnsigned {
215				r#type: TypeLegacy {},
216				chain_id: self.chain_id,
217				input: self.input.to_bytes(),
218				nonce: self.nonce.unwrap_or_default(),
219				value: self.value.unwrap_or_default(),
220				to: self.to,
221				gas: self.gas.unwrap_or_default(),
222				gas_price: self.gas_price.unwrap_or_default(),
223			}
224			.into()),
225			TYPE_EIP1559 => Ok(Transaction1559Unsigned {
226				r#type: TypeEip1559 {},
227				chain_id: self.chain_id.unwrap_or_default(),
228				input: self.input.to_bytes(),
229				nonce: self.nonce.unwrap_or_default(),
230				value: self.value.unwrap_or_default(),
231				to: self.to,
232				gas: self.gas.unwrap_or_default(),
233				gas_price: self.max_fee_per_gas.unwrap_or_default(),
234				access_list: self.access_list.unwrap_or_default(),
235				max_fee_per_gas: self.max_fee_per_gas.unwrap_or_default(),
236				max_priority_fee_per_gas: self.max_priority_fee_per_gas.unwrap_or_default(),
237			}
238			.into()),
239			TYPE_EIP2930 => Ok(Transaction2930Unsigned {
240				r#type: TypeEip2930 {},
241				chain_id: self.chain_id.unwrap_or_default(),
242				input: self.input.to_bytes(),
243				nonce: self.nonce.unwrap_or_default(),
244				value: self.value.unwrap_or_default(),
245				to: self.to,
246				gas: self.gas.unwrap_or_default(),
247				gas_price: self.gas_price.unwrap_or_default(),
248				access_list: self.access_list.unwrap_or_default(),
249			}
250			.into()),
251			TYPE_EIP4844 => Ok(Transaction4844Unsigned {
252				r#type: TypeEip4844 {},
253				chain_id: self.chain_id.unwrap_or_default(),
254				input: self.input.to_bytes(),
255				nonce: self.nonce.unwrap_or_default(),
256				value: self.value.unwrap_or_default(),
257				to: self.to.unwrap_or_default(),
258				gas: self.gas.unwrap_or_default(),
259				max_fee_per_gas: self.max_fee_per_gas.unwrap_or_default(),
260				max_fee_per_blob_gas: self.max_fee_per_blob_gas.unwrap_or_default(),
261				max_priority_fee_per_gas: self.max_priority_fee_per_gas.unwrap_or_default(),
262				access_list: self.access_list.unwrap_or_default(),
263				blob_versioned_hashes: self.blob_versioned_hashes,
264			}
265			.into()),
266			TYPE_EIP7702 => Ok(Transaction7702Unsigned {
267				r#type: TypeEip7702 {},
268				chain_id: self.chain_id.unwrap_or_default(),
269				input: self.input.to_bytes(),
270				nonce: self.nonce.unwrap_or_default(),
271				value: self.value.unwrap_or_default(),
272				to: self.to.unwrap_or_default(),
273				gas: self.gas.unwrap_or_default(),
274				max_fee_per_gas: self.max_fee_per_gas.unwrap_or_default(),
275				max_priority_fee_per_gas: self.max_priority_fee_per_gas.unwrap_or_default(),
276				access_list: self.access_list.unwrap_or_default(),
277				authorization_list: self.authorization_list,
278			}
279			.into()),
280			_ => Err(()),
281		}
282	}
283}
284
285/// Transaction information
286#[derive(Debug, Default, Clone, Serialize, Eq, PartialEq, TypeInfo, Encode, Decode)]
287#[serde(rename_all = "camelCase")]
288pub struct TransactionInfo {
289	/// block hash
290	pub block_hash: H256,
291	/// block number
292	pub block_number: U256,
293	/// from address
294	pub from: Address,
295	/// transaction hash
296	pub hash: H256,
297	/// transaction index
298	pub transaction_index: U256,
299	#[serde(flatten)]
300	pub transaction_signed: TransactionSigned,
301}
302
303// Custom deserializer to work around serde's limitation with flatten + untagged enums from Value
304// See: https://github.com/serde-rs/serde/issues/1183
305impl<'de> Deserialize<'de> for TransactionInfo {
306	fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
307	where
308		D: serde::Deserializer<'de>,
309	{
310		use alloc::{collections::BTreeMap, string::String};
311		use serde::de::Error;
312
313		// First try deserializing to a map
314		let mut map = <BTreeMap<String, serde_json::Value>>::deserialize(deserializer)?;
315
316		// Extract the TransactionInfo-specific fields
317		let block_hash =
318			map.remove("blockHash").ok_or_else(|| D::Error::missing_field("blockHash"))?;
319		let block_number = map
320			.remove("blockNumber")
321			.ok_or_else(|| D::Error::missing_field("blockNumber"))?;
322		let from = map.remove("from").ok_or_else(|| D::Error::missing_field("from"))?;
323		let hash = map.remove("hash").ok_or_else(|| D::Error::missing_field("hash"))?;
324		let transaction_index = map
325			.remove("transactionIndex")
326			.ok_or_else(|| D::Error::missing_field("transactionIndex"))?;
327
328		// The remaining fields should be for TransactionSigned
329		// Convert back to JSON and deserialize
330		let remaining = serde_json::Value::Object(map.into_iter().collect());
331		let json_str = serde_json::to_string(&remaining).map_err(D::Error::custom)?;
332		let transaction_signed: TransactionSigned =
333			serde_json::from_str(&json_str).map_err(D::Error::custom)?;
334
335		Ok(Self {
336			block_hash: serde_json::from_value(block_hash).map_err(D::Error::custom)?,
337			block_number: serde_json::from_value(block_number).map_err(D::Error::custom)?,
338			from: serde_json::from_value(from).map_err(D::Error::custom)?,
339			hash: serde_json::from_value(hash).map_err(D::Error::custom)?,
340			transaction_index: serde_json::from_value(transaction_index)
341				.map_err(D::Error::custom)?,
342			transaction_signed,
343		})
344	}
345}
346
347#[derive(
348	Debug,
349	Clone,
350	Serialize,
351	Deserialize,
352	From,
353	TryInto,
354	Eq,
355	PartialEq,
356	TypeInfo,
357	Encode,
358	Decode,
359	DecodeWithMemTracking,
360)]
361#[serde(untagged)]
362pub enum TransactionSigned {
363	Transaction7702Signed(Transaction7702Signed),
364	Transaction4844Signed(Transaction4844Signed),
365	Transaction1559Signed(Transaction1559Signed),
366	Transaction2930Signed(Transaction2930Signed),
367	TransactionLegacySigned(TransactionLegacySigned),
368}
369
370impl Default for TransactionSigned {
371	fn default() -> Self {
372		TransactionSigned::TransactionLegacySigned(Default::default())
373	}
374}
375
376#[derive(Debug, Clone, Serialize, Deserialize, From, TryInto, Eq, PartialEq)]
377#[serde(untagged)]
378pub enum TransactionUnsigned {
379	Transaction7702Unsigned(Transaction7702Unsigned),
380	Transaction4844Unsigned(Transaction4844Unsigned),
381	Transaction1559Unsigned(Transaction1559Unsigned),
382	Transaction2930Unsigned(Transaction2930Unsigned),
383	TransactionLegacyUnsigned(TransactionLegacyUnsigned),
384}
385
386impl Default for TransactionUnsigned {
387	fn default() -> Self {
388		TransactionUnsigned::TransactionLegacyUnsigned(Default::default())
389	}
390}
391
392impl From<TransactionSigned> for TransactionUnsigned {
393	fn from(tx: TransactionSigned) -> Self {
394		use TransactionSigned::*;
395		match tx {
396			Transaction7702Signed(tx) => tx.transaction_7702_unsigned.into(),
397			Transaction4844Signed(tx) => tx.transaction_4844_unsigned.into(),
398			Transaction1559Signed(tx) => tx.transaction_1559_unsigned.into(),
399			Transaction2930Signed(tx) => tx.transaction_2930_unsigned.into(),
400			TransactionLegacySigned(tx) => tx.transaction_legacy_unsigned.into(),
401		}
402	}
403}
404
405/// EIP-1559 transaction.
406#[derive(
407	Debug,
408	Default,
409	Clone,
410	Serialize,
411	Deserialize,
412	Eq,
413	PartialEq,
414	TypeInfo,
415	Encode,
416	Decode,
417	DecodeWithMemTracking,
418)]
419#[serde(rename_all = "camelCase")]
420pub struct Transaction1559Unsigned {
421	/// accessList
422	/// EIP-2930 access list
423	pub access_list: AccessList,
424	/// chainId
425	/// Chain ID that this transaction is valid on.
426	pub chain_id: U256,
427	/// gas limit
428	pub gas: U256,
429	/// gas price
430	/// The effective gas price paid by the sender in wei. For transactions not yet included in a
431	/// block, this value should be set equal to the max fee per gas. This field is DEPRECATED,
432	/// please transition to using effectiveGasPrice in the receipt object going forward.
433	pub gas_price: U256,
434	/// input data
435	pub input: Bytes,
436	/// max fee per gas
437	/// The maximum total fee per gas the sender is willing to pay (includes the network / base fee
438	/// and miner / priority fee) in wei
439	pub max_fee_per_gas: U256,
440	/// max priority fee per gas
441	/// Maximum fee per gas the sender is willing to pay to miners in wei
442	pub max_priority_fee_per_gas: U256,
443	/// nonce
444	pub nonce: U256,
445	/// to address
446	pub to: Option<Address>,
447	/// type
448	pub r#type: TypeEip1559,
449	/// value
450	pub value: U256,
451}
452/// EIP-2930 transaction.
453#[derive(
454	Debug,
455	Default,
456	Clone,
457	Serialize,
458	Deserialize,
459	Eq,
460	PartialEq,
461	TypeInfo,
462	Encode,
463	Decode,
464	DecodeWithMemTracking,
465)]
466#[serde(rename_all = "camelCase")]
467pub struct Transaction2930Unsigned {
468	/// accessList
469	/// EIP-2930 access list
470	pub access_list: AccessList,
471	/// chainId
472	/// Chain ID that this transaction is valid on.
473	pub chain_id: U256,
474	/// gas limit
475	pub gas: U256,
476	/// gas price
477	/// The gas price willing to be paid by the sender in wei
478	pub gas_price: U256,
479	/// input data
480	pub input: Bytes,
481	/// nonce
482	pub nonce: U256,
483	/// to address
484	pub to: Option<Address>,
485	/// type
486	pub r#type: TypeEip2930,
487	/// value
488	pub value: U256,
489}
490/// EIP-4844 transaction.
491#[derive(
492	Debug,
493	Default,
494	Clone,
495	Serialize,
496	Deserialize,
497	Eq,
498	PartialEq,
499	TypeInfo,
500	Encode,
501	Decode,
502	DecodeWithMemTracking,
503)]
504#[serde(rename_all = "camelCase")]
505pub struct Transaction4844Unsigned {
506	/// accessList
507	/// EIP-2930 access list
508	pub access_list: AccessList,
509	/// blobVersionedHashes
510	/// List of versioned blob hashes associated with the transaction's EIP-4844 data blobs.
511	pub blob_versioned_hashes: Vec<H256>,
512	/// chainId
513	/// Chain ID that this transaction is valid on.
514	pub chain_id: U256,
515	/// gas limit
516	pub gas: U256,
517	/// input data
518	pub input: Bytes,
519	/// max fee per blob gas
520	/// The maximum total fee per gas the sender is willing to pay for blob gas in wei
521	pub max_fee_per_blob_gas: U256,
522	/// max fee per gas
523	/// The maximum total fee per gas the sender is willing to pay (includes the network / base fee
524	/// and miner / priority fee) in wei
525	pub max_fee_per_gas: U256,
526	/// max priority fee per gas
527	/// Maximum fee per gas the sender is willing to pay to miners in wei
528	pub max_priority_fee_per_gas: U256,
529	/// nonce
530	pub nonce: U256,
531	/// to address
532	pub to: Address,
533	/// type
534	pub r#type: TypeEip4844,
535	/// value
536	pub value: U256,
537}
538/// Legacy transaction.
539#[derive(
540	Debug,
541	Default,
542	Clone,
543	Serialize,
544	Deserialize,
545	Eq,
546	PartialEq,
547	TypeInfo,
548	Encode,
549	Decode,
550	DecodeWithMemTracking,
551)]
552#[serde(rename_all = "camelCase")]
553pub struct TransactionLegacyUnsigned {
554	/// chainId
555	/// Chain ID that this transaction is valid on.
556	#[serde(skip_serializing_if = "Option::is_none")]
557	pub chain_id: Option<U256>,
558	/// gas limit
559	pub gas: U256,
560	/// gas price
561	/// The gas price willing to be paid by the sender in wei
562	pub gas_price: U256,
563	/// input data
564	pub input: Bytes,
565	/// nonce
566	pub nonce: U256,
567	/// to address
568	pub to: Option<Address>,
569	/// type
570	pub r#type: TypeLegacy,
571	/// value
572	pub value: U256,
573}
574/// EIP-7702 transaction.
575#[derive(
576	Debug,
577	Clone,
578	Serialize,
579	Deserialize,
580	Default,
581	From,
582	Eq,
583	PartialEq,
584	TypeInfo,
585	Encode,
586	Decode,
587	DecodeWithMemTracking,
588)]
589#[serde(rename_all = "camelCase")]
590pub struct Transaction7702Unsigned {
591	/// accessList
592	/// EIP-2930 access list
593	pub access_list: AccessList,
594	/// authorizationList
595	/// List of account code authorizations
596	pub authorization_list: Vec<AuthorizationListEntry>,
597	/// chainId
598	/// Chain ID that this transaction is valid on.
599	pub chain_id: U256,
600	/// gas limit
601	pub gas: U256,
602	/// input data
603	pub input: Bytes,
604	/// max fee per gas
605	/// The maximum total fee per gas the sender is willing to pay (includes the network / base fee
606	/// and miner / priority fee) in wei
607	pub max_fee_per_gas: U256,
608	/// max priority fee per gas
609	/// Maximum fee per gas the sender is willing to pay to miners in wei
610	pub max_priority_fee_per_gas: U256,
611	/// nonce
612	pub nonce: U256,
613	/// to address
614	///
615	/// # Note
616	///
617	/// Extracted from eip-7702: `Note, this implies a null destination is not valid.`
618	pub to: Address,
619	/// type
620	pub r#type: TypeEip7702,
621	/// value
622	pub value: U256,
623}
624/// Signed 7702 Transaction
625#[derive(
626	Debug,
627	Clone,
628	Serialize,
629	Deserialize,
630	Eq,
631	PartialEq,
632	TypeInfo,
633	Encode,
634	Decode,
635	DecodeWithMemTracking,
636)]
637#[serde(rename_all = "camelCase")]
638pub struct Transaction7702Signed {
639	#[serde(flatten)]
640	pub transaction_7702_unsigned: Transaction7702Unsigned,
641	/// r
642	pub r: U256,
643	/// s
644	pub s: U256,
645	/// v
646	/// For backwards compatibility, `v` is optionally provided as an alternative to `yParity`.
647	/// This field is DEPRECATED and all use of it should migrate to `yParity`.
648	#[serde(skip_serializing_if = "Option::is_none")]
649	pub v: Option<U256>,
650	/// yParity
651	/// The parity (0 for even, 1 for odd) of the y-value of the secp256k1 signature.
652	pub y_parity: U256,
653}
654/// Signed 1559 Transaction
655#[derive(
656	Debug,
657	Default,
658	Clone,
659	Serialize,
660	Deserialize,
661	Eq,
662	PartialEq,
663	TypeInfo,
664	Encode,
665	Decode,
666	DecodeWithMemTracking,
667)]
668#[serde(rename_all = "camelCase")]
669pub struct Transaction1559Signed {
670	#[serde(flatten)]
671	pub transaction_1559_unsigned: Transaction1559Unsigned,
672	/// r
673	pub r: U256,
674	/// s
675	pub s: U256,
676	/// v
677	/// For backwards compatibility, `v` is optionally provided as an alternative to `yParity`.
678	/// This field is DEPRECATED and all use of it should migrate to `yParity`.
679	#[serde(skip_serializing_if = "Option::is_none")]
680	pub v: Option<U256>,
681	/// yParity
682	/// The parity (0 for even, 1 for odd) of the y-value of the secp256k1 signature.
683	pub y_parity: U256,
684}
685/// Signed 2930 Transaction
686#[derive(
687	Debug,
688	Default,
689	Clone,
690	Serialize,
691	Deserialize,
692	Eq,
693	PartialEq,
694	TypeInfo,
695	Encode,
696	Decode,
697	DecodeWithMemTracking,
698)]
699#[serde(rename_all = "camelCase")]
700pub struct Transaction2930Signed {
701	#[serde(flatten)]
702	pub transaction_2930_unsigned: Transaction2930Unsigned,
703	/// r
704	pub r: U256,
705	/// s
706	pub s: U256,
707	/// v
708	/// For backwards compatibility, `v` is optionally provided as an alternative to `yParity`.
709	/// This field is DEPRECATED and all use of it should migrate to `yParity`.
710	#[serde(skip_serializing_if = "Option::is_none")]
711	pub v: Option<U256>,
712	/// yParity
713	/// The parity (0 for even, 1 for odd) of the y-value of the secp256k1 signature.
714	pub y_parity: U256,
715}
716/// Signed 4844 Transaction
717#[derive(
718	Debug,
719	Default,
720	Clone,
721	Serialize,
722	Deserialize,
723	Eq,
724	PartialEq,
725	TypeInfo,
726	Encode,
727	Decode,
728	DecodeWithMemTracking,
729)]
730#[serde(rename_all = "camelCase")]
731pub struct Transaction4844Signed {
732	#[serde(flatten)]
733	pub transaction_4844_unsigned: Transaction4844Unsigned,
734	/// r
735	pub r: U256,
736	/// s
737	pub s: U256,
738	/// yParity
739	/// The parity (0 for even, 1 for odd) of the y-value of the secp256k1 signature.
740	pub y_parity: U256,
741}
742/// Signed Legacy Transaction
743#[derive(
744	Debug,
745	Default,
746	Clone,
747	Serialize,
748	Deserialize,
749	Eq,
750	PartialEq,
751	TypeInfo,
752	Encode,
753	Decode,
754	DecodeWithMemTracking,
755)]
756#[serde(rename_all = "camelCase")]
757pub struct TransactionLegacySigned {
758	#[serde(flatten)]
759	pub transaction_legacy_unsigned: TransactionLegacyUnsigned,
760	/// r
761	pub r: U256,
762	/// s
763	pub s: U256,
764	/// v
765	pub v: U256,
766}
767
768/// Access list
769pub type AccessList = Vec<AccessListEntry>;
770
771/// Access list entry
772#[derive(
773	Debug,
774	Default,
775	Clone,
776	Encode,
777	Decode,
778	TypeInfo,
779	Serialize,
780	Deserialize,
781	Eq,
782	PartialEq,
783	DecodeWithMemTracking,
784)]
785#[serde(rename_all = "camelCase")]
786pub struct AccessListEntry {
787	pub address: Address,
788	pub storage_keys: Vec<H256>,
789}
790
791/// Authorization list entry for EIP-7702
792#[derive(
793	Debug,
794	Default,
795	Clone,
796	Serialize,
797	Deserialize,
798	Eq,
799	PartialEq,
800	TypeInfo,
801	Encode,
802	Decode,
803	DecodeWithMemTracking,
804)]
805#[serde(rename_all = "camelCase")]
806pub struct AuthorizationListEntry {
807	/// Chain ID that this authorization is valid on
808	pub chain_id: U256,
809	/// Address to authorize
810	pub address: Address,
811	/// Nonce of the authorization
812	pub nonce: U256,
813	/// y-parity of the signature
814	pub y_parity: U256,
815	/// r component of signature
816	pub r: U256,
817	/// s component of signature
818	pub s: U256,
819}
820
821#[derive(
822	Debug, Clone, Serialize, Deserialize, From, TryInto, Eq, PartialEq, TypeInfo, Encode, Decode,
823)]
824#[serde(untagged)]
825pub enum HashesOrTransactionInfos {
826	/// Transaction hashes
827	Hashes(Vec<H256>),
828	/// Full transactions
829	TransactionInfos(Vec<TransactionInfo>),
830}
831
832impl Default for HashesOrTransactionInfos {
833	fn default() -> Self {
834		HashesOrTransactionInfos::Hashes(Default::default())
835	}
836}
837
838impl HashesOrTransactionInfos {
839	pub fn push_hash(&mut self, hash: H256) {
840		match self {
841			HashesOrTransactionInfos::Hashes(hashes) => hashes.push(hash),
842			_ => {},
843		}
844	}
845
846	pub fn len(&self) -> usize {
847		match self {
848			HashesOrTransactionInfos::Hashes(v) => v.len(),
849			HashesOrTransactionInfos::TransactionInfos(v) => v.len(),
850		}
851	}
852
853	pub fn is_empty(&self) -> bool {
854		self.len() == 0
855	}
856
857	pub fn contains_tx(&self, hash: H256) -> bool {
858		match self {
859			HashesOrTransactionInfos::Hashes(hashes) => hashes.iter().any(|h256| *h256 == hash),
860			HashesOrTransactionInfos::TransactionInfos(transaction_infos) => {
861				transaction_infos.iter().any(|ti| ti.hash == hash)
862			},
863		}
864	}
865}
866
867/// Input of a `GenericTransaction`
868#[derive(
869	Debug, Default, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, Eq, PartialEq,
870)]
871pub struct InputOrData {
872	#[serde(skip_serializing_if = "Option::is_none")]
873	input: Option<Bytes>,
874	#[serde(skip_serializing_if = "Option::is_none")]
875	data: Option<Bytes>,
876}
877
878impl From<Bytes> for InputOrData {
879	fn from(value: Bytes) -> Self {
880		InputOrData { input: Some(value), data: None }
881	}
882}
883
884impl From<Vec<u8>> for InputOrData {
885	fn from(value: Vec<u8>) -> Self {
886		InputOrData { input: Some(Bytes(value)), data: None }
887	}
888}
889
890impl InputOrData {
891	/// Get the input as `Bytes`.
892	pub fn to_bytes(self) -> Bytes {
893		match self {
894			InputOrData { input: Some(input), data: _ } => input,
895			InputOrData { input: None, data: Some(data) } => data,
896			_ => Default::default(),
897		}
898	}
899
900	/// Get the input as `Vec<u8>`.
901	pub fn to_vec(self) -> Vec<u8> {
902		self.to_bytes().0
903	}
904
905	/// Returns the input as a byte slice, preferring `input` over `data`.
906	pub fn as_slice(&self) -> &[u8] {
907		self.input
908			.as_ref()
909			.or(self.data.as_ref())
910			.map(|bytes| bytes.0.as_slice())
911			.unwrap_or_default()
912	}
913
914	/// Returns true if the input carries no bytes.
915	pub fn is_empty(&self) -> bool {
916		self.as_slice().is_empty()
917	}
918}
919
920fn deserialize_input_or_data<'d, D: Deserializer<'d>>(d: D) -> Result<InputOrData, D::Error> {
921	let value = InputOrData::deserialize(d)?;
922	match &value {
923		InputOrData { input: Some(input), data: Some(data) } if input != data => {
924			Err(serde::de::Error::custom(
925				"Both \"data\" and \"input\" are set and not equal. Please use \"input\" to pass transaction call data",
926			))
927		},
928		_ => Ok(value),
929	}
930}
931
932#[cfg(test)]
933mod tests {
934	use crate::evm::*;
935
936	#[test]
937	fn test_transaction_info_deserialize_from_value() {
938		// This tests the custom deserializer for TransactionInfo
939		// which works around serde's limitation with flatten + untagged enums from Value
940		let tx_info_expected = serde_json::json!({
941			"blockHash": "0xfb8c980d1da1a75e68c2ea4d55cb88d62dedbbb5eaf69df8fe337e9f6922b73a",
942			"blockNumber": "0x161bd0f",
943			"from": "0x4838b106fce9647bdf1e7877bf73ce8b0bad5f97",
944			"hash": "0x2c522d01183e9ed70caaf75c940ba9908d573cfc9996b3e7adc90313798279c8",
945			"transactionIndex": "0x7a",
946			"chainId": "0x1",
947			"gas": "0x565f",
948			"gasPrice": "0x23cf3fd4",
949			"input": "0x",
950			"nonce": "0x2c5ce1",
951			"r": "0x4a5703e4d8daf045f021cb32897a25b17d61b9ab629a59f0731ef4cce63f93d6",
952			"s": "0x711812237c1fed6aaf08e9f47fc47e547fdaceba9ab7507e62af29a945354fb6",
953			"to": "0x388c818ca8b9251b393131c08a736a67ccb19297",
954			"type": "0x0",
955			"v": "0x1",
956			"value": "0x12bf92aae0c2e70"
957		});
958
959		// Test deserializing from Value (this was failing before the custom deserializer) with
960		// below error:
961		// ```
962		// Failed to deserialize from Value: Some(Error("data did not match any variant of untagged enum TransactionSigned", line: 0, column: 0))
963		// ```
964		let tx_info_from_value: Result<TransactionInfo, serde_json::Error> =
965			serde_json::from_value(tx_info_expected.clone());
966		assert!(
967			tx_info_from_value.is_ok(),
968			"Failed to deserialize from Value: {:?}",
969			tx_info_from_value.err()
970		);
971
972		// Test deserializing from string (this was always working)
973		let json_str = serde_json::to_string(&tx_info_expected).unwrap();
974		let tx_info_from_str: Result<TransactionInfo, serde_json::Error> =
975			serde_json::from_str(&json_str);
976		assert!(
977			tx_info_from_str.is_ok(),
978			"Failed to deserialize from string: {:?}",
979			tx_info_from_str.err()
980		);
981
982		// Verify both methods produce the same result
983		let tx_info_from_value = tx_info_from_value.unwrap();
984		let tx_info_from_str = tx_info_from_str.unwrap();
985		assert_eq!(
986			tx_info_from_value, tx_info_from_str,
987			"Value and string deserialization should match"
988		);
989
990		// Serialize it back to JSON
991		let tx_info_serialized = serde_json::to_value(&tx_info_from_value);
992		assert!(
993			tx_info_serialized.is_ok(),
994			"Failed to serialize to value: {:?}",
995			tx_info_serialized.err()
996		);
997		let tx_info_serialized = tx_info_serialized.unwrap();
998
999		// Verify that deserializing and serializing leads to the same result
1000		assert_eq!(tx_info_serialized, tx_info_expected);
1001	}
1002
1003	#[test]
1004	fn test_transaction_hashes_deserialization() {
1005		let json = r#"["0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"]"#;
1006		let result: HashesOrTransactionInfos = serde_json::from_str(json).unwrap();
1007		assert!(matches!(result, HashesOrTransactionInfos::Hashes(_)));
1008
1009		let json = r#"[]"#;
1010		let result: HashesOrTransactionInfos = serde_json::from_str(json).unwrap();
1011		assert!(matches!(result, HashesOrTransactionInfos::Hashes(_)));
1012
1013		let json = r#"[{"invalid": "data"}]"#;
1014		let result: Result<HashesOrTransactionInfos, _> = serde_json::from_str(json);
1015		assert!(result.is_err());
1016	}
1017
1018	#[test]
1019	fn test_transaction_infos_deserialization() {
1020		let json = r#"[{
1021			"accessList": [{
1022				"address": "0x9008d19f58aabd9ed0d60971565aa8510560ab41",
1023				"storageKeys": [
1024					"0x0000000000000000000000000000000000000000000000000000000000000001"
1025				]
1026			}],
1027			"blockHash": "0xfb8c980d1da1a75e68c2ea4d55cb88d62dedbbb5eaf69df8fe337e9f6922b73a",
1028			"blockNumber": "0x161bd0f",
1029			"chainId": "0x1",
1030			"from": "0x4838b106fce9647bdf1e7877bf73ce8b0bad5f97",
1031			"gas": "0x565f",
1032			"gasPrice": "0x23cf3fd4",
1033			"hash": "0x2c522d01183e9ed70caaf75c940ba9908d573cfc9996b3e7adc90313798279c8",
1034			"input": "0x",
1035			"maxFeePerGas": "0x23cf3fd4",
1036			"maxPriorityFeePerGas": "0x0",
1037			"nonce": "0x2c5ce1",
1038			"r": "0x4a5703e4d8daf045f021cb32897a25b17d61b9ab629a59f0731ef4cce63f93d6",
1039			"s": "0x711812237c1fed6aaf08e9f47fc47e547fdaceba9ab7507e62af29a945354fb6",
1040			"to": "0x388c818ca8b9251b393131c08a736a67ccb19297",
1041			"transactionIndex": "0x7a",
1042			"type": "0x2",
1043			"v": "0x0",
1044			"value": "0x12bf92aae0c2e70",
1045			"yParity": "0x0"
1046			}]
1047		"#;
1048		let result: HashesOrTransactionInfos = serde_json::from_str(json).unwrap();
1049		assert!(matches!(result, HashesOrTransactionInfos::TransactionInfos(_)));
1050	}
1051
1052	#[test]
1053	fn can_deserialize_input_or_data_field_from_generic_transaction() {
1054		let cases = [
1055			("with input", r#"{"input": "0x01"}"#),
1056			("with data", r#"{"data": "0x01"}"#),
1057			("with both", r#"{"data": "0x01", "input": "0x01"}"#),
1058		];
1059
1060		for (name, json) in cases {
1061			let tx = serde_json::from_str::<GenericTransaction>(json).unwrap();
1062			assert_eq!(tx.input.to_vec(), vec![1u8], "{}", name);
1063		}
1064
1065		let err =
1066			serde_json::from_str::<GenericTransaction>(r#"{"data": "0x02", "input": "0x01"}"#)
1067				.unwrap_err();
1068		assert!(
1069			err.to_string().starts_with(
1070			"Both \"data\" and \"input\" are set and not equal. Please use \"input\" to pass transaction call data"
1071			)
1072		);
1073	}
1074
1075	#[test]
1076	fn from_unsigned_works_for_legacy() {
1077		let base_gas_price = U256::from(10);
1078		let tx = TransactionUnsigned::from(TransactionLegacyUnsigned {
1079			chain_id: Some(U256::from(1)),
1080			input: Bytes::from(vec![1u8]),
1081			nonce: U256::from(1),
1082			value: U256::from(1),
1083			to: Some(H160::zero()),
1084			gas: U256::from(1),
1085			gas_price: U256::from(10),
1086			..Default::default()
1087		});
1088
1089		let generic = GenericTransaction::from_unsigned(tx.clone(), base_gas_price, None);
1090		assert_eq!(generic.gas_price, Some(U256::from(10)));
1091
1092		let tx2 = generic.try_into_unsigned().unwrap();
1093		assert_eq!(tx, tx2);
1094	}
1095
1096	#[test]
1097	fn from_unsigned_works_for_1559() {
1098		let base_gas_price = U256::from(10);
1099		let tx = TransactionUnsigned::from(Transaction1559Unsigned {
1100			chain_id: U256::from(1),
1101			input: Bytes::from(vec![1u8]),
1102			nonce: U256::from(1),
1103			value: U256::from(1),
1104			to: Some(H160::zero()),
1105			gas: U256::from(1),
1106			gas_price: U256::from(20),
1107			max_fee_per_gas: U256::from(20),
1108			max_priority_fee_per_gas: U256::from(1),
1109			..Default::default()
1110		});
1111
1112		let generic = GenericTransaction::from_unsigned(tx.clone(), base_gas_price, None);
1113		assert_eq!(generic.gas_price, Some(U256::from(10)));
1114
1115		let tx2 = generic.try_into_unsigned().unwrap();
1116		assert_eq!(tx, tx2);
1117	}
1118
1119	#[test]
1120	fn from_unsigned_works_for_7702() {
1121		let base_gas_price = U256::from(10);
1122		let tx = TransactionUnsigned::from(Transaction7702Unsigned {
1123			chain_id: U256::from(1),
1124			input: Bytes::from(vec![1u8]),
1125			nonce: U256::from(1),
1126			value: U256::from(1),
1127			to: H160::zero(),
1128			gas: U256::from(1),
1129			max_fee_per_gas: U256::from(20),
1130			max_priority_fee_per_gas: U256::from(1),
1131			authorization_list: vec![AuthorizationListEntry {
1132				chain_id: U256::from(1),
1133				address: H160::from_low_u64_be(42),
1134				nonce: U256::from(0),
1135				y_parity: U256::from(1),
1136				r: U256::from(1),
1137				s: U256::from(2),
1138			}],
1139			..Default::default()
1140		});
1141
1142		let generic = GenericTransaction::from_unsigned(tx.clone(), base_gas_price, None);
1143		assert_eq!(generic.gas_price, Some(U256::from(10)));
1144
1145		let tx2 = generic.try_into_unsigned().unwrap();
1146		assert_eq!(tx, tx2);
1147	}
1148}