Skip to main content

near_kit/types/
error.rs

1//! Typed error types for NEAR transaction execution.
2//!
3//! These types mirror the error hierarchy returned by NEAR RPC in
4//! `ExecutionStatus::Failure`, replacing opaque `serde_json::Value`.
5
6use serde::Deserialize;
7
8use super::rpc::GlobalContractIdentifierView;
9use super::{AccountId, CryptoHash, Gas, NearToken, PublicKey};
10
11// ============================================================================
12// Top-level error
13// ============================================================================
14
15/// Error returned by NEAR RPC when a transaction or receipt fails.
16#[derive(Debug, Clone, Deserialize)]
17pub enum TxExecutionError {
18    /// An error happened during action execution.
19    ActionError(ActionError),
20    /// An error happened during transaction validation.
21    InvalidTxError(InvalidTxError),
22}
23
24impl std::fmt::Display for TxExecutionError {
25    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26        match self {
27            Self::ActionError(e) => write!(f, "ActionError: {e}"),
28            Self::InvalidTxError(e) => write!(f, "InvalidTxError: {e}"),
29        }
30    }
31}
32
33impl std::error::Error for TxExecutionError {}
34
35// ============================================================================
36// Action errors
37// ============================================================================
38
39/// An error that occurred during action execution.
40#[derive(Debug, Clone, Deserialize)]
41pub struct ActionError {
42    /// Index of the failed action in the transaction.
43    /// Not defined if kind is `ActionErrorKind::LackBalanceForState`.
44    #[serde(default)]
45    pub index: Option<u64>,
46    /// The kind of action error.
47    pub kind: ActionErrorKind,
48}
49
50impl std::fmt::Display for ActionError {
51    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52        match self.index {
53            Some(i) => write!(f, "action #{i}: {}", self.kind),
54            None => write!(f, "{}", self.kind),
55        }
56    }
57}
58
59/// Specific kind of action error.
60#[derive(Debug, Clone, Deserialize)]
61pub enum ActionErrorKind {
62    AccountAlreadyExists {
63        account_id: AccountId,
64    },
65    AccountDoesNotExist {
66        account_id: AccountId,
67    },
68    CreateAccountOnlyByRegistrar {
69        account_id: AccountId,
70        predecessor_id: AccountId,
71        registrar_account_id: AccountId,
72    },
73    CreateAccountNotAllowed {
74        account_id: AccountId,
75        predecessor_id: AccountId,
76    },
77    ActorNoPermission {
78        account_id: AccountId,
79        actor_id: AccountId,
80    },
81    DeleteKeyDoesNotExist {
82        account_id: AccountId,
83        public_key: PublicKey,
84    },
85    AddKeyAlreadyExists {
86        account_id: AccountId,
87        public_key: PublicKey,
88    },
89    DeleteAccountStaking {
90        account_id: AccountId,
91    },
92    LackBalanceForState {
93        account_id: AccountId,
94        amount: NearToken,
95    },
96    TriesToUnstake {
97        account_id: AccountId,
98    },
99    TriesToStake {
100        account_id: AccountId,
101        balance: NearToken,
102        locked: NearToken,
103        stake: NearToken,
104    },
105    InsufficientStake {
106        account_id: AccountId,
107        minimum_stake: NearToken,
108        stake: NearToken,
109    },
110    FunctionCallError(FunctionCallError),
111    NewReceiptValidationError(ReceiptValidationError),
112    OnlyImplicitAccountCreationAllowed {
113        account_id: AccountId,
114    },
115    DeleteAccountWithLargeState {
116        account_id: AccountId,
117    },
118    DelegateActionInvalidSignature,
119    DelegateActionSenderDoesNotMatchTxReceiver {
120        receiver_id: AccountId,
121        sender_id: AccountId,
122    },
123    DelegateActionExpired,
124    DelegateActionAccessKeyError(InvalidAccessKeyError),
125    DelegateActionInvalidNonce {
126        ak_nonce: u64,
127        delegate_nonce: u64,
128    },
129    DelegateActionNonceTooLarge {
130        delegate_nonce: u64,
131        upper_bound: u64,
132    },
133    GlobalContractDoesNotExist {
134        identifier: GlobalContractIdentifierView,
135    },
136    GasKeyDoesNotExist {
137        account_id: AccountId,
138        public_key: PublicKey,
139    },
140    InsufficientGasKeyBalance {
141        account_id: AccountId,
142        balance: NearToken,
143        public_key: PublicKey,
144        required: NearToken,
145    },
146    GasKeyBalanceTooHigh {
147        account_id: AccountId,
148        balance: NearToken,
149        #[serde(default)]
150        public_key: Option<PublicKey>,
151    },
152}
153
154impl std::fmt::Display for ActionErrorKind {
155    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
156        match self {
157            Self::AccountAlreadyExists { account_id } => {
158                write!(f, "account {account_id} already exists")
159            }
160            Self::AccountDoesNotExist { account_id } => {
161                write!(f, "account {account_id} does not exist")
162            }
163            Self::CreateAccountOnlyByRegistrar {
164                account_id,
165                predecessor_id,
166                registrar_account_id,
167            } => write!(
168                f,
169                "top-level account {account_id} can only be created by registrar {registrar_account_id}, not by {predecessor_id}"
170            ),
171            Self::CreateAccountNotAllowed {
172                account_id,
173                predecessor_id,
174            } => write!(
175                f,
176                "account {account_id} must be under a namespace of the creator account {predecessor_id}"
177            ),
178            Self::ActorNoPermission {
179                account_id,
180                actor_id,
181            } => write!(
182                f,
183                "actor {actor_id} does not have permission to act on account {account_id}"
184            ),
185            Self::DeleteKeyDoesNotExist {
186                account_id,
187                public_key,
188            } => write!(
189                f,
190                "account {account_id} tries to remove an access key {public_key} that doesn't exist"
191            ),
192            Self::AddKeyAlreadyExists {
193                account_id,
194                public_key,
195            } => write!(
196                f,
197                "public key {public_key} already exists for account {account_id}"
198            ),
199            Self::DeleteAccountStaking { account_id } => {
200                write!(f, "account {account_id} is staking and cannot be deleted")
201            }
202            Self::LackBalanceForState { account_id, amount } => write!(
203                f,
204                "account {account_id} needs {} to cover storage cost",
205                amount.exact_amount_display()
206            ),
207            Self::TriesToUnstake { account_id } => {
208                write!(f, "account {account_id} is not staked but tries to unstake")
209            }
210            Self::TriesToStake {
211                account_id,
212                stake,
213                balance,
214                ..
215            } => write!(
216                f,
217                "account {account_id} doesn't have enough balance ({}) to increase the stake ({})",
218                balance.exact_amount_display(),
219                stake.exact_amount_display()
220            ),
221            Self::InsufficientStake {
222                account_id,
223                stake,
224                minimum_stake,
225            } => write!(
226                f,
227                "account {account_id} has insufficient stake ({}), minimum required is {}",
228                stake.exact_amount_display(),
229                minimum_stake.exact_amount_display()
230            ),
231            Self::FunctionCallError(e) => write!(f, "{e}"),
232            Self::NewReceiptValidationError(e) => {
233                write!(f, "receipt validation error: {e}")
234            }
235            Self::OnlyImplicitAccountCreationAllowed { account_id } => write!(
236                f,
237                "only implicit account creation is allowed for {account_id}"
238            ),
239            Self::DeleteAccountWithLargeState { account_id } => write!(
240                f,
241                "deleting account {account_id} with large state is temporarily banned"
242            ),
243            Self::DelegateActionInvalidSignature => {
244                write!(f, "invalid signature on delegate action")
245            }
246            Self::DelegateActionSenderDoesNotMatchTxReceiver {
247                sender_id,
248                receiver_id,
249            } => write!(
250                f,
251                "delegate action sender {sender_id} does not match transaction receiver {receiver_id}"
252            ),
253            Self::DelegateActionExpired => write!(f, "delegate action has expired"),
254            Self::DelegateActionAccessKeyError(e) => {
255                write!(f, "delegate action access key error: {e}")
256            }
257            Self::DelegateActionInvalidNonce {
258                delegate_nonce,
259                ak_nonce,
260            } => write!(
261                f,
262                "delegate action nonce {delegate_nonce} must be larger than access key nonce {ak_nonce}"
263            ),
264            Self::DelegateActionNonceTooLarge {
265                delegate_nonce,
266                upper_bound,
267            } => write!(
268                f,
269                "delegate action nonce {delegate_nonce} is larger than the upper bound {upper_bound}"
270            ),
271            Self::GlobalContractDoesNotExist { identifier } => {
272                write!(f, "global contract {identifier} does not exist")
273            }
274            Self::GasKeyDoesNotExist {
275                account_id,
276                public_key,
277            } => write!(
278                f,
279                "gas key {public_key} does not exist for account {account_id}"
280            ),
281            Self::InsufficientGasKeyBalance {
282                account_id,
283                public_key,
284                balance,
285                required,
286            } => write!(
287                f,
288                "gas key {public_key} for account {account_id} has insufficient balance ({}, required: {})",
289                balance.exact_amount_display(),
290                required.exact_amount_display()
291            ),
292            Self::GasKeyBalanceTooHigh {
293                account_id,
294                public_key,
295                balance,
296            } => {
297                let key_info = match public_key {
298                    Some(pk) => format!("gas key {pk}"),
299                    None => "gas keys".to_string(),
300                };
301                write!(
302                    f,
303                    "balance ({}) of {key_info} for account {account_id} is too high",
304                    balance.exact_amount_display()
305                )
306            }
307        }
308    }
309}
310
311// ============================================================================
312// Transaction validation errors
313// ============================================================================
314
315/// An error during transaction validation (before execution).
316#[derive(Debug, Clone, Deserialize)]
317pub enum InvalidTxError {
318    InvalidAccessKeyError(InvalidAccessKeyError),
319    InvalidSignerId {
320        signer_id: String,
321    },
322    SignerDoesNotExist {
323        signer_id: AccountId,
324    },
325    InvalidNonce {
326        ak_nonce: u64,
327        tx_nonce: u64,
328    },
329    NonceTooLarge {
330        tx_nonce: u64,
331        upper_bound: u64,
332    },
333    InvalidReceiverId {
334        receiver_id: String,
335    },
336    InvalidSignature,
337    NotEnoughBalance {
338        balance: NearToken,
339        cost: NearToken,
340        signer_id: AccountId,
341    },
342    LackBalanceForState {
343        amount: NearToken,
344        signer_id: AccountId,
345    },
346    CostOverflow,
347    InvalidChain,
348    Expired,
349    ActionsValidation(ActionsValidationError),
350    TransactionSizeExceeded {
351        limit: u64,
352        size: u64,
353    },
354    InvalidTransactionVersion,
355    StorageError(StorageError),
356    ShardCongested {
357        congestion_level: f64,
358        shard_id: u64,
359    },
360    ShardStuck {
361        missed_chunks: u64,
362        shard_id: u64,
363    },
364    InvalidNonceIndex {
365        num_nonces: u16,
366        #[serde(default)]
367        tx_nonce_index: Option<u16>,
368    },
369    NotEnoughGasKeyBalance {
370        balance: NearToken,
371        cost: NearToken,
372        signer_id: AccountId,
373    },
374    NotEnoughBalanceForDeposit {
375        balance: NearToken,
376        cost: NearToken,
377        reason: DepositCostFailureReason,
378        signer_id: AccountId,
379    },
380}
381
382impl InvalidTxError {
383    /// Returns `true` if this error is transient and the transaction may
384    /// succeed on retry (e.g. invalid nonce, shard congestion).
385    pub fn is_retryable(&self) -> bool {
386        matches!(
387            self,
388            InvalidTxError::InvalidNonce { .. }
389                | InvalidTxError::ShardCongested { .. }
390                | InvalidTxError::ShardStuck { .. }
391        )
392    }
393}
394
395impl std::fmt::Display for InvalidTxError {
396    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
397        match self {
398            Self::InvalidAccessKeyError(e) => write!(f, "{e}"),
399            Self::InvalidSignerId { signer_id } => {
400                write!(f, "signer ID {signer_id} is not a valid account ID")
401            }
402            Self::SignerDoesNotExist { signer_id } => {
403                write!(f, "signer account {signer_id} does not exist")
404            }
405            Self::InvalidNonce {
406                ak_nonce, tx_nonce, ..
407            } => write!(
408                f,
409                "transaction nonce {tx_nonce} must be larger than access key nonce {ak_nonce}"
410            ),
411            Self::NonceTooLarge {
412                tx_nonce,
413                upper_bound,
414            } => write!(
415                f,
416                "transaction nonce {tx_nonce} is larger than the upper bound {upper_bound}"
417            ),
418            Self::InvalidReceiverId { receiver_id } => {
419                write!(f, "receiver ID {receiver_id} is not a valid account ID")
420            }
421            Self::InvalidSignature => write!(f, "transaction signature is not valid"),
422            Self::NotEnoughBalance {
423                signer_id,
424                balance,
425                cost,
426            } => write!(
427                f,
428                "account {signer_id} does not have enough balance ({}) to cover transaction cost ({})",
429                balance.exact_amount_display(),
430                cost.exact_amount_display()
431            ),
432            Self::LackBalanceForState { signer_id, amount } => write!(
433                f,
434                "account {signer_id} doesn't have enough balance ({}) after transaction",
435                amount.exact_amount_display()
436            ),
437            Self::CostOverflow => {
438                write!(f, "integer overflow during transaction cost estimation")
439            }
440            Self::InvalidChain => write!(
441                f,
442                "transaction parent block hash doesn't belong to the current chain"
443            ),
444            Self::Expired => write!(f, "transaction has expired"),
445            Self::ActionsValidation(e) => write!(f, "{e}"),
446            Self::TransactionSizeExceeded { size, limit } => write!(
447                f,
448                "serialized transaction size ({size}) exceeded the limit ({limit})"
449            ),
450            Self::InvalidTransactionVersion => write!(f, "invalid transaction version"),
451            Self::StorageError(e) => write!(f, "storage error: {e}"),
452            Self::ShardCongested {
453                shard_id,
454                congestion_level,
455            } => write!(
456                f,
457                "shard {shard_id} is too congested ({congestion_level:.2}/1.00) to accept new transactions"
458            ),
459            Self::ShardStuck {
460                shard_id,
461                missed_chunks,
462            } => write!(
463                f,
464                "shard {shard_id} is {missed_chunks} blocks behind and can't accept transactions"
465            ),
466            Self::InvalidNonceIndex {
467                tx_nonce_index,
468                num_nonces,
469            } => match tx_nonce_index {
470                Some(idx) => write!(
471                    f,
472                    "invalid nonce index {idx} for key with {num_nonces} nonces"
473                ),
474                None => write!(f, "missing nonce index for key with {num_nonces} nonces"),
475            },
476            Self::NotEnoughGasKeyBalance {
477                signer_id,
478                balance,
479                cost,
480            } => write!(
481                f,
482                "gas key for {signer_id} does not have enough balance ({}) for gas cost ({})",
483                balance.exact_amount_display(),
484                cost.exact_amount_display()
485            ),
486            Self::NotEnoughBalanceForDeposit {
487                signer_id,
488                balance,
489                cost,
490                reason,
491            } => write!(
492                f,
493                "signer {signer_id} does not have enough balance ({}) to cover deposit cost ({}): {reason}",
494                balance.exact_amount_display(),
495                cost.exact_amount_display()
496            ),
497        }
498    }
499}
500
501// ============================================================================
502// Access key errors
503// ============================================================================
504
505/// Error related to access key validation.
506#[derive(Debug, Clone, Deserialize)]
507pub enum InvalidAccessKeyError {
508    AccessKeyNotFound {
509        account_id: AccountId,
510        public_key: PublicKey,
511    },
512    ReceiverMismatch {
513        ak_receiver: String,
514        tx_receiver: AccountId,
515    },
516    MethodNameMismatch {
517        method_name: String,
518    },
519    RequiresFullAccess,
520    NotEnoughAllowance {
521        account_id: AccountId,
522        allowance: NearToken,
523        cost: NearToken,
524        public_key: PublicKey,
525    },
526    DepositWithFunctionCall,
527}
528
529impl std::fmt::Display for InvalidAccessKeyError {
530    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
531        match self {
532            Self::AccessKeyNotFound {
533                account_id,
534                public_key,
535            } => write!(
536                f,
537                "public key {public_key} doesn't exist for account {account_id}"
538            ),
539            Self::ReceiverMismatch {
540                ak_receiver,
541                tx_receiver,
542            } => write!(
543                f,
544                "transaction receiver {tx_receiver} doesn't match access key receiver {ak_receiver}"
545            ),
546            Self::MethodNameMismatch { method_name } => {
547                write!(f, "method {method_name} isn't allowed by the access key")
548            }
549            Self::RequiresFullAccess => {
550                write!(f, "transaction requires a full access key")
551            }
552            Self::NotEnoughAllowance {
553                account_id,
554                public_key,
555                allowance,
556                cost,
557            } => write!(
558                f,
559                "access key {public_key} for account {account_id} does not have enough allowance ({}) to cover transaction cost ({})",
560                allowance.exact_amount_display(),
561                cost.exact_amount_display()
562            ),
563            Self::DepositWithFunctionCall => {
564                write!(f, "deposits are not allowed with function call access keys")
565            }
566        }
567    }
568}
569
570// ============================================================================
571// Function call errors (VM / contract)
572// ============================================================================
573
574/// An error during function call execution.
575#[derive(Debug, Clone, Deserialize)]
576pub enum FunctionCallError {
577    WasmUnknownError,
578    #[serde(rename = "_EVMError")]
579    EvmError,
580    CompilationError(CompilationError),
581    LinkError {
582        msg: String,
583    },
584    MethodResolveError(MethodResolveError),
585    WasmTrap(WasmTrap),
586    HostError(HostError),
587    ExecutionError(String),
588}
589
590impl std::fmt::Display for FunctionCallError {
591    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
592        match self {
593            Self::WasmUnknownError => write!(f, "unknown Wasm error"),
594            Self::EvmError => write!(f, "EVM error"),
595            Self::CompilationError(e) => write!(f, "compilation error: {e}"),
596            Self::LinkError { msg } => write!(f, "link error: {msg}"),
597            Self::MethodResolveError(e) => write!(f, "method resolve error: {e}"),
598            Self::WasmTrap(e) => write!(f, "Wasm trap: {e}"),
599            Self::HostError(e) => write!(f, "host error: {e}"),
600            Self::ExecutionError(msg) => write!(f, "execution error: {msg}"),
601        }
602    }
603}
604
605/// Wasm compilation error.
606#[derive(Debug, Clone, Deserialize)]
607pub enum CompilationError {
608    CodeDoesNotExist { account_id: AccountId },
609    PrepareError(PrepareError),
610    WasmerCompileError { msg: String },
611}
612
613impl std::fmt::Display for CompilationError {
614    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
615        match self {
616            Self::CodeDoesNotExist { account_id } => {
617                write!(f, "contract code does not exist for account {account_id}")
618            }
619            Self::PrepareError(e) => write!(f, "{e}"),
620            Self::WasmerCompileError { msg } => write!(f, "Wasmer compilation error: {msg}"),
621        }
622    }
623}
624
625/// Error preparing a Wasm module.
626#[derive(Debug, Clone, Deserialize)]
627pub enum PrepareError {
628    Serialization,
629    Deserialization,
630    InternalMemoryDeclared,
631    GasInstrumentation,
632    StackHeightInstrumentation,
633    Instantiate,
634    Memory,
635    TooManyFunctions,
636    TooManyLocals,
637    TooManyTables,
638    TooManyTableElements,
639}
640
641impl std::fmt::Display for PrepareError {
642    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
643        match self {
644            Self::Serialization => write!(f, "Wasm serialization error"),
645            Self::Deserialization => write!(f, "Wasm deserialization error"),
646            Self::InternalMemoryDeclared => {
647                write!(f, "Wasm module declares internal memory")
648            }
649            Self::GasInstrumentation => write!(f, "gas instrumentation failed"),
650            Self::StackHeightInstrumentation => {
651                write!(f, "stack height instrumentation failed")
652            }
653            Self::Instantiate => write!(f, "Wasm instantiation error"),
654            Self::Memory => write!(f, "Wasm memory error"),
655            Self::TooManyFunctions => write!(f, "too many functions in Wasm module"),
656            Self::TooManyLocals => write!(f, "too many locals in Wasm module"),
657            Self::TooManyTables => write!(f, "too many tables in Wasm module"),
658            Self::TooManyTableElements => {
659                write!(f, "too many table elements in Wasm module")
660            }
661        }
662    }
663}
664
665/// Error resolving a method in Wasm.
666#[derive(Debug, Clone, Deserialize)]
667pub enum MethodResolveError {
668    MethodEmptyName,
669    MethodNotFound,
670    MethodInvalidSignature,
671}
672
673impl std::fmt::Display for MethodResolveError {
674    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
675        match self {
676            Self::MethodEmptyName => write!(f, "method name is empty"),
677            Self::MethodNotFound => write!(f, "method not found in contract"),
678            Self::MethodInvalidSignature => write!(f, "method has an invalid signature"),
679        }
680    }
681}
682
683/// A trap during Wasm execution.
684#[derive(Debug, Clone, Deserialize)]
685pub enum WasmTrap {
686    Unreachable,
687    IncorrectCallIndirectSignature,
688    MemoryOutOfBounds,
689    #[serde(rename = "CallIndirectOOB")]
690    CallIndirectOob,
691    IllegalArithmetic,
692    MisalignedAtomicAccess,
693    IndirectCallToNull,
694    StackOverflow,
695    GenericTrap,
696}
697
698impl std::fmt::Display for WasmTrap {
699    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
700        match self {
701            Self::Unreachable => write!(f, "unreachable instruction executed"),
702            Self::IncorrectCallIndirectSignature => {
703                write!(f, "incorrect call indirect signature")
704            }
705            Self::MemoryOutOfBounds => write!(f, "memory out of bounds"),
706            Self::CallIndirectOob => write!(f, "call indirect out of bounds"),
707            Self::IllegalArithmetic => write!(f, "illegal arithmetic operation"),
708            Self::MisalignedAtomicAccess => write!(f, "misaligned atomic access"),
709            Self::IndirectCallToNull => write!(f, "indirect call to null"),
710            Self::StackOverflow => write!(f, "stack overflow"),
711            Self::GenericTrap => write!(f, "generic trap"),
712        }
713    }
714}
715
716/// Error from a host function call.
717#[derive(Debug, Clone, Deserialize)]
718pub enum HostError {
719    #[serde(rename = "BadUTF16")]
720    BadUtf16,
721    #[serde(rename = "BadUTF8")]
722    BadUtf8,
723    GasExceeded,
724    GasLimitExceeded,
725    BalanceExceeded,
726    EmptyMethodName,
727    GuestPanic {
728        panic_msg: String,
729    },
730    IntegerOverflow,
731    InvalidPromiseIndex {
732        promise_idx: u64,
733    },
734    CannotAppendActionToJointPromise,
735    CannotReturnJointPromise,
736    InvalidPromiseResultIndex {
737        result_idx: u64,
738    },
739    InvalidRegisterId {
740        register_id: u64,
741    },
742    IteratorWasInvalidated {
743        iterator_index: u64,
744    },
745    MemoryAccessViolation,
746    InvalidReceiptIndex {
747        receipt_index: u64,
748    },
749    InvalidIteratorIndex {
750        iterator_index: u64,
751    },
752    InvalidAccountId,
753    InvalidMethodName,
754    InvalidPublicKey,
755    ProhibitedInView {
756        method_name: String,
757    },
758    NumberOfLogsExceeded {
759        limit: u64,
760    },
761    KeyLengthExceeded {
762        length: u64,
763        limit: u64,
764    },
765    ValueLengthExceeded {
766        length: u64,
767        limit: u64,
768    },
769    TotalLogLengthExceeded {
770        length: u64,
771        limit: u64,
772    },
773    NumberPromisesExceeded {
774        limit: u64,
775        number_of_promises: u64,
776    },
777    NumberInputDataDependenciesExceeded {
778        limit: u64,
779        number_of_input_data_dependencies: u64,
780    },
781    ReturnedValueLengthExceeded {
782        length: u64,
783        limit: u64,
784    },
785    ContractSizeExceeded {
786        limit: u64,
787        size: u64,
788    },
789    Deprecated {
790        method_name: String,
791    },
792    #[serde(rename = "ECRecoverError")]
793    EcRecoverError {
794        msg: String,
795    },
796    AltBn128InvalidInput {
797        msg: String,
798    },
799    Ed25519VerifyInvalidInput {
800        msg: String,
801    },
802}
803
804impl std::fmt::Display for HostError {
805    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
806        match self {
807            Self::BadUtf16 => write!(f, "bad UTF-16 string"),
808            Self::BadUtf8 => write!(f, "bad UTF-8 string"),
809            Self::GasExceeded => write!(f, "gas exceeded"),
810            Self::GasLimitExceeded => write!(f, "gas limit exceeded"),
811            Self::BalanceExceeded => write!(f, "balance exceeded"),
812            Self::EmptyMethodName => write!(f, "method name is empty"),
813            Self::GuestPanic { panic_msg } => write!(f, "smart contract panicked: {panic_msg}"),
814            Self::IntegerOverflow => write!(f, "integer overflow"),
815            Self::InvalidPromiseIndex { promise_idx } => {
816                write!(f, "invalid promise index {promise_idx}")
817            }
818            Self::CannotAppendActionToJointPromise => {
819                write!(f, "cannot append action to joint promise")
820            }
821            Self::CannotReturnJointPromise => write!(f, "cannot return joint promise"),
822            Self::InvalidPromiseResultIndex { result_idx } => {
823                write!(f, "invalid promise result index {result_idx}")
824            }
825            Self::InvalidRegisterId { register_id } => {
826                write!(f, "invalid register ID {register_id}")
827            }
828            Self::IteratorWasInvalidated { iterator_index } => {
829                write!(f, "iterator {iterator_index} was invalidated")
830            }
831            Self::MemoryAccessViolation => write!(f, "memory access violation"),
832            Self::InvalidReceiptIndex { receipt_index } => {
833                write!(f, "invalid receipt index {receipt_index}")
834            }
835            Self::InvalidIteratorIndex { iterator_index } => {
836                write!(f, "invalid iterator index {iterator_index}")
837            }
838            Self::InvalidAccountId => write!(f, "invalid account ID"),
839            Self::InvalidMethodName => write!(f, "invalid method name"),
840            Self::InvalidPublicKey => write!(f, "invalid public key"),
841            Self::ProhibitedInView { method_name } => {
842                write!(f, "method {method_name} is not allowed in a view call")
843            }
844            Self::NumberOfLogsExceeded { limit } => {
845                write!(f, "number of logs exceeded the limit of {limit}")
846            }
847            Self::KeyLengthExceeded { length, limit } => {
848                write!(f, "key length {length} exceeded the limit of {limit}")
849            }
850            Self::ValueLengthExceeded { length, limit } => {
851                write!(f, "value length {length} exceeded the limit of {limit}")
852            }
853            Self::TotalLogLengthExceeded { length, limit } => {
854                write!(f, "total log length {length} exceeded the limit of {limit}")
855            }
856            Self::NumberPromisesExceeded {
857                number_of_promises,
858                limit,
859            } => write!(
860                f,
861                "number of promises {number_of_promises} exceeded the limit of {limit}"
862            ),
863            Self::NumberInputDataDependenciesExceeded {
864                number_of_input_data_dependencies,
865                limit,
866            } => write!(
867                f,
868                "number of input data dependencies {number_of_input_data_dependencies} exceeded the limit of {limit}"
869            ),
870            Self::ReturnedValueLengthExceeded { length, limit } => {
871                write!(
872                    f,
873                    "returned value length {length} exceeded the limit of {limit}"
874                )
875            }
876            Self::ContractSizeExceeded { size, limit } => {
877                write!(f, "contract size {size} exceeded the limit of {limit}")
878            }
879            Self::Deprecated { method_name } => {
880                write!(f, "method {method_name} is deprecated")
881            }
882            Self::EcRecoverError { msg } => write!(f, "EC recover error: {msg}"),
883            Self::AltBn128InvalidInput { msg } => {
884                write!(f, "AltBn128 invalid input: {msg}")
885            }
886            Self::Ed25519VerifyInvalidInput { msg } => {
887                write!(f, "Ed25519 verification invalid input: {msg}")
888            }
889        }
890    }
891}
892
893// ============================================================================
894// Actions validation errors
895// ============================================================================
896
897/// Error validating actions in a transaction or receipt.
898#[derive(Debug, Clone, Deserialize)]
899pub enum ActionsValidationError {
900    DeleteActionMustBeFinal,
901    TotalPrepaidGasExceeded {
902        limit: Gas,
903        total_prepaid_gas: Gas,
904    },
905    TotalNumberOfActionsExceeded {
906        limit: u64,
907        total_number_of_actions: u64,
908    },
909    AddKeyMethodNamesNumberOfBytesExceeded {
910        limit: u64,
911        total_number_of_bytes: u64,
912    },
913    AddKeyMethodNameLengthExceeded {
914        length: u64,
915        limit: u64,
916    },
917    IntegerOverflow,
918    InvalidAccountId {
919        account_id: String,
920    },
921    ContractSizeExceeded {
922        limit: u64,
923        size: u64,
924    },
925    FunctionCallMethodNameLengthExceeded {
926        length: u64,
927        limit: u64,
928    },
929    FunctionCallArgumentsLengthExceeded {
930        length: u64,
931        limit: u64,
932    },
933    UnsuitableStakingKey {
934        public_key: PublicKey,
935    },
936    FunctionCallZeroAttachedGas,
937    DelegateActionMustBeOnlyOne,
938    UnsupportedProtocolFeature {
939        protocol_feature: String,
940        version: u32,
941    },
942    InvalidDeterministicStateInitReceiver {
943        derived_id: AccountId,
944        receiver_id: AccountId,
945    },
946    DeterministicStateInitKeyLengthExceeded {
947        length: u64,
948        limit: u64,
949    },
950    DeterministicStateInitValueLengthExceeded {
951        length: u64,
952        limit: u64,
953    },
954    GasKeyInvalidNumNonces {
955        limit: u16,
956        requested_nonces: u16,
957    },
958    AddGasKeyWithNonZeroBalance {
959        balance: NearToken,
960    },
961    GasKeyFunctionCallAllowanceNotAllowed,
962}
963
964impl std::fmt::Display for ActionsValidationError {
965    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
966        match self {
967            Self::DeleteActionMustBeFinal => {
968                write!(f, "delete action must be the final action in a transaction")
969            }
970            Self::TotalPrepaidGasExceeded {
971                total_prepaid_gas,
972                limit,
973            } => write!(
974                f,
975                "total prepaid gas ({total_prepaid_gas}) exceeded the limit ({limit})"
976            ),
977            Self::TotalNumberOfActionsExceeded {
978                total_number_of_actions,
979                limit,
980            } => write!(
981                f,
982                "number of actions ({total_number_of_actions}) exceeded the limit ({limit})"
983            ),
984            Self::AddKeyMethodNamesNumberOfBytesExceeded {
985                total_number_of_bytes,
986                limit,
987            } => write!(
988                f,
989                "total size of method names ({total_number_of_bytes} bytes) exceeded the limit ({limit}) in add key action"
990            ),
991            Self::AddKeyMethodNameLengthExceeded { length, limit } => write!(
992                f,
993                "method name length ({length}) exceeded the limit ({limit}) in add key action"
994            ),
995            Self::IntegerOverflow => write!(f, "integer overflow"),
996            Self::InvalidAccountId { account_id } => {
997                write!(f, "invalid account ID {account_id}")
998            }
999            Self::ContractSizeExceeded { size, limit } => write!(
1000                f,
1001                "contract size ({size}) exceeded the limit ({limit}) in deploy action"
1002            ),
1003            Self::FunctionCallMethodNameLengthExceeded { length, limit } => write!(
1004                f,
1005                "method name length ({length}) exceeded the limit ({limit}) in function call action"
1006            ),
1007            Self::FunctionCallArgumentsLengthExceeded { length, limit } => write!(
1008                f,
1009                "arguments length ({length}) exceeded the limit ({limit}) in function call action"
1010            ),
1011            Self::UnsuitableStakingKey { public_key } => {
1012                write!(f, "public key {public_key} is not suitable for staking")
1013            }
1014            Self::FunctionCallZeroAttachedGas => {
1015                write!(
1016                    f,
1017                    "function call must have a positive amount of gas attached"
1018                )
1019            }
1020            Self::DelegateActionMustBeOnlyOne => {
1021                write!(f, "transaction must contain only one delegate action")
1022            }
1023            Self::UnsupportedProtocolFeature {
1024                protocol_feature,
1025                version,
1026            } => write!(
1027                f,
1028                "protocol feature {protocol_feature} is unsupported in version {version}"
1029            ),
1030            Self::InvalidDeterministicStateInitReceiver {
1031                derived_id,
1032                receiver_id,
1033            } => write!(
1034                f,
1035                "invalid receiver {receiver_id} for deterministic account {derived_id}"
1036            ),
1037            Self::DeterministicStateInitKeyLengthExceeded { length, limit } => write!(
1038                f,
1039                "deterministic state init key length ({length}) exceeded the limit ({limit})"
1040            ),
1041            Self::DeterministicStateInitValueLengthExceeded { length, limit } => write!(
1042                f,
1043                "deterministic state init value length ({length}) exceeded the limit ({limit})"
1044            ),
1045            Self::GasKeyInvalidNumNonces {
1046                requested_nonces,
1047                limit,
1048            } => write!(
1049                f,
1050                "gas key requested invalid number of nonces: {requested_nonces} (must be between 1 and {limit})"
1051            ),
1052            Self::AddGasKeyWithNonZeroBalance { balance } => write!(
1053                f,
1054                "adding a gas key with non-zero balance ({}) is not allowed",
1055                balance.exact_amount_display()
1056            ),
1057            Self::GasKeyFunctionCallAllowanceNotAllowed => write!(
1058                f,
1059                "gas keys with function call permission cannot have an allowance"
1060            ),
1061        }
1062    }
1063}
1064
1065// ============================================================================
1066// Receipt validation errors
1067// ============================================================================
1068
1069/// Error validating a receipt.
1070#[derive(Debug, Clone, Deserialize)]
1071pub enum ReceiptValidationError {
1072    InvalidPredecessorId {
1073        account_id: String,
1074    },
1075    InvalidReceiverId {
1076        account_id: String,
1077    },
1078    InvalidSignerId {
1079        account_id: String,
1080    },
1081    InvalidDataReceiverId {
1082        account_id: String,
1083    },
1084    ReturnedValueLengthExceeded {
1085        length: u64,
1086        limit: u64,
1087    },
1088    NumberInputDataDependenciesExceeded {
1089        limit: u64,
1090        number_of_input_data_dependencies: u64,
1091    },
1092    ActionsValidation(ActionsValidationError),
1093    ReceiptSizeExceeded {
1094        limit: u64,
1095        size: u64,
1096    },
1097    InvalidRefundTo {
1098        account_id: String,
1099    },
1100}
1101
1102impl std::fmt::Display for ReceiptValidationError {
1103    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1104        match self {
1105            Self::InvalidPredecessorId { account_id } => {
1106                write!(f, "invalid predecessor ID {account_id}")
1107            }
1108            Self::InvalidReceiverId { account_id } => {
1109                write!(f, "invalid receiver ID {account_id}")
1110            }
1111            Self::InvalidSignerId { account_id } => {
1112                write!(f, "invalid signer ID {account_id}")
1113            }
1114            Self::InvalidDataReceiverId { account_id } => {
1115                write!(f, "invalid data receiver ID {account_id}")
1116            }
1117            Self::ReturnedValueLengthExceeded { length, limit } => write!(
1118                f,
1119                "returned value length ({length}) exceeded the limit ({limit})"
1120            ),
1121            Self::NumberInputDataDependenciesExceeded {
1122                number_of_input_data_dependencies,
1123                limit,
1124            } => write!(
1125                f,
1126                "number of input data dependencies ({number_of_input_data_dependencies}) exceeded the limit ({limit})"
1127            ),
1128            Self::ActionsValidation(e) => write!(f, "{e}"),
1129            Self::ReceiptSizeExceeded { size, limit } => {
1130                write!(f, "receipt size ({size}) exceeded the limit ({limit})")
1131            }
1132            Self::InvalidRefundTo { account_id } => {
1133                write!(f, "invalid refund-to account ID {account_id}")
1134            }
1135        }
1136    }
1137}
1138
1139// ============================================================================
1140// Storage errors
1141// ============================================================================
1142
1143/// Internal storage error.
1144#[derive(Debug, Clone, Deserialize)]
1145pub enum StorageError {
1146    StorageInternalError,
1147    MissingTrieValue(MissingTrieValue),
1148    UnexpectedTrieValue,
1149    StorageInconsistentState(String),
1150    FlatStorageBlockNotSupported(String),
1151    MemTrieLoadingError(String),
1152}
1153
1154/// Details about a missing trie value.
1155#[derive(Debug, Clone, Deserialize)]
1156pub struct MissingTrieValue {
1157    pub context: MissingTrieValueContext,
1158    pub hash: CryptoHash,
1159}
1160
1161/// Context in which a trie value was missing.
1162#[derive(Debug, Clone, Deserialize)]
1163#[allow(clippy::enum_variant_names)] // Matches nearcore naming
1164pub enum MissingTrieValueContext {
1165    TrieIterator,
1166    TriePrefetchingStorage,
1167    TrieMemoryPartialStorage,
1168    TrieStorage,
1169}
1170
1171impl std::fmt::Display for MissingTrieValueContext {
1172    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1173        match self {
1174            Self::TrieIterator => write!(f, "trie iterator"),
1175            Self::TriePrefetchingStorage => write!(f, "prefetching storage"),
1176            Self::TrieMemoryPartialStorage => write!(f, "memory partial storage"),
1177            Self::TrieStorage => write!(f, "trie storage"),
1178        }
1179    }
1180}
1181
1182impl std::fmt::Display for StorageError {
1183    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1184        match self {
1185            Self::StorageInternalError => write!(f, "internal storage error"),
1186            Self::MissingTrieValue(v) => {
1187                write!(f, "missing trie value {} in {}", v.hash, v.context)
1188            }
1189            Self::UnexpectedTrieValue => write!(f, "unexpected trie value"),
1190            Self::StorageInconsistentState(msg) => {
1191                write!(f, "storage is in an inconsistent state: {msg}")
1192            }
1193            Self::FlatStorageBlockNotSupported(msg) => {
1194                write!(f, "block is not supported by flat storage: {msg}")
1195            }
1196            Self::MemTrieLoadingError(msg) => {
1197                write!(f, "trie is not loaded in memory: {msg}")
1198            }
1199        }
1200    }
1201}
1202
1203// ============================================================================
1204// Misc
1205// ============================================================================
1206
1207/// Reason a deposit cost check failed on a gas key transaction.
1208#[derive(Debug, Clone, Deserialize)]
1209pub enum DepositCostFailureReason {
1210    NotEnoughBalance,
1211    LackBalanceForState,
1212}
1213
1214impl std::fmt::Display for DepositCostFailureReason {
1215    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1216        match self {
1217            Self::NotEnoughBalance => write!(f, "not enough balance"),
1218            Self::LackBalanceForState => write!(f, "not enough balance for state"),
1219        }
1220    }
1221}