1use alloy_consensus::crypto::RecoveryError;
4use alloy_evm::overrides::StateOverrideError;
5use alloy_primitives::{B256, Bytes, SignatureError, TxHash, U256};
6use alloy_rpc_types::BlockNumberOrTag;
7use alloy_signer::Error as SignerError;
8use alloy_transport::TransportError;
9use anvil_core::eth::wallet::WalletError;
10use anvil_rpc::{
11 error::{ErrorCode, RpcError},
12 response::ResponseResult,
13};
14use foundry_evm::{backend::DatabaseError, decode::RevertDecoder};
15use revm::{
16 context_interface::result::{EVMError, InvalidHeader, InvalidTransaction},
17 interpreter::InstructionResult,
18};
19use serde::Serialize;
20use tempo_revm::TempoInvalidTransaction;
21use tokio::time::Duration;
22
23#[cfg(feature = "optimism")]
24mod optimism;
25
26pub(crate) type Result<T> = std::result::Result<T, BlockchainError>;
27
28#[derive(Debug, thiserror::Error)]
29pub enum BlockchainError {
30 #[error(transparent)]
31 Pool(#[from] PoolError),
32 #[error("No signer available")]
33 NoSignerAvailable,
34 #[error("Chain Id not available")]
35 ChainIdNotAvailable,
36 #[error("Invalid input: `max_priority_fee_per_gas` greater than `max_fee_per_gas`")]
37 InvalidFeeInput,
38 #[error("Transaction data is empty")]
39 EmptyRawTransactionData,
40 #[error("Failed to decode signed transaction")]
41 FailedToDecodeSignedTransaction,
42 #[error("Failed to decode transaction")]
43 FailedToDecodeTransaction,
44 #[error("Failed to decode receipt")]
45 FailedToDecodeReceipt,
46 #[error("Cannot EIP-2718 encode transaction type 0x{0:x}")]
47 UnsupportedTransactionEncoding(u8),
48 #[error("Failed to decode state")]
49 FailedToDecodeStateDump,
50 #[error("Prevrandao not in the EVM's environment after merge")]
51 PrevrandaoNotSet,
52 #[error(transparent)]
53 SignatureError(#[from] SignatureError),
54 #[error(transparent)]
55 RecoveryError(#[from] RecoveryError),
56 #[error(transparent)]
57 SignerError(#[from] SignerError),
58 #[error("Rpc Endpoint not implemented")]
59 RpcUnimplemented,
60 #[error("Rpc error {0:?}")]
61 RpcError(RpcError),
62 #[error(transparent)]
63 InvalidTransaction(#[from] InvalidTransactionError),
64 #[error(transparent)]
65 FeeHistory(#[from] FeeHistoryError),
66 #[error(transparent)]
67 AlloyForkProvider(#[from] TransportError),
68 #[error("EVM error {0:?}")]
69 EvmError(InstructionResult),
70 #[error("Evm override error: {0}")]
71 EvmOverrideError(String),
72 #[error("Invalid url {0:?}")]
73 InvalidUrl(String),
74 #[error("unsupported fork network for chain {chain_id}: {reason}")]
75 UnsupportedForkNetwork { chain_id: u64, reason: &'static str },
76 #[error("Internal error: {0:?}")]
77 Internal(String),
78 #[error("BlockOutOfRangeError: block height is {0} but requested was {1}")]
79 BlockOutOfRange(u64, u64),
80 #[error("Resource not found")]
81 BlockNotFound,
82 #[error("unknown block")]
83 UnknownBlock,
84 #[error("transaction not found")]
86 TransactionNotFound,
87 #[error("Required data unavailable")]
88 DataUnavailable,
89 #[error("Trie error: {0}")]
90 TrieError(String),
91 #[error("{0}")]
92 UintConversion(&'static str),
93 #[error("State override error: {0}")]
94 StateOverrideError(String),
95 #[error("Timestamp error: {0}")]
96 TimestampError(String),
97 #[error(transparent)]
98 DatabaseError(#[from] DatabaseError),
99 #[error(
100 "EIP-1559 style fee params (maxFeePerGas or maxPriorityFeePerGas) received but they are not supported by the current hardfork.\n\nYou can use them by running anvil with '--hardfork london' or later."
101 )]
102 EIP1559TransactionUnsupportedAtHardfork,
103 #[error(
104 "Access list received but is not supported by the current hardfork.\n\nYou can use it by running anvil with '--hardfork berlin' or later."
105 )]
106 EIP2930TransactionUnsupportedAtHardfork,
107 #[error(
108 "EIP-4844 fields received but is not supported by the current hardfork.\n\nYou can use it by running anvil with '--hardfork cancun' or later."
109 )]
110 EIP4844TransactionUnsupportedAtHardfork,
111 #[error(
112 "EIP-7702 fields received but is not supported by the current hardfork.\n\nYou can use it by running anvil with '--hardfork prague' or later."
113 )]
114 EIP7702TransactionUnsupportedAtHardfork,
115 #[error(
116 "op-stack deposit tx received but is not supported.\n\nYou can use it by running anvil with '--optimism'."
117 )]
118 DepositTransactionUnsupported,
119 #[error(
120 "tempo transaction received but is not supported.\n\nYou can use it by running anvil with '--tempo'."
121 )]
122 TempoTransactionUnsupported,
123 #[error("Unknown transaction type not supported")]
124 UnknownTransactionType,
125 #[error("Excess blob gas not set.")]
126 ExcessBlobGasNotSet,
127 #[error("{0}")]
128 Message(String),
129 #[error("Transaction {hash} was added to the mempool but wasn't confirmed within {duration:?}")]
130 TransactionConfirmationTimeout {
131 hash: B256,
133 duration: Duration,
135 },
136 #[error("Invalid transaction request: {0}")]
137 InvalidTransactionRequest(String),
138 #[error("filter not found")]
139 FilterNotFound,
140}
141
142impl From<eyre::Report> for BlockchainError {
143 fn from(err: eyre::Report) -> Self {
144 Self::Message(err.to_string())
145 }
146}
147
148impl From<RpcError> for BlockchainError {
149 fn from(err: RpcError) -> Self {
150 Self::RpcError(err)
151 }
152}
153
154impl<T> From<EVMError<T>> for BlockchainError
155where
156 T: Into<Self>,
157{
158 fn from(err: EVMError<T>) -> Self {
159 match err {
160 EVMError::Transaction(err) => InvalidTransactionError::from(err).into(),
161 EVMError::Header(err) => match err {
162 InvalidHeader::ExcessBlobGasNotSet => Self::ExcessBlobGasNotSet,
163 InvalidHeader::PrevrandaoNotSet => Self::PrevrandaoNotSet,
164 },
165 EVMError::Database(err) => err.into(),
166 EVMError::Custom(err) => Self::Message(err),
167 EVMError::CustomAny(err) => Self::Message(err.to_string()),
168 }
169 }
170}
171
172impl<T> From<EVMError<T, TempoInvalidTransaction>> for BlockchainError
173where
174 T: Into<Self>,
175{
176 fn from(err: EVMError<T, TempoInvalidTransaction>) -> Self {
177 match err {
178 EVMError::Transaction(err) => match err {
179 TempoInvalidTransaction::EthInvalidTransaction(err) => {
180 InvalidTransactionError::from(err).into()
181 }
182 err => Self::Message(format!("tempo transaction error: {err}")),
183 },
184 EVMError::Header(err) => match err {
185 InvalidHeader::ExcessBlobGasNotSet => Self::ExcessBlobGasNotSet,
186 InvalidHeader::PrevrandaoNotSet => Self::PrevrandaoNotSet,
187 },
188 EVMError::Database(err) => err.into(),
189 EVMError::Custom(err) => Self::Message(err),
190 EVMError::CustomAny(err) => Self::Message(err.to_string()),
191 }
192 }
193}
194
195impl From<WalletError> for BlockchainError {
196 fn from(value: WalletError) -> Self {
197 Self::Message(value.to_string())
198 }
199}
200
201impl<E> From<StateOverrideError<E>> for BlockchainError
202where
203 E: Into<Self>,
204{
205 fn from(value: StateOverrideError<E>) -> Self {
206 match value {
207 StateOverrideError::InvalidBytecode(err) => Self::StateOverrideError(err.to_string()),
208 StateOverrideError::BothStateAndStateDiff(addr) => Self::StateOverrideError(format!(
209 "state and state_diff can't be used together for account {addr}",
210 )),
211 StateOverrideError::Database(err) => err.into(),
212 }
213 }
214}
215
216#[derive(Debug, thiserror::Error)]
218pub enum PoolError {
219 #[error("Transaction with cyclic dependent transactions")]
220 CyclicTransaction,
221 #[error("Tx: [{0:?}] insufficient gas price to replace existing transaction")]
223 ReplacementUnderpriced(TxHash),
224 #[error("Tx: [{0:?}] already Imported")]
225 AlreadyImported(TxHash),
226}
227
228#[derive(Debug, thiserror::Error)]
230pub enum FeeHistoryError {
231 #[error("requested block range is out of bounds")]
232 InvalidBlockRange,
233 #[error("reward percentiles must be strictly increasing and between 0 and 100")]
234 InvalidRewardPercentiles,
235 #[error("could not find block number requested: {0}")]
236 BlockNotFound(BlockNumberOrTag),
237}
238
239#[derive(Debug)]
240pub struct ErrDetail {
241 pub detail: String,
242}
243
244#[derive(Debug, thiserror::Error)]
246pub enum InvalidTransactionError {
247 #[error("nonce too low")]
249 NonceTooLow,
250 #[error("Nonce too high")]
253 NonceTooHigh,
254 #[error("nonce has max value")]
257 NonceMaxValue,
258 #[error("insufficient funds for transfer")]
260 InsufficientFundsForTransfer,
261 #[error("max initcode size exceeded")]
263 MaxInitCodeSizeExceeded,
264 #[error("Insufficient funds for gas * price + value")]
266 InsufficientFunds,
267 #[error("gas uint64 overflow")]
269 GasUintOverflow,
270 #[error("intrinsic gas too low")]
273 GasTooLow,
274 #[error("intrinsic gas too high -- {}",.0.detail)]
276 GasTooHigh(ErrDetail),
277 #[error("max priority fee per gas higher than max fee per gas")]
280 TipAboveFeeCap,
281 #[error("max fee per gas less than block base fee")]
283 FeeCapTooLow,
284 #[error("Out of gas: gas required exceeds allowance: {0:?}")]
286 BasicOutOfGas(u128),
287 #[error("execution reverted: {0:?}")]
289 Revert(Option<Bytes>),
290 #[error("sender not an eoa")]
292 SenderNoEOA,
293 #[error("invalid chain id for signer")]
295 InvalidChainId,
296 #[error("Incompatible EIP-155 transaction, signed for another chain")]
298 IncompatibleEIP155,
299 #[error("Access lists are not supported before the Berlin hardfork")]
301 AccessListNotSupported,
302 #[error("Block `blob_gas_price` is greater than tx-specified `max_fee_per_blob_gas`")]
305 BlobFeeCapTooLow(u128, u128),
306 #[error("Block `blob_versioned_hashes` is not supported before the Cancun hardfork")]
309 BlobVersionedHashesNotSupported,
310 #[error("`max_fee_per_blob_gas` is not supported for blocks before the Cancun hardfork.")]
312 MaxFeePerBlobGasNotSupported,
313 #[error("`blob_hashes` are required for EIP-4844 transactions")]
315 NoBlobHashes,
316 #[error("too many blobs in one transaction, have: {0}, max: {1}")]
317 TooManyBlobs(usize, usize),
318 #[error(transparent)]
320 BlobTransactionValidationError(#[from] alloy_consensus::BlobTransactionValidationError),
321 #[error("Blob transaction can't be a create transaction. `to` must be present.")]
323 BlobCreateTransaction,
324 #[error("Blob transaction contains a versioned hash with an incorrect version")]
326 BlobVersionNotSupported,
327 #[error("EIP-4844 blob transactions are not supported on Monad")]
329 MonadBlobTransactionUnsupported,
330 #[error("There should be at least one blob in a Blob transaction.")]
332 EmptyBlobs,
333 #[error("EIP-7702 authorization lists are not supported before the Prague hardfork")]
335 AuthorizationListNotSupported,
336 #[error("Transaction gas limit is greater than the block gas limit, gas_limit: {0}, cap: {1}")]
337 TxGasLimitGreaterThanCap(u64, u64),
338 #[error(transparent)]
340 Revm(revm::context_interface::result::InvalidTransaction),
341 #[error("op-deposit failure post regolith")]
343 DepositTxErrorPostRegolith,
344 #[error("missing enveloped transaction")]
346 MissingEnvelopedTx,
347 #[error("native value transfer not allowed in Tempo mode")]
349 TempoNativeValueTransfer,
350 #[error("Tempo tx valid_before ({valid_before}) must be > current time + 3s ({min_allowed})")]
352 TempoValidBeforeExpired { valid_before: u64, min_allowed: u64 },
353 #[error("Tempo tx valid_after ({valid_after}) must be <= current time + 1h ({max_allowed})")]
355 TempoValidAfterTooFar { valid_after: u64, max_allowed: u64 },
356 #[error("Tempo tx has too many authorizations ({count}), max allowed is {max}")]
358 TempoTooManyAuthorizations { count: usize, max: usize },
359 #[error("insufficient fee token balance: have {balance}, need {required}")]
361 TempoInsufficientFeeTokenBalance { balance: U256, required: U256 },
362}
363
364impl From<InvalidTransaction> for InvalidTransactionError {
365 fn from(err: InvalidTransaction) -> Self {
366 match err {
367 InvalidTransaction::InvalidChainId => Self::InvalidChainId,
368 InvalidTransaction::PriorityFeeGreaterThanMaxFee => Self::TipAboveFeeCap,
369 InvalidTransaction::GasPriceLessThanBasefee => Self::FeeCapTooLow,
370 InvalidTransaction::CallerGasLimitMoreThanBlock => {
371 Self::GasTooHigh(ErrDetail { detail: String::from("CallerGasLimitMoreThanBlock") })
372 }
373 InvalidTransaction::CallGasCostMoreThanGasLimit { .. } => {
374 Self::GasTooHigh(ErrDetail { detail: String::from("CallGasCostMoreThanGasLimit") })
375 }
376 InvalidTransaction::GasFloorMoreThanGasLimit { .. } => {
377 Self::GasTooHigh(ErrDetail { detail: String::from("GasFloorMoreThanGasLimit") })
378 }
379 InvalidTransaction::RejectCallerWithCode => Self::SenderNoEOA,
380 InvalidTransaction::LackOfFundForMaxFee { .. } => Self::InsufficientFunds,
381 InvalidTransaction::OverflowPaymentInTransaction => Self::GasUintOverflow,
382 InvalidTransaction::NonceOverflowInTransaction => Self::NonceMaxValue,
383 InvalidTransaction::CreateInitCodeSizeLimit => Self::MaxInitCodeSizeExceeded,
384 InvalidTransaction::NonceTooHigh { .. } => Self::NonceTooHigh,
385 InvalidTransaction::NonceTooLow { .. } => Self::NonceTooLow,
386 InvalidTransaction::AccessListNotSupported => Self::AccessListNotSupported,
387 InvalidTransaction::BlobGasPriceGreaterThanMax {
388 block_blob_gas_price,
389 tx_max_fee_per_blob_gas,
390 } => Self::BlobFeeCapTooLow(block_blob_gas_price, tx_max_fee_per_blob_gas),
391 InvalidTransaction::BlobVersionedHashesNotSupported => {
392 Self::BlobVersionedHashesNotSupported
393 }
394 InvalidTransaction::MaxFeePerBlobGasNotSupported => Self::MaxFeePerBlobGasNotSupported,
395 InvalidTransaction::BlobCreateTransaction => Self::BlobCreateTransaction,
396 InvalidTransaction::BlobVersionNotSupported => Self::BlobVersionNotSupported,
397 InvalidTransaction::EmptyBlobs => Self::EmptyBlobs,
398 InvalidTransaction::TooManyBlobs { have, max } => Self::TooManyBlobs(have, max),
399 InvalidTransaction::AuthorizationListNotSupported => {
400 Self::AuthorizationListNotSupported
401 }
402 InvalidTransaction::TxGasLimitGreaterThanCap { gas_limit, cap } => {
403 Self::TxGasLimitGreaterThanCap(gas_limit, cap)
404 }
405
406 InvalidTransaction::AuthorizationListInvalidFields
407 | InvalidTransaction::Eip1559NotSupported
408 | InvalidTransaction::Eip2930NotSupported
409 | InvalidTransaction::Eip4844NotSupported
410 | InvalidTransaction::Eip7702NotSupported
411 | InvalidTransaction::EmptyAuthorizationList
412 | InvalidTransaction::Eip7873NotSupported
413 | InvalidTransaction::Eip7873MissingTarget
414 | InvalidTransaction::MissingChainId
415 | InvalidTransaction::Str(_) => Self::Revm(err),
416 }
417 }
418}
419
420pub(crate) trait ToRpcResponseResult {
422 fn to_rpc_result(self) -> ResponseResult;
423}
424
425pub fn to_rpc_result<T: Serialize>(val: T) -> ResponseResult {
427 match serde_json::to_value(val) {
428 Ok(success) => ResponseResult::Success(success),
429 Err(err) => {
430 error!(%err, "Failed serialize rpc response");
431 ResponseResult::error(RpcError::internal_error())
432 }
433 }
434}
435
436impl<T: Serialize> ToRpcResponseResult for Result<T> {
437 fn to_rpc_result(self) -> ResponseResult {
438 match self {
439 Ok(val) => to_rpc_result(val),
440 Err(err) => match err {
441 BlockchainError::Pool(err) => {
442 error!(%err, "txpool error");
443 match err {
444 PoolError::CyclicTransaction => {
445 RpcError::transaction_rejected("Cyclic transaction detected")
446 }
447 PoolError::ReplacementUnderpriced(_) => {
448 RpcError::transaction_rejected("replacement transaction underpriced")
449 }
450 PoolError::AlreadyImported(_) => {
451 RpcError::transaction_rejected("transaction already imported")
452 }
453 }
454 }
455 BlockchainError::NoSignerAvailable => {
456 RpcError::invalid_params("No Signer available")
457 }
458 BlockchainError::ChainIdNotAvailable => {
459 RpcError::invalid_params("Chain Id not available")
460 }
461 BlockchainError::TransactionConfirmationTimeout { hash, .. } => RpcError {
462 code: ErrorCode::ServerError(4),
463 message: "Transaction confirmation timeout".into(),
464 data: Some(serde_json::Value::String(hash.to_string())),
465 },
466 BlockchainError::InvalidTransaction(err) => match err {
467 InvalidTransactionError::Revert(data) => {
468 let mut msg = "execution reverted".to_string();
470 if let Some(reason) = data
471 .as_ref()
472 .and_then(|data| RevertDecoder::new().maybe_decode(data, None))
473 {
474 msg = format!("{msg}: {reason}");
475 }
476 RpcError {
477 code: ErrorCode::ExecutionError,
479 message: msg.into(),
480 data: serde_json::to_value(data).ok(),
481 }
482 }
483 InvalidTransactionError::GasTooLow => {
484 RpcError {
486 code: ErrorCode::ServerError(-32000),
487 message: err.to_string().into(),
488 data: None,
489 }
490 }
491 InvalidTransactionError::GasTooHigh(_) => {
492 RpcError {
494 code: ErrorCode::ServerError(-32000),
495 message: err.to_string().into(),
496 data: None,
497 }
498 }
499 _ => RpcError::transaction_rejected(err.to_string()),
500 },
501 BlockchainError::FeeHistory(err) => RpcError::invalid_params(err.to_string()),
502 BlockchainError::EmptyRawTransactionData => {
503 RpcError::invalid_params("Empty transaction data")
504 }
505 BlockchainError::FailedToDecodeSignedTransaction => {
506 RpcError::invalid_params("Failed to decode transaction")
507 }
508 BlockchainError::FailedToDecodeTransaction => {
509 RpcError::invalid_params("Failed to decode transaction")
510 }
511 BlockchainError::FailedToDecodeReceipt => {
512 RpcError::invalid_params("Failed to decode receipt")
513 }
514 BlockchainError::UnsupportedTransactionEncoding(_) => {
515 RpcError::internal_error_with(err.to_string())
516 }
517 BlockchainError::FailedToDecodeStateDump => {
518 RpcError::invalid_params("Failed to decode state dump")
519 }
520 BlockchainError::SignerError(err) => RpcError::invalid_params(err.to_string()),
521 BlockchainError::SignatureError(err) => RpcError::invalid_params(err.to_string()),
522 BlockchainError::RpcUnimplemented => {
523 RpcError::internal_error_with("Not implemented")
524 }
525 BlockchainError::PrevrandaoNotSet => RpcError::internal_error_with(err.to_string()),
526 BlockchainError::RpcError(err) => err,
527 BlockchainError::InvalidFeeInput => RpcError::invalid_params(
528 "Invalid input: `max_priority_fee_per_gas` greater than `max_fee_per_gas`",
529 ),
530 BlockchainError::AlloyForkProvider(err) => {
531 error!(target: "backend", %err, "fork provider error");
532 match err {
533 TransportError::ErrorResp(err) => RpcError {
534 code: ErrorCode::from(err.code),
535 message: err.message,
536 data: err.data.and_then(|data| serde_json::to_value(data).ok()),
537 },
538 err => RpcError::internal_error_with(format!("Fork Error: {err:?}")),
539 }
540 }
541 err @ BlockchainError::EvmError(_) => RpcError {
542 code: ErrorCode::TransactionRejected,
545 message: err.to_string().into(),
546 data: None,
547 },
548 err @ BlockchainError::EvmOverrideError(_) => {
549 RpcError::invalid_params(err.to_string())
550 }
551 err @ BlockchainError::InvalidUrl(_) => RpcError::invalid_params(err.to_string()),
552 err @ BlockchainError::UnsupportedForkNetwork { .. } => {
553 RpcError::invalid_params(err.to_string())
554 }
555 BlockchainError::Internal(err) => RpcError::internal_error_with(err),
556 err @ BlockchainError::BlockOutOfRange(_, _) => {
557 RpcError::invalid_params(err.to_string())
558 }
559 err @ BlockchainError::BlockNotFound => RpcError {
560 code: ErrorCode::ServerError(-32001),
562 message: err.to_string().into(),
563 data: None,
564 },
565 err @ BlockchainError::TransactionNotFound => RpcError {
566 code: ErrorCode::ServerError(-32001),
567 message: err.to_string().into(),
568 data: None,
569 },
570 err @ BlockchainError::DataUnavailable => {
571 RpcError::internal_error_with(err.to_string())
572 }
573 err @ BlockchainError::TrieError(_) => {
574 RpcError::internal_error_with(err.to_string())
575 }
576 BlockchainError::UintConversion(err) => RpcError::invalid_params(err),
577 err @ BlockchainError::StateOverrideError(_) => {
578 RpcError::invalid_params(err.to_string())
579 }
580 err @ BlockchainError::TimestampError(_) => {
581 RpcError::invalid_params(err.to_string())
582 }
583 BlockchainError::DatabaseError(err) => {
584 RpcError::internal_error_with(err.to_string())
585 }
586 err @ BlockchainError::EIP1559TransactionUnsupportedAtHardfork => {
587 RpcError::invalid_params(err.to_string())
588 }
589 err @ BlockchainError::EIP2930TransactionUnsupportedAtHardfork => {
590 RpcError::invalid_params(err.to_string())
591 }
592 err @ BlockchainError::EIP4844TransactionUnsupportedAtHardfork => {
593 RpcError::invalid_params(err.to_string())
594 }
595 err @ BlockchainError::EIP7702TransactionUnsupportedAtHardfork => {
596 RpcError::invalid_params(err.to_string())
597 }
598 err @ BlockchainError::DepositTransactionUnsupported => {
599 RpcError::invalid_params(err.to_string())
600 }
601 err @ BlockchainError::TempoTransactionUnsupported => {
602 RpcError::invalid_params(err.to_string())
603 }
604 err @ BlockchainError::ExcessBlobGasNotSet => {
605 RpcError::invalid_params(err.to_string())
606 }
607 err @ BlockchainError::Message(_) => RpcError::internal_error_with(err.to_string()),
608 err @ BlockchainError::UnknownTransactionType => {
609 RpcError::invalid_params(err.to_string())
610 }
611 err @ BlockchainError::InvalidTransactionRequest(_) => {
612 RpcError::invalid_params(err.to_string())
613 }
614 err @ BlockchainError::RecoveryError(_) => {
615 RpcError::invalid_params(err.to_string())
616 }
617 BlockchainError::FilterNotFound => RpcError {
618 code: ErrorCode::ServerError(-32000),
619 message: "filter not found".into(),
620 data: None,
621 },
622 err @ BlockchainError::UnknownBlock => RpcError {
623 code: ErrorCode::ServerError(-32000),
624 message: err.to_string().into(),
625 data: None,
626 },
627 }
628 .into(),
629 }
630 }
631}