referrerpolicy=no-referrer-when-downgrade

pallet_revive_eth_rpc/
lib.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//! The [`EthRpcServer`] RPC server implementation
18#![cfg_attr(docsrs, feature(doc_cfg))]
19
20pub use alloy_rpc_types::{BlockId, BlockNumberOrTag, Filter, FilterBlockOption};
21use client::ClientError;
22use futures::{Stream, StreamExt, TryStreamExt};
23use jsonrpsee::{
24	PendingSubscriptionSink, SubscriptionMessage, SubscriptionSink,
25	core::{RpcResult, async_trait},
26	types::{ErrorCode, ErrorObjectOwned},
27};
28use pallet_revive::evm::*;
29use pallet_revive_types::runtime_api::{
30	BlockV1, ExecutionTracerConfigV1, GenericTransactionV1, ReceiptGasInfoV1, StateOverrideSetV1,
31	TraceV1, TracerTypeV1, TransactionInfoV1,
32};
33use sp_core::{H160, H256, U256};
34use sp_crypto_hashing::keccak_256;
35use std::pin::Pin;
36use subxt::rpcs::methods::legacy::TransactionStatus;
37use thiserror::Error;
38use tokio_stream::wrappers::{BroadcastStream, errors::BroadcastStreamRecvError};
39
40mod block_sync;
41pub(crate) use block_sync::{ChainMetadata, SyncLabel, SyncStateKey};
42pub mod cli;
43pub mod client;
44pub mod example;
45pub mod subxt_client;
46
47#[cfg(test)]
48mod tests;
49
50mod block_info_provider;
51pub use block_info_provider::*;
52
53mod receipt_provider;
54pub use receipt_provider::*;
55
56mod fee_history_provider;
57pub use fee_history_provider::*;
58
59mod receipt_extractor;
60pub use receipt_extractor::*;
61
62mod apis;
63pub use apis::*;
64
65mod types;
66pub use types::*;
67
68pub const LOG_TARGET: &str = "eth-rpc";
69
70/// An EVM RPC server implementation.
71pub struct EthRpcServerImpl {
72	/// The client used to interact with the substrate node.
73	client: client::Client,
74
75	/// The accounts managed by the server.
76	accounts: Vec<Account>,
77
78	/// Controls if unprotected txs are allowed or not.
79	allow_unprotected_txs: bool,
80
81	/// When true, estimate_gas uses Pending block if no block is specified.
82	use_pending_for_estimate_gas: bool,
83}
84
85impl EthRpcServerImpl {
86	/// Creates a new [`EthRpcServerImpl`].
87	pub fn new(client: client::Client) -> Self {
88		Self {
89			client,
90			accounts: vec![],
91			allow_unprotected_txs: false,
92			use_pending_for_estimate_gas: false,
93		}
94	}
95
96	/// Sets the accounts managed by the server.
97	pub fn with_accounts(mut self, accounts: Vec<Account>) -> Self {
98		self.accounts = accounts;
99		self
100	}
101
102	/// Sets whether unprotected transactions are allowed or not.
103	pub fn with_allow_unprotected_txs(mut self, allow_unprotected_txs: bool) -> Self {
104		self.allow_unprotected_txs = allow_unprotected_txs;
105		self
106	}
107
108	/// Sets whether estimate_gas uses Pending block when no block is specified.
109	pub fn with_use_pending_for_estimate_gas(mut self, use_pending_for_estimate_gas: bool) -> Self {
110		self.use_pending_for_estimate_gas = use_pending_for_estimate_gas;
111		self
112	}
113}
114
115/// The error type for the EVM RPC server.
116#[derive(Error, Debug)]
117pub enum EthRpcError {
118	/// A [`ClientError`] wrapper error.
119	#[error("Client error: {0}")]
120	ClientError(#[from] ClientError),
121	/// A [`rlp::DecoderError`] wrapper error.
122	#[error("Decoding error: {0}")]
123	RlpError(#[from] rlp::DecoderError),
124	/// A Decimals conversion error.
125	#[error("Conversion error")]
126	ConversionError,
127	/// An invalid signature error.
128	#[error("Invalid signature")]
129	InvalidSignature,
130	/// The account was not found at the given address
131	#[error("Account not found for address {0:?}")]
132	AccountNotFound(H160),
133	/// Received an invalid transaction
134	#[error("Invalid transaction")]
135	InvalidTransaction,
136	/// Received an invalid transaction
137	#[error("Invalid transaction {0:?}")]
138	TransactionTypeNotSupported(Byte),
139	/// The requested `eth_feeHistory` reward percentiles are invalid.
140	#[error("{0}")]
141	InvalidRewardPercentiles(String),
142}
143
144impl From<EthRpcError> for ErrorObjectOwned {
145	fn from(value: EthRpcError) -> Self {
146		use jsonrpsee::types::error::CALL_EXECUTION_FAILED_CODE;
147		let message = value.to_string();
148		let code = match value {
149			// `ClientError` already produces a fully formed JSON-RPC error object.
150			EthRpcError::ClientError(err) => return Self::from(err),
151			EthRpcError::ConversionError => ErrorCode::InvalidParams.code(),
152			// Matches Geth/Nethermind, which return `-32000` for these execution-time errors.
153			EthRpcError::RlpError(_) |
154			EthRpcError::InvalidSignature |
155			EthRpcError::AccountNotFound(_) |
156			EthRpcError::InvalidTransaction |
157			EthRpcError::InvalidRewardPercentiles(_) |
158			EthRpcError::TransactionTypeNotSupported(_) => CALL_EXECUTION_FAILED_CODE,
159		};
160		Self::owned::<String>(code, message, None)
161	}
162}
163
164#[async_trait]
165impl EthRpcServer for EthRpcServerImpl {
166	async fn net_version(&self) -> RpcResult<String> {
167		Ok(self.client.chain_id().to_string())
168	}
169
170	async fn net_listening(&self) -> RpcResult<bool> {
171		let syncing = self.client.syncing().await?;
172		let listening = matches!(syncing, SyncingStatus::Bool(false));
173		Ok(listening)
174	}
175
176	async fn syncing(&self) -> RpcResult<SyncingStatus> {
177		Ok(self.client.syncing().await?)
178	}
179
180	async fn block_number(&self) -> RpcResult<U256> {
181		let number = self.client.block_number().await?;
182		Ok(number.into())
183	}
184
185	async fn get_transaction_receipt(
186		&self,
187		transaction_hash: H256,
188	) -> RpcResult<Option<ReceiptInfo>> {
189		let receipt = self.client.receipt(&transaction_hash).await;
190		Ok(receipt)
191	}
192
193	/// Performs gas estimations to find the lowest gas limit required to run the transaction.
194	///
195	/// This method implements the same gas estimation logic found in Geth which performs binary
196	/// search with some simple heuristics to find the smallest gas limit for the transaction.
197	async fn estimate_gas(
198		&self,
199		transaction: GenericTransactionV1,
200		block: Option<BlockNumberOrTag>,
201	) -> RpcResult<U256> {
202		log::trace!(target: LOG_TARGET, "estimate_gas transaction={transaction:?} block={block:?}");
203
204		let block = block.unwrap_or_else(|| {
205			if self.use_pending_for_estimate_gas {
206				BlockNumberOrTag::Pending
207			} else {
208				Default::default()
209			}
210		});
211		let block = BlockId::from(block);
212		let hash = self.client.block_hash_for_tag(block).await?;
213		let gas_estimate = self
214			.client
215			.runtime_api(hash)
216			.await?
217			.estimate_gas(transaction, block)
218			.ok_or(ClientError::UnsupportedRuntimeApiMethod("eth_estimate_gas"))?
219			.await?;
220
221		log::trace!(
222			target: LOG_TARGET,
223			"estimate_gas result={gas_estimate:?}",
224		);
225		Ok(gas_estimate)
226	}
227
228	async fn call(
229		&self,
230		transaction: GenericTransactionV1,
231		block: Option<BlockId>,
232		state_overrides: Option<StateOverrideSetV1>,
233	) -> RpcResult<Bytes> {
234		let block = block.unwrap_or_default();
235		let hash = self.client.block_hash_for_tag(block).await?;
236		let runtime_api = self.client.runtime_api(hash).await?;
237		let dry_run = runtime_api
238			.dry_run(transaction, block, state_overrides)
239			.ok_or(ClientError::UnsupportedRuntimeApiMethod("eth_transact"))?
240			.await?;
241		Ok(dry_run.data.into())
242	}
243
244	async fn send_raw_transaction(&self, transaction: Bytes) -> RpcResult<H256> {
245		let hash = H256(keccak_256(&transaction.0));
246		log::trace!(target: LOG_TARGET, "send_raw_transaction transaction: {transaction:?} ethereum_hash: {hash:?}");
247
248		if !self.allow_unprotected_txs {
249			let signed_transaction = TransactionSigned::decode(transaction.0.as_slice())
250				.map_err(|err| {
251					log::trace!(target: LOG_TARGET, "Transaction decoding failed. ethereum_hash: {hash:?}, error: {err:?}");
252					EthRpcError::InvalidTransaction
253				})?;
254
255			let is_chain_id_provided = match signed_transaction {
256				TransactionSigned::Transaction7702Signed(tx) => {
257					tx.transaction_7702_unsigned.chain_id != U256::zero()
258				},
259				TransactionSigned::Transaction4844Signed(tx) => {
260					tx.transaction_4844_unsigned.chain_id != U256::zero()
261				},
262				TransactionSigned::Transaction1559Signed(tx) => {
263					tx.transaction_1559_unsigned.chain_id != U256::zero()
264				},
265				TransactionSigned::Transaction2930Signed(tx) => {
266					tx.transaction_2930_unsigned.chain_id != U256::zero()
267				},
268				TransactionSigned::TransactionLegacySigned(tx) => {
269					tx.transaction_legacy_unsigned.chain_id.is_some()
270				},
271			};
272
273			if !is_chain_id_provided {
274				log::trace!(target: LOG_TARGET, "Invalid Transaction: transaction doesn't include a chain-id. ethereum_hash: {hash:?}");
275				Err(EthRpcError::InvalidTransaction)?;
276			}
277		}
278
279		let call = subxt_client::tx().revive().eth_transact(transaction.0);
280
281		// Subscribe to new block only when automine is enabled.
282		let receiver = self.client.block_notifier().map(|sender| sender.subscribe());
283
284		// Submit the transaction
285		let tx_status = self.client.submit(call).await.map_err(|err| {
286			log::trace!(target: LOG_TARGET, "send_raw_transaction ethereum_hash: {hash:?} failed: {err:?}");
287			err
288		})?;
289
290		if matches!(tx_status, TransactionStatus::Future) {
291			return Ok(hash);
292		}
293
294		// Wait for the transaction to be included in a block if automine is enabled
295		if let Some(mut receiver) = receiver {
296			loop {
297				if let Ok(block_hash) = receiver.recv().await {
298					let Ok(Some(block)) = self.client.block_by_hash(&block_hash).await else {
299						log::debug!(target: LOG_TARGET, "Could not find the block with the received hash: {hash:?}.");
300						continue;
301					};
302					let Some(evm_block) = self.client.evm_block(block, false).await else {
303						log::debug!(target: LOG_TARGET, "Failed to get the EVM block for substrate block with hash: {hash:?}");
304						continue;
305					};
306					if evm_block.transactions.contains_tx(hash) {
307						log::debug!(target: LOG_TARGET, "{hash:} was included in a block");
308						break;
309					}
310				}
311			}
312		}
313
314		log::debug!(target: LOG_TARGET, "send_raw_transaction hash: {hash:?}");
315		Ok(hash)
316	}
317
318	async fn send_transaction(&self, mut transaction: GenericTransactionV1) -> RpcResult<H256> {
319		log::debug!(target: LOG_TARGET, "{transaction:#?}");
320
321		let Some(from) = transaction.from else {
322			log::debug!(target: LOG_TARGET, "Transaction must have a sender");
323			return Err(EthRpcError::InvalidTransaction.into());
324		};
325
326		let account = self
327			.accounts
328			.iter()
329			.find(|account| account.address() == from)
330			.ok_or(EthRpcError::AccountNotFound(from))?;
331
332		if transaction.gas.is_none() {
333			transaction.gas = Some(self.estimate_gas(transaction.clone(), None).await?);
334		}
335
336		if transaction.gas_price.is_none() {
337			transaction.gas_price = Some(self.gas_price().await?);
338		}
339
340		if transaction.nonce.is_none() {
341			transaction.nonce = Some(self.get_transaction_count(from, Default::default()).await?);
342		}
343
344		if transaction.chain_id.is_none() {
345			transaction.chain_id = Some(self.chain_id().await?);
346		}
347
348		let tx = GenericTransaction::from(transaction)
349			.try_into_unsigned()
350			.map_err(|_| EthRpcError::InvalidTransaction)?;
351		let payload = account.sign_transaction(tx).signed_payload();
352		self.send_raw_transaction(Bytes(payload)).await
353	}
354
355	async fn get_block_by_hash(
356		&self,
357		block_hash: H256,
358		hydrated_transactions: bool,
359	) -> RpcResult<Option<BlockV1>> {
360		let Some(block) = self.client.block_by_ethereum_hash(&block_hash).await? else {
361			return Ok(None);
362		};
363		let block = self.client.evm_block(block, hydrated_transactions).await;
364		Ok(block)
365	}
366
367	async fn get_balance(&self, address: H160, block: BlockId) -> RpcResult<U256> {
368		let hash = self.client.block_hash_for_tag(block).await?;
369		let runtime_api = self.client.runtime_api(hash).await?;
370		let balance = runtime_api
371			.balance(address)
372			.ok_or(ClientError::UnsupportedRuntimeApiMethod("balance"))?
373			.await?;
374		Ok(balance)
375	}
376
377	async fn chain_id(&self) -> RpcResult<U256> {
378		Ok(self.client.chain_id().into())
379	}
380
381	async fn gas_price(&self) -> RpcResult<U256> {
382		let hash = self.client.block_hash_for_tag(Default::default()).await?;
383		let runtime_api = self.client.runtime_api(hash).await?;
384		let gas_price = runtime_api
385			.gas_price()
386			.ok_or(ClientError::UnsupportedRuntimeApiMethod("gas_price"))?
387			.await?;
388		Ok(gas_price)
389	}
390
391	async fn max_priority_fee_per_gas(&self) -> RpcResult<U256> {
392		// We do not support tips. Hence the recommended priority fee is
393		// always zero. The effective gas price will always be the base price.
394		Ok(Default::default())
395	}
396
397	async fn get_code(&self, address: H160, block: BlockId) -> RpcResult<Bytes> {
398		let hash = self.client.block_hash_for_tag(block).await?;
399		let code = self
400			.client
401			.runtime_api(hash)
402			.await?
403			.code(address)
404			.ok_or(ClientError::UnsupportedRuntimeApiMethod("code"))?
405			.await?;
406		Ok(code.into())
407	}
408
409	async fn accounts(&self) -> RpcResult<Vec<H160>> {
410		Ok(self.accounts.iter().map(|account| account.address()).collect())
411	}
412
413	async fn get_block_by_number(
414		&self,
415		block_number: BlockNumberOrTag,
416		hydrated_transactions: bool,
417	) -> RpcResult<Option<BlockV1>> {
418		let Some(block) = self.client.block_by_number_or_tag(&block_number).await? else {
419			return Ok(None);
420		};
421		let block = self.client.evm_block(block, hydrated_transactions).await;
422		Ok(block)
423	}
424
425	async fn get_block_receipts(&self, block: BlockId) -> RpcResult<Option<Vec<ReceiptInfo>>> {
426		let receipts = self.client.block_receipts(block).await?;
427		Ok(receipts)
428	}
429
430	async fn get_block_transaction_count_by_hash(
431		&self,
432		block_hash: Option<H256>,
433	) -> RpcResult<Option<U256>> {
434		let block_hash = if let Some(block_hash) = block_hash {
435			block_hash
436		} else {
437			self.client.latest_block().await.hash()
438		};
439
440		let Some(substrate_hash) = self.client.resolve_substrate_hash(&block_hash).await else {
441			return Ok(None);
442		};
443
444		Ok(self.client.receipts_count_per_block(&substrate_hash).await.map(U256::from))
445	}
446
447	async fn get_block_transaction_count_by_number(
448		&self,
449		block: Option<BlockNumberOrTag>,
450	) -> RpcResult<Option<U256>> {
451		let substrate_hash = if let Some(block) = self
452			.client
453			.block_by_number_or_tag(&block.unwrap_or_else(|| BlockNumberOrTag::Latest))
454			.await?
455		{
456			block.block_hash()
457		} else {
458			return Ok(None);
459		};
460
461		Ok(self.client.receipts_count_per_block(&substrate_hash).await.map(U256::from))
462	}
463
464	async fn get_logs(&self, filter: Option<Filter>) -> RpcResult<FilterResults> {
465		let logs = self.client.logs(filter).await?;
466		Ok(FilterResults::Logs(logs))
467	}
468
469	async fn get_storage_at(
470		&self,
471		address: H160,
472		storage_slot: U256,
473		block: BlockId,
474	) -> RpcResult<Bytes> {
475		let hash = self.client.block_hash_for_tag(block).await?;
476		let runtime_api = self.client.runtime_api(hash).await?;
477		let get_storage = runtime_api
478			.get_storage(address, storage_slot.to_big_endian())
479			.ok_or(ClientError::UnsupportedRuntimeApiMethod("get_storage"))?;
480		let bytes = match get_storage.await {
481			Ok(value) => value.unwrap_or([0u8; 32].into()),
482			// Per Ethereum spec, return zero for non-contract addresses.
483			Err(ClientError::ContractNotFound) => {
484				log::trace!(target: LOG_TARGET, "get_storage_at: ContractNotFound for {address:?}, returning zero");
485				[0u8; 32].into()
486			},
487			Err(err) => return Err(err.into()),
488		};
489		Ok(bytes.into())
490	}
491
492	async fn get_transaction_by_block_hash_and_index(
493		&self,
494		block_hash: H256,
495		transaction_index: U256,
496	) -> RpcResult<Option<TransactionInfoV1>> {
497		let Some(substrate_block_hash) = self.client.resolve_substrate_hash(&block_hash).await
498		else {
499			return Ok(None);
500		};
501		self.get_transaction_by_substrate_block_hash_and_index(
502			substrate_block_hash,
503			transaction_index,
504		)
505		.await
506	}
507
508	async fn get_transaction_by_block_number_and_index(
509		&self,
510		block: BlockNumberOrTag,
511		transaction_index: U256,
512	) -> RpcResult<Option<TransactionInfoV1>> {
513		let Some(block) = self.client.block_by_number_or_tag(&block).await? else {
514			return Ok(None);
515		};
516		self.get_transaction_by_substrate_block_hash_and_index(
517			block.block_hash(),
518			transaction_index,
519		)
520		.await
521	}
522
523	async fn get_transaction_by_hash(
524		&self,
525		transaction_hash: H256,
526	) -> RpcResult<Option<TransactionInfoV1>> {
527		let receipt = self.client.receipt(&transaction_hash).await;
528		let signed_tx = self.client.signed_tx_by_hash(&transaction_hash).await;
529		if let (Some(receipt), Some(signed_tx)) = (receipt, signed_tx) {
530			return Ok(Some(receipt.transaction_info(signed_tx)));
531		}
532
533		Ok(None)
534	}
535
536	async fn get_transaction_count(&self, address: H160, block: BlockId) -> RpcResult<U256> {
537		let hash = self.client.block_hash_for_tag(block).await?;
538		let runtime_api = self.client.runtime_api(hash).await?;
539		let nonce = runtime_api
540			.nonce(address)
541			.ok_or(ClientError::UnsupportedRuntimeApiMethod("nonce"))?
542			.await?;
543		Ok(nonce)
544	}
545
546	async fn web3_client_version(&self) -> RpcResult<String> {
547		let git_revision = env!("GIT_REVISION");
548		let rustc_version = env!("RUSTC_VERSION");
549		let target = env!("TARGET");
550		Ok(format!("eth-rpc/{git_revision}/{target}/{rustc_version}"))
551	}
552
553	async fn fee_history(
554		&self,
555		block_count: U256,
556		newest_block: BlockNumberOrTag,
557		reward_percentiles: Option<Vec<f64>>,
558	) -> RpcResult<FeeHistoryResult> {
559		let block_count: u32 = block_count.try_into().map_err(|_| EthRpcError::ConversionError)?;
560
561		// As in go-ethereum, a request for fewer than one block returns an empty result rather
562		// than an error, before any percentile validation.
563		if block_count == 0 {
564			return Ok(FeeHistoryResult {
565				oldest_block: U256::zero(),
566				base_fee_per_gas: vec![],
567				gas_used_ratio: vec![],
568				reward: vec![],
569			});
570		}
571
572		// Reject malformed percentiles up front, as go-ethereum does, instead of silently
573		// clamping or approximating them at the wrong bucket.
574		if let Some(percentiles) = reward_percentiles.as_deref() {
575			validate_reward_percentiles(percentiles)
576				.map_err(EthRpcError::InvalidRewardPercentiles)?;
577		}
578
579		let result = self.client.fee_history(block_count, newest_block, reward_percentiles).await?;
580		Ok(result)
581	}
582
583	async fn eth_subscribe(
584		&self,
585		pending: PendingSubscriptionSink,
586		kind: SubscriptionKind,
587		options: Option<SubscriptionOptions>,
588	) {
589		let Some(subscription_parameters) = SubscriptionParameters::new(kind, options) else {
590			return pending
591				.reject(ErrorObjectOwned::owned(
592					jsonrpsee::types::error::INVALID_PARAMS_CODE,
593					"Invalid subscription parameters",
594					None::<()>,
595				))
596				.await;
597		};
598		let Ok(sink) = pending.accept().await else {
599			return;
600		};
601
602		let stream: Pin<
603			Box<dyn Stream<Item = Result<SubscriptionItem, BroadcastStreamRecvError>> + Send>,
604		> = match subscription_parameters {
605			SubscriptionParameters::NewBlockHeaders => Box::pin(
606				BroadcastStream::new(self.client.get_block_subscription_rx())
607					.map_ok(|block| SubscriptionItem::BlockHeader(BlockHeader::from(block))),
608			) as _,
609			SubscriptionParameters::Logs(filter) => Box::pin(
610				BroadcastStream::new(self.client.get_log_subscription_rx())
611					.try_filter(move |log| futures::future::ready(filter.matches(log)))
612					.map_ok(SubscriptionItem::Log),
613			) as _,
614		};
615		let _ = tokio::spawn(Self::handle_subscription_forwarding(sink, stream));
616	}
617}
618
619impl EthRpcServerImpl {
620	async fn get_transaction_by_substrate_block_hash_and_index(
621		&self,
622		substrate_block_hash: H256,
623		transaction_index: U256,
624	) -> RpcResult<Option<TransactionInfoV1>> {
625		let Some(receipt) = self
626			.client
627			.receipt_by_hash_and_index(
628				&substrate_block_hash,
629				transaction_index.try_into().map_err(|_| EthRpcError::ConversionError)?,
630			)
631			.await
632		else {
633			return Ok(None);
634		};
635		let Some(signed_tx) = self.client.signed_tx_by_hash(&receipt.transaction_hash).await else {
636			return Ok(None);
637		};
638
639		Ok(Some(receipt.transaction_info(signed_tx)))
640	}
641
642	async fn handle_subscription_forwarding(
643		sink: SubscriptionSink,
644		mut stream: Pin<
645			Box<dyn Stream<Item = Result<SubscriptionItem, BroadcastStreamRecvError>> + Send>,
646		>,
647	) {
648		loop {
649			tokio::select! {
650				_ = sink.closed() => break,
651				item = stream.next() => {
652					match item {
653						// Stream ended.
654						None => break,
655						// Send the item to the subscriber.
656						Some(Ok(sub_item)) => {
657							let msg = SubscriptionMessage::from_json(&sub_item)
658								.expect("SubscriptionItem is serializable; qed");
659							if sink.send(msg).await.is_err() {
660								break;
661							}
662						},
663						// Broadcast receiver lagged behind — missed messages.
664						Some(Err(BroadcastStreamRecvError::Lagged(count))) => {
665							log::warn!(
666								target: LOG_TARGET,
667								"Subscription lagged, skipped {count} messages"
668							);
669						},
670					}
671				}
672			}
673		}
674	}
675}
676
677#[cfg(test)]
678mod error_codes_tests {
679	use super::*;
680	use jsonrpsee::types::error::{CALL_EXECUTION_FAILED_CODE, INVALID_PARAMS_CODE};
681
682	#[test]
683	fn eth_rpc_error_maps_to_expected_code_and_message() {
684		let cases: Vec<(EthRpcError, i32)> = vec![
685			(EthRpcError::RlpError(rlp::DecoderError::RlpIsTooShort), CALL_EXECUTION_FAILED_CODE),
686			(EthRpcError::ConversionError, INVALID_PARAMS_CODE),
687			(EthRpcError::InvalidSignature, CALL_EXECUTION_FAILED_CODE),
688			(EthRpcError::AccountNotFound(H160::repeat_byte(0xab)), CALL_EXECUTION_FAILED_CODE),
689			(EthRpcError::InvalidTransaction, CALL_EXECUTION_FAILED_CODE),
690			(
691				EthRpcError::TransactionTypeNotSupported(Byte::from(0x7eu8)),
692				CALL_EXECUTION_FAILED_CODE,
693			),
694		];
695
696		for (err, expected_code) in cases {
697			let expected_message = err.to_string();
698			let obj = ErrorObjectOwned::from(err);
699			assert_eq!(obj.code(), expected_code, "unexpected code for `{expected_message}`");
700			assert_eq!(obj.message(), expected_message);
701		}
702	}
703}