Saltearse al contenido

Abort y Assert

return y abort son dos construcciones de flujo de control que terminan la ejecución, una para la función actual y otra para toda la transacción.

Más información sobre return se puede encontrar en la sección enlazada.

abort es una expresión que toma un argumento: un código de abort de tipo u64. Por ejemplo:

abort 42

La expresión abort detiene la ejecución de la función actual y revierte todos los cambios hechos al estado global por la transacción actual. No hay mecanismo para “atrapar” o manejar de otra manera un abort.

Afortunadamente, en Move las transacciones son todo o nada, lo que significa que cualquier cambio al almacenamiento global se hace todo de una vez solo si la transacción tiene éxito. Debido a este compromiso transaccional de cambios, después de un abort no hay necesidad de preocuparse por deshacer cambios. Aunque este enfoque carece de flexibilidad, es increíblemente simple y predecible.

Similar a return, abort es útil para salir del flujo de control cuando alguna condición no puede ser cumplida.

En este ejemplo, la función sacará dos elementos del vector, pero abortará temprano si el vector no tiene dos elementos:

script {
use std::vector;
fun pop_twice<T>(v: &mut vector<T>): (T, T) {
if (vector::length(v) < 2) abort 42;
(vector::pop_back(v), vector::pop_back(v))
}
}

Esto es aún más útil profundamente dentro de una construcción de flujo de control. Por ejemplo, esta función verifica que todos los números en el vector sean menores que el bound especificado. Y aborta de lo contrario:

script {
use std::vector;
fun check_vec(v: &vector<u64>, bound: u64) {
let i = 0;
let n = vector::length(v);
while (i < n) {
let cur = *vector::borrow(v, i);
if (cur > bound) abort 42;
i = i + 1;
}
}
}

assert es una operación integrada, similar a una macro, proporcionada por el compilador de Move. Toma dos argumentos, una condición de tipo bool y un código de tipo u64:

assert!(condition: bool, code: u64)
assert!(condition: bool) // Desde Move 2.0

Dado que la operación es una macro, debe ser invocada con el !. Esto es para transmitir que los argumentos de assert son call-by-expression. En otras palabras, assert no es una función normal y no existe a nivel de bytecode. Se reemplaza dentro del compilador con:

if (condition) () else abort code

Desde Move 2.0, se soporta assert sin un código de error. Si se usa este assert, se genera el código de abort 0xCA26CBD9BE0B0000. En términos de la convención std::error, este código tiene categoría std::error::INTERNAL y razón 0.

assert se usa más comúnmente que solo abort por sí mismo. Los ejemplos de abort anteriores pueden ser reescritos usando assert:

script {
use std::vector;
fun pop_twice<T>(v: &mut vector<T>): (T, T) {
assert!(vector::length(v) >= 2, 42); // Ahora usa 'assert'
(vector::pop_back(v), vector::pop_back(v))
}
}

y

script {
use std::vector;
fun check_vec(v: &vector<u64>, bound: u64) {
let i = 0;
let n = vector::length(v);
while (i < n) {
let cur = *vector::borrow(v, i);
assert!(cur <= bound, 42); // Ahora usa 'assert'
i = i + 1;
}
}
}

La expresión abort i puede tener cualquier tipo! Esto es porque nunca devuelve un valor, por lo que nunca necesita coincidir con el tipo esperado por el contexto.

En una función que espera devolver un valor de algún tipo, el siguiente ejemplo no compilaría sin el abort:

script {
fun example(): u64 {
if (true) {
1
} else {
abort 42 // abort puede ser tratado como un u64
}
}
}

En Move, es una práctica común usar códigos de error sistemáticos para hacer que la depuración sea más fácil. Los códigos de error típicamente siguen un patrón:

El módulo estándar std::error proporciona constantes para categorías de error comunes:

use std::error;
script {
fun example() {
// Error de argumento inválido
assert!(x > 0, error::invalid_argument(1));
// Error de estado inválido
assert!(is_initialized(), error::invalid_state(2));
// Error de permisos
assert!(is_authorized(), error::permission_denied(3));
}
}

Para módulos personalizados, es común definir constantes para códigos de error:

module 0x42::bank {
// Constantes de error
const E_INSUFFICIENT_BALANCE: u64 = 1;
const E_ACCOUNT_NOT_FOUND: u64 = 2;
const E_INVALID_AMOUNT: u64 = 3;
const E_ACCOUNT_FROZEN: u64 = 4;
const E_UNAUTHORIZED: u64 = 5;
public fun withdraw(account: &signer, amount: u64) {
let addr = std::signer::address_of(account);
assert!(exists<Account>(addr), E_ACCOUNT_NOT_FOUND);
assert!(amount > 0, E_INVALID_AMOUNT);
let account_ref = borrow_global_mut<Account>(addr);
assert!(!account_ref.frozen, E_ACCOUNT_FROZEN);
assert!(account_ref.balance >= amount, E_INSUFFICIENT_BALANCE);
account_ref.balance = account_ref.balance - amount;
}
}
public fun create_user(name: vector<u8>, age: u8) {
assert!(!vector::is_empty(&name), E_EMPTY_NAME);
assert!(age >= 18, E_UNDERAGE);
assert!(age <= 120, E_INVALID_AGE);
// crear usuario...
}
public fun admin_function(admin: &signer) {
let admin_addr = std::signer::address_of(admin);
assert!(admin_addr == @admin_address, E_NOT_AUTHORIZED);
// función administrativa...
}
public fun transfer(from: &signer, to: address, amount: u64) acquires Account {
let from_addr = std::signer::address_of(from);
assert!(exists<Account>(from_addr), E_ACCOUNT_NOT_FOUND);
assert!(exists<Account>(to), E_RECIPIENT_NOT_FOUND);
assert!(amount > 0, E_ZERO_AMOUNT);
assert!(from_addr != to, E_SELF_TRANSFER);
let from_account = borrow_global_mut<Account>(from_addr);
assert!(from_account.balance >= amount, E_INSUFFICIENT_BALANCE);
// ejecutar transferencia...
}
public fun set_fee_rate(rate: u64) {
assert!(rate <= 10000, E_RATE_TOO_HIGH); // máximo 100% (10000 basis points)
// establecer tarifa...
}
public fun claim_reward(account: &signer) acquires StakeInfo {
let addr = std::signer::address_of(account);
let stake_info = borrow_global<StakeInfo>(addr);
let now = timestamp::now_seconds();
assert!(now >= stake_info.lock_until, E_STILL_LOCKED);
// reclamar recompensa...
}
public fun validate_and_process(data: &vector<u8>) {
// Validar todos los requisitos primero
assert!(!vector::is_empty(data), E_EMPTY_DATA);
assert!(vector::length(data) <= MAX_SIZE, E_DATA_TOO_LARGE);
assert!(is_valid_format(data), E_INVALID_FORMAT);
// Procesar solo si todas las validaciones pasan
process_data(data);
}
public fun multi_step_validation(user: &signer, amount: u64) {
// Paso 1: Validar usuario
let user_addr = std::signer::address_of(user);
assert!(exists<UserProfile>(user_addr), E_USER_NOT_FOUND);
let profile = borrow_global<UserProfile>(user_addr);
assert!(profile.is_active, E_USER_INACTIVE);
assert!(profile.is_verified, E_USER_NOT_VERIFIED);
// Paso 2: Validar cantidad
assert!(amount > 0, E_INVALID_AMOUNT);
assert!(amount <= profile.daily_limit, E_EXCEEDS_DAILY_LIMIT);
// Paso 3: Validar saldo
assert!(profile.balance >= amount, E_INSUFFICIENT_BALANCE);
// Proceder con la operación...
}
public fun complex_business_logic(
user: &signer,
target: address,
amount: u64
) acquires Account, Settings {
let user_addr = std::signer::address_of(user);
let settings = borrow_global<Settings>(@admin);
// Verificaciones básicas
assert!(settings.system_active, E_SYSTEM_MAINTENANCE);
assert!(amount >= settings.min_amount, E_AMOUNT_TOO_SMALL);
assert!(amount <= settings.max_amount, E_AMOUNT_TOO_LARGE);
// Verificaciones de usuario
let user_account = borrow_global<Account>(user_addr);
assert!(user_account.status == STATUS_ACTIVE, E_ACCOUNT_SUSPENDED);
// Verificaciones específicas del negocio
if (amount > settings.large_amount_threshold) {
assert!(user_account.is_premium, E_PREMIUM_REQUIRED);
assert!(user_account.verification_level >= LEVEL_ENHANCED, E_ENHANCED_VERIFICATION_REQUIRED);
};
// Proceder...
}
// ✓ Bueno: códigos de error claros
const E_INSUFFICIENT_BALANCE: u64 = 1;
const E_ACCOUNT_NOT_FOUND: u64 = 2;
const E_UNAUTHORIZED_ACCESS: u64 = 3;
// ✗ Poco claro: números mágicos
assert!(balance >= amount, 42);
// ✓ Bueno: validaciones agrupadas al inicio
public fun process_payment(amount: u64, recipient: address) {
// Todas las validaciones primero
assert!(amount > 0, E_INVALID_AMOUNT);
assert!(recipient != @0x0, E_INVALID_RECIPIENT);
assert!(exists<Account>(recipient), E_RECIPIENT_NOT_FOUND);
// Lógica del negocio después
execute_payment(amount, recipient);
}

3. Proporciona Mensajes de Error Informativos

Sección titulada «3. Proporciona Mensajes de Error Informativos»
// ✓ Bueno: comentarios explican los errores
/// Error: Saldo insuficiente para completar la transacción
const E_INSUFFICIENT_BALANCE: u64 = 1;
/// Error: La cuenta no existe en el sistema
const E_ACCOUNT_NOT_FOUND: u64 = 2;
// ✓ Bueno: assert para condiciones que deben ser verdaderas
assert!(user.is_active, E_USER_INACTIVE);
assert!(amount > 0, E_INVALID_AMOUNT);
// ✗ Confuso: lógica negativa
assert!(!(amount == 0), E_INVALID_AMOUNT);

5. Evita Abort Directo Cuando Assert es Más Claro

Sección titulada «5. Evita Abort Directo Cuando Assert es Más Claro»
// ✓ Bueno: assert es más claro
assert!(balance >= amount, E_INSUFFICIENT_BALANCE);
// ✗ Menos claro: abort directo
if (balance < amount) abort E_INSUFFICIENT_BALANCE;
module error_codes {
/// Errores de autenticación (1-10)
const E_NOT_AUTHORIZED: u64 = 1;
const E_INVALID_SIGNATURE: u64 = 2;
/// Errores de saldo (11-20)
const E_INSUFFICIENT_BALANCE: u64 = 11;
const E_AMOUNT_TOO_LARGE: u64 = 12;
/// Errores de estado (21-30)
const E_ACCOUNT_FROZEN: u64 = 21;
const E_SYSTEM_PAUSED: u64 = 22;
}