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::LackBalanceForState { account_id, amount } => {
164                write!(f, "account {account_id} lacks {amount} for state")
165            }
166            Self::FunctionCallError(e) => write!(f, "{e}"),
167            _ => write!(f, "{self:?}"),
168        }
169    }
170}
171
172// ============================================================================
173// Transaction validation errors
174// ============================================================================
175
176/// An error during transaction validation (before execution).
177#[derive(Debug, Clone, Deserialize)]
178pub enum InvalidTxError {
179    InvalidAccessKeyError(InvalidAccessKeyError),
180    InvalidSignerId {
181        signer_id: String,
182    },
183    SignerDoesNotExist {
184        signer_id: AccountId,
185    },
186    InvalidNonce {
187        ak_nonce: u64,
188        tx_nonce: u64,
189    },
190    NonceTooLarge {
191        tx_nonce: u64,
192        upper_bound: u64,
193    },
194    InvalidReceiverId {
195        receiver_id: String,
196    },
197    InvalidSignature,
198    NotEnoughBalance {
199        balance: NearToken,
200        cost: NearToken,
201        signer_id: AccountId,
202    },
203    LackBalanceForState {
204        amount: NearToken,
205        signer_id: AccountId,
206    },
207    CostOverflow,
208    InvalidChain,
209    Expired,
210    ActionsValidation(ActionsValidationError),
211    TransactionSizeExceeded {
212        limit: u64,
213        size: u64,
214    },
215    InvalidTransactionVersion,
216    StorageError(StorageError),
217    ShardCongested {
218        congestion_level: f64,
219        shard_id: u64,
220    },
221    ShardStuck {
222        missed_chunks: u64,
223        shard_id: u64,
224    },
225    InvalidNonceIndex {
226        num_nonces: u16,
227        #[serde(default)]
228        tx_nonce_index: Option<u16>,
229    },
230    NotEnoughGasKeyBalance {
231        balance: NearToken,
232        cost: NearToken,
233        signer_id: AccountId,
234    },
235    NotEnoughBalanceForDeposit {
236        balance: NearToken,
237        cost: NearToken,
238        reason: DepositCostFailureReason,
239        signer_id: AccountId,
240    },
241}
242
243impl std::fmt::Display for InvalidTxError {
244    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
245        match self {
246            Self::InvalidSignature => write!(f, "invalid signature"),
247            Self::NotEnoughBalance {
248                signer_id, cost, ..
249            } => write!(
250                f,
251                "{signer_id} does not have enough balance to cover {cost}"
252            ),
253            Self::InvalidNonce {
254                ak_nonce, tx_nonce, ..
255            } => write!(
256                f,
257                "invalid nonce: tx nonce {tx_nonce}, access key nonce {ak_nonce}"
258            ),
259            Self::Expired => write!(f, "transaction has expired"),
260            Self::ShardCongested { shard_id, .. } => write!(f, "shard {shard_id} is congested"),
261            _ => write!(f, "{self:?}"),
262        }
263    }
264}
265
266// ============================================================================
267// Access key errors
268// ============================================================================
269
270/// Error related to access key validation.
271#[derive(Debug, Clone, Deserialize)]
272pub enum InvalidAccessKeyError {
273    AccessKeyNotFound {
274        account_id: AccountId,
275        public_key: PublicKey,
276    },
277    ReceiverMismatch {
278        ak_receiver: String,
279        tx_receiver: AccountId,
280    },
281    MethodNameMismatch {
282        method_name: String,
283    },
284    RequiresFullAccess,
285    NotEnoughAllowance {
286        account_id: AccountId,
287        allowance: NearToken,
288        cost: NearToken,
289        public_key: PublicKey,
290    },
291    DepositWithFunctionCall,
292}
293
294// ============================================================================
295// Function call errors (VM / contract)
296// ============================================================================
297
298/// An error during function call execution.
299#[derive(Debug, Clone, Deserialize)]
300pub enum FunctionCallError {
301    WasmUnknownError,
302    #[serde(rename = "_EVMError")]
303    EvmError,
304    CompilationError(CompilationError),
305    LinkError {
306        msg: String,
307    },
308    MethodResolveError(MethodResolveError),
309    WasmTrap(WasmTrap),
310    HostError(HostError),
311    ExecutionError(String),
312}
313
314impl std::fmt::Display for FunctionCallError {
315    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
316        match self {
317            Self::ExecutionError(msg) => write!(f, "execution error: {msg}"),
318            Self::HostError(e) => write!(f, "host error: {e:?}"),
319            Self::WasmTrap(e) => write!(f, "wasm trap: {e:?}"),
320            Self::CompilationError(e) => write!(f, "compilation error: {e:?}"),
321            Self::MethodResolveError(e) => write!(f, "method resolve error: {e:?}"),
322            Self::LinkError { msg } => write!(f, "link error: {msg}"),
323            _ => write!(f, "{self:?}"),
324        }
325    }
326}
327
328/// Wasm compilation error.
329#[derive(Debug, Clone, Deserialize)]
330pub enum CompilationError {
331    CodeDoesNotExist { account_id: AccountId },
332    PrepareError(PrepareError),
333    WasmerCompileError { msg: String },
334}
335
336/// Error preparing a Wasm module.
337#[derive(Debug, Clone, Deserialize)]
338pub enum PrepareError {
339    Serialization,
340    Deserialization,
341    InternalMemoryDeclared,
342    GasInstrumentation,
343    StackHeightInstrumentation,
344    Instantiate,
345    Memory,
346    TooManyFunctions,
347    TooManyLocals,
348    TooManyTables,
349    TooManyTableElements,
350}
351
352/// Error resolving a method in Wasm.
353#[derive(Debug, Clone, Deserialize)]
354pub enum MethodResolveError {
355    MethodEmptyName,
356    MethodNotFound,
357    MethodInvalidSignature,
358}
359
360/// A trap during Wasm execution.
361#[derive(Debug, Clone, Deserialize)]
362pub enum WasmTrap {
363    Unreachable,
364    IncorrectCallIndirectSignature,
365    MemoryOutOfBounds,
366    #[serde(rename = "CallIndirectOOB")]
367    CallIndirectOob,
368    IllegalArithmetic,
369    MisalignedAtomicAccess,
370    IndirectCallToNull,
371    StackOverflow,
372    GenericTrap,
373}
374
375/// Error from a host function call.
376#[derive(Debug, Clone, Deserialize)]
377pub enum HostError {
378    #[serde(rename = "BadUTF16")]
379    BadUtf16,
380    #[serde(rename = "BadUTF8")]
381    BadUtf8,
382    GasExceeded,
383    GasLimitExceeded,
384    BalanceExceeded,
385    EmptyMethodName,
386    GuestPanic {
387        panic_msg: String,
388    },
389    IntegerOverflow,
390    InvalidPromiseIndex {
391        promise_idx: u64,
392    },
393    CannotAppendActionToJointPromise,
394    CannotReturnJointPromise,
395    InvalidPromiseResultIndex {
396        result_idx: u64,
397    },
398    InvalidRegisterId {
399        register_id: u64,
400    },
401    IteratorWasInvalidated {
402        iterator_index: u64,
403    },
404    MemoryAccessViolation,
405    InvalidReceiptIndex {
406        receipt_index: u64,
407    },
408    InvalidIteratorIndex {
409        iterator_index: u64,
410    },
411    InvalidAccountId,
412    InvalidMethodName,
413    InvalidPublicKey,
414    ProhibitedInView {
415        method_name: String,
416    },
417    NumberOfLogsExceeded {
418        limit: u64,
419    },
420    KeyLengthExceeded {
421        length: u64,
422        limit: u64,
423    },
424    ValueLengthExceeded {
425        length: u64,
426        limit: u64,
427    },
428    TotalLogLengthExceeded {
429        length: u64,
430        limit: u64,
431    },
432    NumberPromisesExceeded {
433        limit: u64,
434        number_of_promises: u64,
435    },
436    NumberInputDataDependenciesExceeded {
437        limit: u64,
438        number_of_input_data_dependencies: u64,
439    },
440    ReturnedValueLengthExceeded {
441        length: u64,
442        limit: u64,
443    },
444    ContractSizeExceeded {
445        limit: u64,
446        size: u64,
447    },
448    Deprecated {
449        method_name: String,
450    },
451    #[serde(rename = "ECRecoverError")]
452    EcRecoverError {
453        msg: String,
454    },
455    AltBn128InvalidInput {
456        msg: String,
457    },
458    Ed25519VerifyInvalidInput {
459        msg: String,
460    },
461}
462
463// ============================================================================
464// Actions validation errors
465// ============================================================================
466
467/// Error validating actions in a transaction or receipt.
468#[derive(Debug, Clone, Deserialize)]
469pub enum ActionsValidationError {
470    DeleteActionMustBeFinal,
471    TotalPrepaidGasExceeded {
472        limit: Gas,
473        total_prepaid_gas: Gas,
474    },
475    TotalNumberOfActionsExceeded {
476        limit: u64,
477        total_number_of_actions: u64,
478    },
479    AddKeyMethodNamesNumberOfBytesExceeded {
480        limit: u64,
481        total_number_of_bytes: u64,
482    },
483    AddKeyMethodNameLengthExceeded {
484        length: u64,
485        limit: u64,
486    },
487    IntegerOverflow,
488    InvalidAccountId {
489        account_id: String,
490    },
491    ContractSizeExceeded {
492        limit: u64,
493        size: u64,
494    },
495    FunctionCallMethodNameLengthExceeded {
496        length: u64,
497        limit: u64,
498    },
499    FunctionCallArgumentsLengthExceeded {
500        length: u64,
501        limit: u64,
502    },
503    UnsuitableStakingKey {
504        public_key: PublicKey,
505    },
506    FunctionCallZeroAttachedGas,
507    DelegateActionMustBeOnlyOne,
508    UnsupportedProtocolFeature {
509        protocol_feature: String,
510        version: u32,
511    },
512    InvalidDeterministicStateInitReceiver {
513        derived_id: AccountId,
514        receiver_id: AccountId,
515    },
516    DeterministicStateInitKeyLengthExceeded {
517        length: u64,
518        limit: u64,
519    },
520    DeterministicStateInitValueLengthExceeded {
521        length: u64,
522        limit: u64,
523    },
524    GasKeyInvalidNumNonces {
525        limit: u16,
526        requested_nonces: u16,
527    },
528    AddGasKeyWithNonZeroBalance {
529        balance: NearToken,
530    },
531    GasKeyFunctionCallAllowanceNotAllowed,
532}
533
534// ============================================================================
535// Receipt validation errors
536// ============================================================================
537
538/// Error validating a receipt.
539#[derive(Debug, Clone, Deserialize)]
540pub enum ReceiptValidationError {
541    InvalidPredecessorId {
542        account_id: String,
543    },
544    InvalidReceiverId {
545        account_id: String,
546    },
547    InvalidSignerId {
548        account_id: String,
549    },
550    InvalidDataReceiverId {
551        account_id: String,
552    },
553    ReturnedValueLengthExceeded {
554        length: u64,
555        limit: u64,
556    },
557    NumberInputDataDependenciesExceeded {
558        limit: u64,
559        number_of_input_data_dependencies: u64,
560    },
561    ActionsValidation(ActionsValidationError),
562    ReceiptSizeExceeded {
563        limit: u64,
564        size: u64,
565    },
566    InvalidRefundTo {
567        account_id: String,
568    },
569}
570
571// ============================================================================
572// Storage errors
573// ============================================================================
574
575/// Internal storage error.
576#[derive(Debug, Clone, Deserialize)]
577pub enum StorageError {
578    StorageInternalError,
579    MissingTrieValue(MissingTrieValue),
580    UnexpectedTrieValue,
581    StorageInconsistentState(String),
582    FlatStorageBlockNotSupported(String),
583    MemTrieLoadingError(String),
584}
585
586/// Details about a missing trie value.
587#[derive(Debug, Clone, Deserialize)]
588pub struct MissingTrieValue {
589    pub context: MissingTrieValueContext,
590    pub hash: CryptoHash,
591}
592
593/// Context in which a trie value was missing.
594#[derive(Debug, Clone, Deserialize)]
595#[allow(clippy::enum_variant_names)] // Matches nearcore naming
596pub enum MissingTrieValueContext {
597    TrieIterator,
598    TriePrefetchingStorage,
599    TrieMemoryPartialStorage,
600    TrieStorage,
601}
602
603// ============================================================================
604// Misc
605// ============================================================================
606
607/// Reason a deposit cost check failed on a gas key transaction.
608#[derive(Debug, Clone, Deserialize)]
609pub enum DepositCostFailureReason {
610    NotEnoughBalance,
611    LackBalanceForState,
612}