referrerpolicy=no-referrer-when-downgrade

pallet_revive/evm/api/
rpc_types.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//! Utility impl for the RPC types.
18use super::*;
19use alloc::vec::Vec;
20use codec::{Decode, Encode};
21use scale_info::TypeInfo;
22use sp_core::{H160, U256};
23use sp_crypto_hashing::keccak_256;
24
25/// Configuration specific to a dry-run execution.
26///
27/// Passed as an argument to the `eth_transact_with_config` runtime API method. Contains optional
28/// overrides that control how the dry-run is executed, such as timestamp simulation and state
29/// injection.
30///
31/// # Backwards Compatibility
32///
33/// This type is SCALE-encoded when passed across the runtime API boundary via `state_call`.
34/// SCALE is a non-self-describing format: fields are encoded sequentially with no field names,
35/// delimiters, or end-of-message markers. This has important implications when the struct evolves
36/// over time.
37///
38/// ## Adding new trailing fields
39///
40/// New fields may be appended to the end of this struct without requiring a new runtime API
41/// method, provided that:
42///
43/// 1. **New RPC, old runtime (trailing bytes are ignored):** The `sp_api` runtime API argument
44///    decoding machinery uses `Decode::decode` (not `decode_all`) for parameterized calls. This
45///    means bytes remaining after decoding all known fields are silently ignored. An old runtime
46///    that does not know about a newly appended field will decode the fields it recognizes and
47///    discard the rest. This is intentional behavior in `sp_api` — see the generated code in
48///    `substrate/primitives/api/proc-macro/src/impl_runtime_apis.rs`.
49///
50/// 2. **Old RPC, new runtime (missing bytes are defaulted):** A new runtime expecting more fields
51///    than an old RPC provides would hit EOF during decoding and fail. To guard against this, this
52///    type uses a **custom `Decode` implementation** that falls back to `Default` for any trailing
53///    fields that are absent from the input. This ensures that an old RPC sending a shorter
54///    encoding is handled gracefully.
55///
56/// ## Constraints on fields
57///
58/// - New fields **must** be appended to the end. Inserting or reordering fields changes the byte
59///   layout of all subsequent fields, breaking both directions.
60/// - New fields **must** implement `Default` so that the custom `Decode` fallback can produce a
61///   sensible value when the field is absent from the input. This is the only requirement on the
62///   field's type — it does not need to be `Option`.
63/// - This pattern relies on `sp_api` continuing to use `Decode::decode` rather than `decode_all`.
64///   If that ever changes, a new runtime API method would be needed instead.
65///
66/// ## Constraints on runtime API placement
67///
68/// The trailing-bytes trick described in point 1 above only works because `sp_api` discards
69/// unconsumed bytes **at the end of the entire argument buffer**. This means `DryRunConfig`
70/// must be the **last argument** of any runtime API method that uses it (which is currently
71/// the case for both `eth_transact_with_config` and `eth_estimate_gas`). If it were placed
72/// before another argument, the extra bytes from newly appended fields would shift the
73/// decoding offset and corrupt the subsequent argument.
74#[derive(Debug, Encode, TypeInfo, Clone)]
75pub struct DryRunConfig<Moment> {
76	/// Optional timestamp override for dry-run in pending block.
77	pub timestamp_override: Option<Moment>,
78	/// Used to control if the dry run logic should perform the balance checks or not.
79	pub perform_balance_checks: Option<bool>,
80	/// Optional state overrides to apply before executing the call. Each entry maps an account
81	/// address to a set of fields (balance, nonce, code, storage) that should be temporarily
82	/// replaced for the duration of the dry-run.
83	pub state_overrides: Option<StateOverrideSet>,
84}
85
86impl<Moment> Default for DryRunConfig<Moment> {
87	fn default() -> Self {
88		Self { timestamp_override: None, perform_balance_checks: Some(true), state_overrides: None }
89	}
90}
91
92/// A custom implementation of [`Decode`] to ensure forward and backward compatibility of the
93/// [`DryRunConfig`] type.
94///
95/// # Backwards Compatibility
96///
97/// Please review the documentation on the [`DryRunConfig`] for more information about how we
98/// manage and handle compatibility for this type and instructions on what you should do when adding
99/// a new field to this type.
100impl<Moment: Decode> Decode for DryRunConfig<Moment> {
101	fn decode<I: codec::Input>(input: &mut I) -> Result<Self, codec::Error> {
102		let timestamp_override = Option::<Moment>::decode(input)?;
103		let perform_balance_checks = Option::<bool>::decode(input)?;
104		let state_overrides = Option::<StateOverrideSet>::decode(input).unwrap_or_default();
105		Ok(Self { timestamp_override, perform_balance_checks, state_overrides })
106	}
107}
108
109impl<Moment> DryRunConfig<Moment> {
110	/// Create a new `DryRunConfig` with default values.
111	///
112	/// Balance checks are enabled by default. Use the builder methods to customize.
113	pub fn new() -> Self {
114		Self::default()
115	}
116
117	/// A builder method which consumes the object and modifies the `timestamp_override` field.
118	pub fn with_timestamp_override(
119		mut self,
120		timestamp_override: impl Into<Option<Moment>>,
121	) -> Self {
122		self.timestamp_override = timestamp_override.into();
123		self
124	}
125
126	/// A builder method which consumes the object and modifies the `perform_balance_checks` field.
127	pub fn with_perform_balance_checks(
128		mut self,
129		perform_balance_checks: impl Into<Option<bool>>,
130	) -> Self {
131		self.perform_balance_checks = perform_balance_checks.into();
132		self
133	}
134
135	/// A builder method which consumes the object and sets the state overrides.
136	pub fn with_state_overrides(
137		mut self,
138		state_overrides: impl Into<Option<StateOverrideSet>>,
139	) -> Self {
140		self.state_overrides = state_overrides.into();
141		self
142	}
143}
144
145/// Configuration specific to a tracing execution.
146///
147/// Passed as the last argument to the `trace_call_with_config` runtime API method. Contains
148/// optional overrides that affect how the traced execution is performed.
149///
150/// # Backwards Compatibility
151///
152/// This type follows the same backwards compatibility strategy as [`DryRunConfig`]. SCALE is a
153/// non-self-describing format: fields are encoded sequentially with no names or delimiters. This
154/// type uses a custom [`Decode`] implementation that defaults missing trailing fields, and relies
155/// on `sp_api`'s use of `Decode::decode` (not `decode_all`) to silently discard trailing bytes
156/// that an old runtime does not recognize.
157///
158/// ## Constraints on fields
159///
160/// - New fields **must** be appended to the end. Inserting or reordering fields breaks the byte
161///   layout in both directions.
162/// - New fields **must** implement `Default` so the custom `Decode` fallback can produce a sensible
163///   value when the field is absent from the input.
164///
165/// ## Constraints on runtime API placement
166///
167/// `TracingConfig` must be the **last argument** of any runtime API method that uses it. If it
168/// were placed before another argument, extra bytes from newly appended fields would shift the
169/// decoding offset and corrupt the subsequent argument.
170#[derive(Debug, Default, Encode, TypeInfo, Clone)]
171pub struct TracingConfig {
172	/// Optional state overrides to apply before executing the traced call. Each entry maps an
173	/// account address to a set of fields (balance, nonce, code, storage) that should be
174	/// temporarily replaced for the duration of the trace.
175	pub state_overrides: Option<StateOverrideSet>,
176}
177
178/// A custom implementation of [`Decode`] to ensure forward and backward compatibility of the
179/// [`TracingConfig`] type.
180///
181/// # Backwards Compatibility
182///
183/// Please review the documentation on [`TracingConfig`] for more information about how we manage
184/// and handle compatibility for this type and instructions on what you should do when adding a
185/// new field.
186impl Decode for TracingConfig {
187	fn decode<I: codec::Input>(input: &mut I) -> Result<Self, codec::Error> {
188		let state_overrides = Option::<StateOverrideSet>::decode(input).unwrap_or_default();
189		Ok(Self { state_overrides })
190	}
191}
192
193impl TracingConfig {
194	/// Create a new `TracingConfig` with default values.
195	pub fn new() -> Self {
196		Self::default()
197	}
198
199	/// A builder method which consumes the object and sets the state overrides.
200	pub fn with_state_overrides(
201		mut self,
202		state_overrides: impl Into<Option<StateOverrideSet>>,
203	) -> Self {
204		self.state_overrides = state_overrides.into();
205		self
206	}
207}
208
209impl From<BlockNumberOrTag> for BlockNumberOrTagOrHash {
210	fn from(b: BlockNumberOrTag) -> Self {
211		match b {
212			BlockNumberOrTag::U256(n) => BlockNumberOrTagOrHash::BlockNumber(n),
213			BlockNumberOrTag::BlockTag(t) => BlockNumberOrTagOrHash::BlockTag(t),
214		}
215	}
216}
217
218impl From<TransactionSigned> for TransactionUnsigned {
219	fn from(tx: TransactionSigned) -> Self {
220		use TransactionSigned::*;
221		match tx {
222			Transaction7702Signed(tx) => tx.transaction_7702_unsigned.into(),
223			Transaction4844Signed(tx) => tx.transaction_4844_unsigned.into(),
224			Transaction1559Signed(tx) => tx.transaction_1559_unsigned.into(),
225			Transaction2930Signed(tx) => tx.transaction_2930_unsigned.into(),
226			TransactionLegacySigned(tx) => tx.transaction_legacy_unsigned.into(),
227		}
228	}
229}
230
231impl TransactionInfo {
232	/// Create a new [`TransactionInfo`] from a receipt and a signed transaction.
233	pub fn new(receipt: &ReceiptInfo, transaction_signed: TransactionSigned) -> Self {
234		Self {
235			block_hash: receipt.block_hash,
236			block_number: receipt.block_number,
237			from: receipt.from,
238			hash: receipt.transaction_hash,
239			transaction_index: receipt.transaction_index,
240			transaction_signed,
241		}
242	}
243}
244
245impl ReceiptInfo {
246	/// Initialize a new Receipt
247	pub fn new(
248		block_hash: H256,
249		block_number: U256,
250		contract_address: Option<Address>,
251		from: Address,
252		logs: Vec<Log>,
253		to: Option<Address>,
254		effective_gas_price: U256,
255		gas_used: U256,
256		success: bool,
257		transaction_hash: H256,
258		transaction_index: U256,
259		r#type: Byte,
260	) -> Self {
261		let logs_bloom = Self::logs_bloom(&logs);
262		ReceiptInfo {
263			block_hash,
264			block_number,
265			contract_address,
266			from,
267			logs,
268			logs_bloom,
269			to,
270			effective_gas_price,
271			gas_used,
272			status: Some(if success { U256::one() } else { U256::zero() }),
273			transaction_hash,
274			transaction_index,
275			r#type: Some(r#type),
276			..Default::default()
277		}
278	}
279
280	/// Returns `true` if the transaction was successful.
281	pub fn is_success(&self) -> bool {
282		self.status.map_or(false, |status| status == U256::one())
283	}
284
285	/// Calculate receipt logs bloom.
286	fn logs_bloom(logs: &[Log]) -> Bytes256 {
287		let mut bloom = [0u8; 256];
288		for log in logs {
289			m3_2048(&mut bloom, &log.address.as_ref());
290			for topic in &log.topics {
291				m3_2048(&mut bloom, topic.as_ref());
292			}
293		}
294		bloom.into()
295	}
296}
297/// Specialised Bloom filter that sets three bits out of 2048, given an
298/// arbitrary byte sequence.
299///
300/// See Section 4.4.1 "Transaction Receipt" of the [Ethereum Yellow Paper][ref].
301///
302/// [ref]: https://ethereum.github.io/yellowpaper/paper.pdf
303fn m3_2048(bloom: &mut [u8; 256], bytes: &[u8]) {
304	let hash = keccak_256(bytes);
305	for i in [0, 2, 4] {
306		let bit = (hash[i + 1] as usize + ((hash[i] as usize) << 8)) & 0x7FF;
307		bloom[256 - 1 - bit / 8] |= 1 << (bit % 8);
308	}
309}
310
311#[test]
312fn can_deserialize_input_or_data_field_from_generic_transaction() {
313	let cases = [
314		("with input", r#"{"input": "0x01"}"#),
315		("with data", r#"{"data": "0x01"}"#),
316		("with both", r#"{"data": "0x01", "input": "0x01"}"#),
317	];
318
319	for (name, json) in cases {
320		let tx = serde_json::from_str::<GenericTransaction>(json).unwrap();
321		assert_eq!(tx.input.to_vec(), vec![1u8], "{}", name);
322	}
323
324	let err = serde_json::from_str::<GenericTransaction>(r#"{"data": "0x02", "input": "0x01"}"#)
325		.unwrap_err();
326	assert!(
327		err.to_string().starts_with(
328		"Both \"data\" and \"input\" are set and not equal. Please use \"input\" to pass transaction call data"
329		)
330	);
331}
332
333#[test]
334fn test_block_number_or_tag_or_hash_deserialization() {
335	let val: BlockNumberOrTagOrHash = serde_json::from_str("\"latest\"").unwrap();
336	assert_eq!(val, BlockTag::Latest.into());
337
338	for s in ["\"0x1a\"", r#"{ "blockNumber": "0x1a" }"#] {
339		let val: BlockNumberOrTagOrHash = serde_json::from_str(s).unwrap();
340		assert!(matches!(val, BlockNumberOrTagOrHash::BlockNumber(n) if n == 26u64.into()));
341	}
342
343	for s in [
344		"\"0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"",
345		r#"{ "blockHash": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" }"#,
346	] {
347		let val: BlockNumberOrTagOrHash = serde_json::from_str(s).unwrap();
348		assert_eq!(val, BlockNumberOrTagOrHash::BlockHash(H256([0xaau8; 32])));
349	}
350}
351
352#[test]
353fn logs_bloom_works() {
354	let receipt: ReceiptInfo = serde_json::from_str(
355		r#"
356		{
357			"blockHash": "0x835ee379aaabf4802a22a93ad8164c02bbdde2cc03d4552d5c642faf4e09d1f3",
358			"blockNumber": "0x2",
359			"contractAddress": null,
360			"cumulativeGasUsed": "0x5d92",
361			"effectiveGasPrice": "0x2dcd5c2d",
362			"from": "0xb4f1f9ecfe5a28633a27f57300bda217e99b8969",
363			"gasUsed": "0x5d92",
364			"logs": [
365				{
366				"address": "0x82bdb002b9b1f36c42df15fbdc6886abcb2ab31d",
367				"topics": [
368					"0x1585375487296ff2f0370daeec4214074a032b31af827c12622fa9a58c16c7d0",
369					"0x000000000000000000000000b4f1f9ecfe5a28633a27f57300bda217e99b8969"
370				],
371				"data": "0x00000000000000000000000000000000000000000000000000000000000030390000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000b48656c6c6f20776f726c64000000000000000000000000000000000000000000",
372				"blockNumber": "0x2",
373				"transactionHash": "0xad0075127962bdf73d787f2944bdb5f351876f23c35e6a48c1f5b6463a100af4",
374				"transactionIndex": "0x0",
375				"blockHash": "0x835ee379aaabf4802a22a93ad8164c02bbdde2cc03d4552d5c642faf4e09d1f3",
376				"logIndex": "0x0",
377				"removed": false
378				}
379			],
380			"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000008000000000000000000000000000000000000000000000000800000000040000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000004000000000000000800000000000000000080000000000000000000000000000000000000000000",
381			"status": "0x1",
382			"to": "0x82bdb002b9b1f36c42df15fbdc6886abcb2ab31d",
383			"transactionHash": "0xad0075127962bdf73d787f2944bdb5f351876f23c35e6a48c1f5b6463a100af4",
384			"transactionIndex": "0x0",
385			"type": "0x2"
386		}
387		"#,
388	)
389	.unwrap();
390	assert_eq!(receipt.logs_bloom, ReceiptInfo::logs_bloom(&receipt.logs));
391}
392
393impl GenericTransaction {
394	/// Create a new [`GenericTransaction`] from a signed transaction.
395	pub fn from_signed(tx: TransactionSigned, base_gas_price: U256, from: Option<H160>) -> Self {
396		Self::from_unsigned(tx.into(), base_gas_price, from)
397	}
398
399	/// The gas price that is actually paid (including priority fee).
400	pub fn effective_gas_price(&self, base_gas_price: U256) -> Option<U256> {
401		let effective_gas_price = if let Some(prio_price) = self.max_priority_fee_per_gas {
402			let max_price = self.max_fee_per_gas?;
403			Some(max_price.min(base_gas_price.saturating_add(prio_price)))
404		} else {
405			self.gas_price
406		};
407
408		// we do not implement priority fee as it does not map to tip well
409		// hence the effective gas price cannot be higher than the base price
410		effective_gas_price.map(|e| e.min(base_gas_price))
411	}
412
413	/// Create a new [`GenericTransaction`] from a unsigned transaction.
414	pub fn from_unsigned(
415		tx: TransactionUnsigned,
416		base_gas_price: U256,
417		from: Option<H160>,
418	) -> Self {
419		use TransactionUnsigned::*;
420		let mut tx = match tx {
421			TransactionLegacyUnsigned(tx) => GenericTransaction {
422				from,
423				r#type: Some(tx.r#type.as_byte()),
424				chain_id: tx.chain_id,
425				input: tx.input.into(),
426				nonce: Some(tx.nonce),
427				value: Some(tx.value),
428				to: tx.to,
429				gas: Some(tx.gas),
430				gas_price: Some(tx.gas_price),
431				..Default::default()
432			},
433			Transaction4844Unsigned(tx) => GenericTransaction {
434				from,
435				r#type: Some(tx.r#type.as_byte()),
436				chain_id: Some(tx.chain_id),
437				input: tx.input.into(),
438				nonce: Some(tx.nonce),
439				value: Some(tx.value),
440				to: Some(tx.to),
441				gas: Some(tx.gas),
442				access_list: Some(tx.access_list),
443				blob_versioned_hashes: tx.blob_versioned_hashes,
444				max_fee_per_blob_gas: Some(tx.max_fee_per_blob_gas),
445				max_fee_per_gas: Some(tx.max_fee_per_gas),
446				max_priority_fee_per_gas: Some(tx.max_priority_fee_per_gas),
447				..Default::default()
448			},
449			Transaction1559Unsigned(tx) => GenericTransaction {
450				from,
451				r#type: Some(tx.r#type.as_byte()),
452				chain_id: Some(tx.chain_id),
453				input: tx.input.into(),
454				nonce: Some(tx.nonce),
455				value: Some(tx.value),
456				to: tx.to,
457				gas: Some(tx.gas),
458				access_list: Some(tx.access_list),
459				max_fee_per_gas: Some(tx.max_fee_per_gas),
460				max_priority_fee_per_gas: Some(tx.max_priority_fee_per_gas),
461				..Default::default()
462			},
463			Transaction2930Unsigned(tx) => GenericTransaction {
464				from,
465				r#type: Some(tx.r#type.as_byte()),
466				chain_id: Some(tx.chain_id),
467				input: tx.input.into(),
468				nonce: Some(tx.nonce),
469				value: Some(tx.value),
470				to: tx.to,
471				gas: Some(tx.gas),
472				gas_price: Some(tx.gas_price),
473				access_list: Some(tx.access_list),
474				..Default::default()
475			},
476			Transaction7702Unsigned(tx) => GenericTransaction {
477				from,
478				r#type: Some(tx.r#type.as_byte()),
479				chain_id: Some(tx.chain_id),
480				input: tx.input.into(),
481				nonce: Some(tx.nonce),
482				value: Some(tx.value),
483				to: Some(tx.to),
484				gas: Some(tx.gas),
485				access_list: Some(tx.access_list),
486				authorization_list: tx.authorization_list,
487				max_fee_per_gas: Some(tx.max_fee_per_gas),
488				max_priority_fee_per_gas: Some(tx.max_priority_fee_per_gas),
489				..Default::default()
490			},
491		};
492		tx.gas_price = tx.effective_gas_price(base_gas_price);
493		tx
494	}
495
496	/// Convert to a [`TransactionUnsigned`].
497	pub fn try_into_unsigned(self) -> Result<TransactionUnsigned, ()> {
498		match self.r#type.unwrap_or_default().0 {
499			TYPE_LEGACY => Ok(TransactionLegacyUnsigned {
500				r#type: TypeLegacy {},
501				chain_id: self.chain_id,
502				input: self.input.to_bytes(),
503				nonce: self.nonce.unwrap_or_default(),
504				value: self.value.unwrap_or_default(),
505				to: self.to,
506				gas: self.gas.unwrap_or_default(),
507				gas_price: self.gas_price.unwrap_or_default(),
508			}
509			.into()),
510			TYPE_EIP1559 => Ok(Transaction1559Unsigned {
511				r#type: TypeEip1559 {},
512				chain_id: self.chain_id.unwrap_or_default(),
513				input: self.input.to_bytes(),
514				nonce: self.nonce.unwrap_or_default(),
515				value: self.value.unwrap_or_default(),
516				to: self.to,
517				gas: self.gas.unwrap_or_default(),
518				gas_price: self.max_fee_per_gas.unwrap_or_default(),
519				access_list: self.access_list.unwrap_or_default(),
520				max_fee_per_gas: self.max_fee_per_gas.unwrap_or_default(),
521				max_priority_fee_per_gas: self.max_priority_fee_per_gas.unwrap_or_default(),
522			}
523			.into()),
524			TYPE_EIP2930 => Ok(Transaction2930Unsigned {
525				r#type: TypeEip2930 {},
526				chain_id: self.chain_id.unwrap_or_default(),
527				input: self.input.to_bytes(),
528				nonce: self.nonce.unwrap_or_default(),
529				value: self.value.unwrap_or_default(),
530				to: self.to,
531				gas: self.gas.unwrap_or_default(),
532				gas_price: self.gas_price.unwrap_or_default(),
533				access_list: self.access_list.unwrap_or_default(),
534			}
535			.into()),
536			TYPE_EIP4844 => Ok(Transaction4844Unsigned {
537				r#type: TypeEip4844 {},
538				chain_id: self.chain_id.unwrap_or_default(),
539				input: self.input.to_bytes(),
540				nonce: self.nonce.unwrap_or_default(),
541				value: self.value.unwrap_or_default(),
542				to: self.to.unwrap_or_default(),
543				gas: self.gas.unwrap_or_default(),
544				max_fee_per_gas: self.max_fee_per_gas.unwrap_or_default(),
545				max_fee_per_blob_gas: self.max_fee_per_blob_gas.unwrap_or_default(),
546				max_priority_fee_per_gas: self.max_priority_fee_per_gas.unwrap_or_default(),
547				access_list: self.access_list.unwrap_or_default(),
548				blob_versioned_hashes: self.blob_versioned_hashes,
549			}
550			.into()),
551			TYPE_EIP7702 => Ok(Transaction7702Unsigned {
552				r#type: TypeEip7702 {},
553				chain_id: self.chain_id.unwrap_or_default(),
554				input: self.input.to_bytes(),
555				nonce: self.nonce.unwrap_or_default(),
556				value: self.value.unwrap_or_default(),
557				to: self.to.unwrap_or_default(),
558				gas: self.gas.unwrap_or_default(),
559				max_fee_per_gas: self.max_fee_per_gas.unwrap_or_default(),
560				max_priority_fee_per_gas: self.max_priority_fee_per_gas.unwrap_or_default(),
561				access_list: self.access_list.unwrap_or_default(),
562				authorization_list: self.authorization_list,
563			}
564			.into()),
565			_ => Err(()),
566		}
567	}
568}
569
570#[test]
571fn from_unsigned_works_for_legacy() {
572	let base_gas_price = U256::from(10);
573	let tx = TransactionUnsigned::from(TransactionLegacyUnsigned {
574		chain_id: Some(U256::from(1)),
575		input: Bytes::from(vec![1u8]),
576		nonce: U256::from(1),
577		value: U256::from(1),
578		to: Some(H160::zero()),
579		gas: U256::from(1),
580		gas_price: U256::from(10),
581		..Default::default()
582	});
583
584	let generic = GenericTransaction::from_unsigned(tx.clone(), base_gas_price, None);
585	assert_eq!(generic.gas_price, Some(U256::from(10)));
586
587	let tx2 = generic.try_into_unsigned().unwrap();
588	assert_eq!(tx, tx2);
589}
590
591#[test]
592fn from_unsigned_works_for_1559() {
593	let base_gas_price = U256::from(10);
594	let tx = TransactionUnsigned::from(Transaction1559Unsigned {
595		chain_id: U256::from(1),
596		input: Bytes::from(vec![1u8]),
597		nonce: U256::from(1),
598		value: U256::from(1),
599		to: Some(H160::zero()),
600		gas: U256::from(1),
601		gas_price: U256::from(20),
602		max_fee_per_gas: U256::from(20),
603		max_priority_fee_per_gas: U256::from(1),
604		..Default::default()
605	});
606
607	let generic = GenericTransaction::from_unsigned(tx.clone(), base_gas_price, None);
608	assert_eq!(generic.gas_price, Some(U256::from(10)));
609
610	let tx2 = generic.try_into_unsigned().unwrap();
611	assert_eq!(tx, tx2);
612}
613
614#[test]
615fn from_unsigned_works_for_7702() {
616	let base_gas_price = U256::from(10);
617	let tx = TransactionUnsigned::from(Transaction7702Unsigned {
618		chain_id: U256::from(1),
619		input: Bytes::from(vec![1u8]),
620		nonce: U256::from(1),
621		value: U256::from(1),
622		to: H160::zero(),
623		gas: U256::from(1),
624		max_fee_per_gas: U256::from(20),
625		max_priority_fee_per_gas: U256::from(1),
626		authorization_list: vec![AuthorizationListEntry {
627			chain_id: U256::from(1),
628			address: H160::from_low_u64_be(42),
629			nonce: U256::from(0),
630			y_parity: U256::from(1),
631			r: U256::from(1),
632			s: U256::from(2),
633		}],
634		..Default::default()
635	});
636
637	let generic = GenericTransaction::from_unsigned(tx.clone(), base_gas_price, None);
638	assert_eq!(generic.gas_price, Some(U256::from(10)));
639
640	let tx2 = generic.try_into_unsigned().unwrap();
641	assert_eq!(tx, tx2);
642}