llms.txt
Skip to main content

Aborting Execution

A transaction can end in one of two ways: it either succeeds, and all of the changes it made are applied and committed to the blockchain, or it aborts, and none of the changes are applied. There is nothing in between: a transaction cannot partially succeed, and an abort in a deeply nested function call fails the entire transaction. This all-or-nothing model is what makes error handling in Move simple and predictable - a function never needs to undo its changes, because an abort undoes everything at once.

There is no catch mechanism in Move. An abort cannot be intercepted or recovered from: it always fails the whole transaction. This is a design choice - it trades flexibility for simplicity and makes it impossible to end up in a partially updated state.

In this section, we look at the tools Move provides for aborting: the abort expression, the assert! macro, and the conventions for defining error codes and error messages.

Abort

The abort keyword stops execution immediately. It is normally given an abort code - an integer of type u64 - which is returned to the caller of the transaction together with the identity of the module that aborted. Here is an example:

let user_has_access = false;

// abort with a predefined constant if `user_has_access` is false
if (!user_has_access) {
abort 1
};

The code above will, of course, abort with abort code 1.

Two properties of abort codes are worth internalizing early:

  • Abort codes are local to the module. Two different modules can both abort with code 1, and they mean different things; the caller has to interpret the code together with the module that produced it.
  • An abort code carries no message. The blockchain records only the numeric code and the location of the abort - making the codes readable is up to the module author, which is what error constants and error messages below are for.

Omitting the Abort Code

The abort code can be omitted in the source - a bare abort expression is valid Move:

// `abort` can also be used without an explicit abort code.
abort

Omitted does not mean absent, though: the caller still receives a u64 abort code, derived automatically by the compiler. The derived code uses the clever-error encoding described in Error Messages below - it carries the module and the source line of the failure, with the constant name and value left empty.

This form, sometimes called a clean abort, is a good fit for branches that are not expected to be reachable at all - such as the wildcard arm of a match expression covering values that cannot occur (see Enums and Match). Since the derived code points at the failure but says nothing about its meaning, for conditions that external callers can actually trigger, prefer an explicit code or an error message.

assert!

The assert! macro is a built-in macro that checks a condition and aborts if the condition is false. It is a shorthand for the if + abort combination you would otherwise write by hand, and it is by far the most common way to abort in Move code. The first argument is the condition; the second, optional, argument is the abort code - when it is omitted, a code is derived automatically, the same way as for a bare abort:

// aborts if `user_has_access` is `false` with abort code 0
assert!(user_has_access, 0);

// expands to:
if (!user_has_access) {
abort 0
};

// the abort code can be omitted
assert!(user_has_access);

A common practice is to place asserts at the beginning of a function - check everything first, then perform the changes. Because an abort reverts the whole transaction this is not required for safety, but it makes the function's requirements visible at a glance and avoids wasting gas on work that is bound to be thrown away.

Error Constants

A raw numeric code like assert!(user_has_access, 1) tells the reader nothing about what went wrong. To make error codes descriptive, it is a good practice to define them as constants. Error constants follow their own naming convention - E followed by a CamelCase description - which sets them apart from regular ALL_CAPS constants:

/// Error code for when the user has no access.
const ENoAccess: u64 = 0;
/// Trying to access a field that does not exist.
const ENoField: u64 = 1;

/// Updates a record.
public fun update_record(/* ... , */ user_has_access: bool, field_exists: bool) {
// asserts are way more readable now
assert!(user_has_access, ENoAccess);
assert!(field_exists, ENoField);

/* ... */
}

Error constants are regular u64 constants and receive no special treatment from the compiler. However, following the convention makes the code self-documenting - assert!(user_has_access, ENoAccess) reads as a sentence - and a caller who receives the abort code can find the matching constant in the module's source. A well-written module defines an error constant for every abort scenario it can produce.

Error Messages

Move 2024 introduces clever errors - error constants marked with the #[error] attribute. Unlike regular error constants, they can be of any type - most usefully vector<u8>, holding a human-readable error message:

#[error]
const ENotAuthorized: vector<u8> = "The user is not authorized to perform this action";

#[error]
const EValueTooLow: vector<u8> = "The value is too low, it should be at least 10";

/// Performs an action on behalf of the user.
public fun update_value(user: &mut User, value: u64) {
assert!(user.is_authorized, ENotAuthorized);
assert!(value >= 10, EValueTooLow);

user.value = value;
}

The attribute does not change what an abort is: the transaction still fails with a u64 abort code. What changes is the content of that code - the compiler packs into it the source line number of the abort (for a macro like assert!, the line of the call site) and references to the constant's name and value. Tooling that understands the format - the Sui CLI, explorers, SDKs - unpacks it and shows the full picture, along the lines of:

Error from 'book::assert_abort::update_value' (line 15), abort 'EValueTooLow':
"The value is too low, it should be at least 10"

Error messages remove the need to look up the meaning of a numeric code, which matters most in public-facing applications, where the person reading the failure is often not the author of the module. The flip side of the encoding is that the numeric value of a clever abort code depends on the source layout: reformatting the module or adding a line changes it. Refer to these constants by name - never by their compiled numeric value. The exact layout of the encoding is described in Clever Errors in the Move Reference.

Aborts in Tests

Aborting is a behavior worth testing like any other. The #[expected_failure] attribute marks a test that is supposed to abort, and its abort_code argument asserts the exact code - the test fails if the function succeeds or aborts with a different code. We cover this attribute in more detail in the Testing section.

Further Reading

llms.txt