iota_types/
execution_status.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::fmt::{self, Display, Formatter};
6
7use iota_macros::EnumVariantOrder;
8use move_binary_format::file_format::{CodeOffset, TypeParameterIndex};
9use move_core_types::language_storage::ModuleId;
10use serde::{Deserialize, Serialize};
11use thiserror::Error;
12
13use crate::{ObjectID, base_types::IotaAddress};
14
15#[cfg(test)]
16#[path = "unit_tests/execution_status_tests.rs"]
17mod execution_status_tests;
18
19#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)]
20pub enum ExecutionStatus {
21    Success,
22    /// Gas used in the failed case, and the error.
23    Failure {
24        /// The error
25        error: ExecutionFailureStatus,
26        /// Which command the error occurred
27        command: Option<CommandIndex>,
28    },
29}
30
31#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)]
32pub struct CongestedObjects(pub Vec<ObjectID>);
33
34impl fmt::Display for CongestedObjects {
35    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
36        for obj in &self.0 {
37            write!(f, "{obj}, ")?;
38        }
39        Ok(())
40    }
41}
42
43#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, Error, EnumVariantOrder)]
44pub enum ExecutionFailureStatus {
45    // General transaction errors
46    #[error("Insufficient Gas.")]
47    InsufficientGas,
48    #[error("Invalid Gas Object. Possibly not address-owned or possibly not an IOTA coin.")]
49    InvalidGasObject,
50    #[error("INVARIANT VIOLATION.")]
51    InvariantViolation,
52    #[error("Attempted to used feature that is not supported yet")]
53    FeatureNotYetSupported,
54    #[error(
55        "Move object with size {object_size} is larger \
56        than the maximum object size {max_object_size}"
57    )]
58    MoveObjectTooBig {
59        object_size: u64,
60        max_object_size: u64,
61    },
62    #[error(
63        "Move package with size {object_size} is larger than the \
64        maximum object size {max_object_size}"
65    )]
66    MovePackageTooBig {
67        object_size: u64,
68        max_object_size: u64,
69    },
70    #[error("Circular Object Ownership, including object {object}.")]
71    CircularObjectOwnership { object: ObjectID },
72
73    // Coin errors
74    #[error("Insufficient coin balance for operation.")]
75    InsufficientCoinBalance,
76    #[error("The coin balance overflows u64")]
77    CoinBalanceOverflow,
78
79    // Publish/Upgrade errors
80    #[error(
81        "Publish Error, Non-zero Address. \
82        The modules in the package must have their self-addresses set to zero."
83    )]
84    PublishErrorNonZeroAddress,
85
86    #[error(
87        "IOTA Move Bytecode Verification Error. \
88        Please run the IOTA Move Verifier for more information."
89    )]
90    IotaMoveVerificationError,
91
92    // Errors from the Move VM
93    //
94    // Indicates an error from a non-abort instruction
95    #[error(
96        "Move Primitive Runtime Error. Location: {0}. \
97        Arithmetic error, stack overflow, max value depth, etc."
98    )]
99    MovePrimitiveRuntimeError(MoveLocationOpt),
100    #[error("Move Runtime Abort. Location: {0}, Abort Code: {1}")]
101    MoveAbort(MoveLocation, u64),
102    #[error(
103        "Move Bytecode Verification Error. \
104        Please run the Bytecode Verifier for more information."
105    )]
106    VMVerificationOrDeserializationError,
107    #[error("MOVE VM INVARIANT VIOLATION.")]
108    VMInvariantViolation,
109
110    // Programmable Transaction Errors
111    #[error("Function Not Found.")]
112    FunctionNotFound,
113    #[error(
114        "Arity mismatch for Move function. \
115        The number of arguments does not match the number of parameters"
116    )]
117    ArityMismatch,
118    #[error(
119        "Type arity mismatch for Move function. \
120        Mismatch between the number of actual versus expected type arguments."
121    )]
122    TypeArityMismatch,
123    #[error("Non Entry Function Invoked. Move Call must start with an entry function")]
124    NonEntryFunctionInvoked,
125    #[error("Invalid command argument at {arg_idx}. {kind}")]
126    CommandArgumentError {
127        arg_idx: u16,
128        kind: CommandArgumentError,
129    },
130    #[error("Error for type argument at index {argument_idx}: {kind}")]
131    TypeArgumentError {
132        argument_idx: TypeParameterIndex,
133        kind: TypeArgumentError,
134    },
135    #[error(
136        "Unused result without the drop ability. \
137        Command result {result_idx}, return value {secondary_idx}"
138    )]
139    UnusedValueWithoutDrop { result_idx: u16, secondary_idx: u16 },
140    #[error(
141        "Invalid public Move function signature. \
142        Unsupported return type for return value {idx}"
143    )]
144    InvalidPublicFunctionReturnType { idx: u16 },
145    #[error("Invalid Transfer Object, object does not have public transfer.")]
146    InvalidTransferObject,
147
148    // Post-execution errors
149    //
150    // Indicates the effects from the transaction are too large
151    #[error(
152        "Effects of size {current_size} bytes too large. \
153    Limit is {max_size} bytes"
154    )]
155    EffectsTooLarge { current_size: u64, max_size: u64 },
156
157    #[error(
158        "Publish/Upgrade Error, Missing dependency. \
159         A dependency of a published or upgraded package has not been assigned an on-chain \
160         address."
161    )]
162    PublishUpgradeMissingDependency,
163
164    #[error(
165        "Publish/Upgrade Error, Dependency downgrade. \
166         Indirect (transitive) dependency of published or upgraded package has been assigned an \
167         on-chain version that is less than the version required by one of the package's \
168         transitive dependencies."
169    )]
170    PublishUpgradeDependencyDowngrade,
171
172    #[error("Invalid package upgrade. {upgrade_error}")]
173    PackageUpgradeError { upgrade_error: PackageUpgradeError },
174
175    // Indicates the transaction tried to write objects too large to storage
176    #[error(
177        "Written objects of {current_size} bytes too large. \
178    Limit is {max_size} bytes"
179    )]
180    WrittenObjectsTooLarge { current_size: u64, max_size: u64 },
181
182    #[error("Certificate is on the deny list")]
183    CertificateDenied,
184
185    #[error(
186        "IOTA Move Bytecode Verification Timeout. \
187        Please run the IOTA Move Verifier for more information."
188    )]
189    IotaMoveVerificationTimeout,
190
191    #[error("The shared object operation is not allowed.")]
192    SharedObjectOperationNotAllowed,
193
194    #[error("Certificate cannot be executed due to a dependency on a deleted shared object")]
195    InputObjectDeleted,
196
197    #[error("Certificate is cancelled due to congestion on shared objects: {congested_objects}.")]
198    ExecutionCancelledDueToSharedObjectCongestion { congested_objects: CongestedObjects },
199
200    #[error("Address {address:?} is denied for coin {coin_type}")]
201    AddressDeniedForCoin {
202        address: IotaAddress,
203        coin_type: String,
204    },
205
206    #[error("Coin type is globally paused for use: {coin_type}")]
207    CoinTypeGlobalPause { coin_type: String },
208
209    #[error("Certificate is cancelled because randomness could not be generated this epoch")]
210    ExecutionCancelledDueToRandomnessUnavailable,
211
212    // Certificate is cancelled due to congestion on shared objects;
213    // suggested gas price can be used to give this certificate more priority.
214    #[error(
215        "Certificate is cancelled due to congestion on shared objects: {congested_objects}. \
216            To give this certificate more priority to be executed, its gas price can be increased \
217            to at least {suggested_gas_price}."
218    )]
219    ExecutionCancelledDueToSharedObjectCongestionV2 {
220        congested_objects: CongestedObjects,
221        suggested_gas_price: u64,
222    },
223
224    #[error("A valid linkage was unable to be determined for the transaction")]
225    InvalidLinkage,
226    // NOTE: if you want to add a new enum,
227    // please add it at the end for Rust SDK backward compatibility.
228}
229
230#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, Hash)]
231pub struct MoveLocation {
232    pub module: ModuleId,
233    pub function: u16,
234    pub instruction: CodeOffset,
235    pub function_name: Option<String>,
236}
237
238#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, Hash)]
239pub struct MoveLocationOpt(pub Option<MoveLocation>);
240
241#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, Hash, Error)]
242pub enum CommandArgumentError {
243    #[error("The type of the value does not match the expected type")]
244    TypeMismatch,
245    #[error("The argument cannot be deserialized into a value of the specified type")]
246    InvalidBCSBytes,
247    #[error("The argument cannot be instantiated from raw bytes")]
248    InvalidUsageOfPureArg,
249    #[error(
250        "Invalid argument to private entry function. \
251        These functions cannot take arguments from other Move functions"
252    )]
253    InvalidArgumentToPrivateEntryFunction,
254    #[error("Out of bounds access to input or result vector {idx}")]
255    IndexOutOfBounds { idx: u16 },
256    #[error(
257        "Out of bounds secondary access to result vector \
258        {result_idx} at secondary index {secondary_idx}"
259    )]
260    SecondaryIndexOutOfBounds { result_idx: u16, secondary_idx: u16 },
261    #[error(
262        "Invalid usage of result {result_idx}, \
263        expected a single result but found either no return values or multiple."
264    )]
265    InvalidResultArity { result_idx: u16 },
266    #[error(
267        "Invalid taking of the Gas coin. \
268        It can only be used by-value with TransferObjects"
269    )]
270    InvalidGasCoinUsage,
271    #[error(
272        "Invalid usage of value. \
273        Mutably borrowed values require unique usage. \
274        Immutably borrowed values cannot be taken or borrowed mutably. \
275        Taken values cannot be used again."
276    )]
277    InvalidValueUsage,
278    #[error("Immutable objects cannot be passed by-value.")]
279    InvalidObjectByValue,
280    #[error("Immutable objects cannot be passed by mutable reference, &mut.")]
281    InvalidObjectByMutRef,
282    #[error(
283        "Shared object operations such a wrapping, freezing, or converting to owned are not \
284        allowed."
285    )]
286    SharedObjectOperationNotAllowed,
287    #[error(
288        "Invalid argument arity. Expected a single argument but found a result that expanded to \
289        multiple arguments."
290    )]
291    InvalidArgumentArity,
292}
293
294#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, Hash, Error)]
295pub enum PackageUpgradeError {
296    #[error("Unable to fetch package at {package_id}")]
297    UnableToFetchPackage { package_id: ObjectID },
298    #[error("Object {object_id} is not a package")]
299    NotAPackage { object_id: ObjectID },
300    #[error("New package is incompatible with previous version")]
301    IncompatibleUpgrade,
302    #[error("Digest in upgrade ticket and computed digest disagree")]
303    DigestDoesNotMatch { digest: Vec<u8> },
304    #[error("Upgrade policy {policy} is not a valid upgrade policy")]
305    UnknownUpgradePolicy { policy: u8 },
306    #[error("Package ID {package_id} does not match package ID in upgrade ticket {ticket_id}")]
307    PackageIDDoesNotMatch {
308        package_id: ObjectID,
309        ticket_id: ObjectID,
310    },
311}
312
313#[derive(Eq, PartialEq, Clone, Copy, Debug, Serialize, Deserialize, Hash, Error)]
314pub enum TypeArgumentError {
315    #[error("A type was not found in the module specified.")]
316    TypeNotFound,
317    #[error("A type provided did not match the specified constraints.")]
318    ConstraintNotSatisfied,
319}
320
321impl ExecutionFailureStatus {
322    pub fn command_argument_error(kind: CommandArgumentError, arg_idx: u16) -> Self {
323        Self::CommandArgumentError { arg_idx, kind }
324    }
325}
326
327impl Display for MoveLocationOpt {
328    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
329        match &self.0 {
330            None => write!(f, "UNKNOWN"),
331            Some(l) => write!(f, "{l}"),
332        }
333    }
334}
335
336impl Display for MoveLocation {
337    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
338        let Self {
339            module,
340            function,
341            instruction,
342            function_name,
343        } = self;
344        if let Some(fname) = function_name {
345            write!(
346                f,
347                "{module}::{fname} (function index {function}) at offset {instruction}"
348            )
349        } else {
350            write!(
351                f,
352                "{module} in function definition {function} at offset {instruction}"
353            )
354        }
355    }
356}
357
358impl ExecutionStatus {
359    pub fn new_failure(
360        error: ExecutionFailureStatus,
361        command: Option<CommandIndex>,
362    ) -> ExecutionStatus {
363        ExecutionStatus::Failure { error, command }
364    }
365
366    pub fn is_ok(&self) -> bool {
367        matches!(self, ExecutionStatus::Success)
368    }
369
370    pub fn is_err(&self) -> bool {
371        matches!(self, ExecutionStatus::Failure { .. })
372    }
373
374    pub fn unwrap(&self) {
375        match self {
376            ExecutionStatus::Success => {}
377            ExecutionStatus::Failure { .. } => {
378                panic!("Unable to unwrap() on {self:?}");
379            }
380        }
381    }
382
383    pub fn unwrap_err(self) -> (ExecutionFailureStatus, Option<CommandIndex>) {
384        match self {
385            ExecutionStatus::Success => {
386                panic!("Unable to unwrap() on {self:?}");
387            }
388            ExecutionStatus::Failure { error, command } => (error, command),
389        }
390    }
391
392    /// Returns congested objects if the transaction was cancelled due to
393    /// shared object congestion, else returns `None`.
394    pub fn get_congested_objects(&self) -> Option<&CongestedObjects> {
395        match self {
396            ExecutionStatus::Failure {
397                error:
398                    ExecutionFailureStatus::ExecutionCancelledDueToSharedObjectCongestion {
399                        congested_objects,
400                    }
401                    | ExecutionFailureStatus::ExecutionCancelledDueToSharedObjectCongestionV2 {
402                        congested_objects,
403                        ..
404                    },
405                ..
406            } => Some(congested_objects),
407            _ => None,
408        }
409    }
410
411    /// Returns a suggested gas price if the transaction was cancelled due to
412    /// shared object congestion (subject to the gas price feedback mechanism
413    /// is enabled), otherwise returns `None`.
414    pub fn get_feedback_suggested_gas_price(&self) -> Option<u64> {
415        if let ExecutionStatus::Failure {
416            error:
417                ExecutionFailureStatus::ExecutionCancelledDueToSharedObjectCongestionV2 {
418                    suggested_gas_price,
419                    ..
420                },
421            ..
422        } = self
423        {
424            Some(*suggested_gas_price)
425        } else {
426            None
427        }
428    }
429
430    /// Check is the transaction was cancelled due to shared object congestion.
431    pub fn is_cancelled_due_to_congestion(&self) -> bool {
432        matches!(
433            self,
434            ExecutionStatus::Failure {
435                error: ExecutionFailureStatus::ExecutionCancelledDueToSharedObjectCongestion { .. }
436                    | ExecutionFailureStatus::ExecutionCancelledDueToSharedObjectCongestionV2 { .. },
437                ..
438            }
439        )
440    }
441
442    /// Check is the transaction was cancelled due to randomness unavailable.
443    pub fn is_cancelled_due_to_randomness(&self) -> bool {
444        matches!(
445            self,
446            ExecutionStatus::Failure {
447                error: ExecutionFailureStatus::ExecutionCancelledDueToRandomnessUnavailable,
448                ..
449            }
450        )
451    }
452}
453
454pub type CommandIndex = usize;