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; } }}
El Tipo de abort
Sección titulada «El Tipo de abort»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 } }}
Convenciones de Códigos de Error
Sección titulada «Convenciones de Códigos de Error»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:
Usando el Módulo std::error
Sección titulada «Usando el Módulo std::error»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)); }}
Códigos de Error Personalizados
Sección titulada «Códigos de Error Personalizados»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; }}
Ejemplos Prácticos
Sección titulada «Ejemplos Prácticos»Validación de Entrada
Sección titulada «Validación de Entrada»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...}
Verificación de Permisos
Sección titulada «Verificación de Permisos»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...}
Validación de Estado
Sección titulada «Validación de Estado»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...}
Validación de Rango
Sección titulada «Validación de Rango»public fun set_fee_rate(rate: u64) { assert!(rate <= 10000, E_RATE_TOO_HIGH); // máximo 100% (10000 basis points)
// establecer tarifa...}
Verificación de Tiempo
Sección titulada «Verificación de Tiempo»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...}
Patrones de Manejo de Errores
Sección titulada «Patrones de Manejo de Errores»Early Return Pattern
Sección titulada «Early Return Pattern»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);}
Validación en Cascada
Sección titulada «Validación en Cascada»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...}
Manejo de Condiciones Complejas
Sección titulada «Manejo de Condiciones Complejas»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...}
Buenas Prácticas
Sección titulada «Buenas Prácticas»1. Usa Códigos de Error Descriptivos
Sección titulada «1. Usa Códigos de Error Descriptivos»// ✓ Bueno: códigos de error clarosconst E_INSUFFICIENT_BALANCE: u64 = 1;const E_ACCOUNT_NOT_FOUND: u64 = 2;const E_UNAUTHORIZED_ACCESS: u64 = 3;
// ✗ Poco claro: números mágicosassert!(balance >= amount, 42);
2. Agrupa Validaciones
Sección titulada «2. Agrupa Validaciones»// ✓ Bueno: validaciones agrupadas al iniciopublic 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ónconst E_INSUFFICIENT_BALANCE: u64 = 1;
/// Error: La cuenta no existe en el sistemaconst E_ACCOUNT_NOT_FOUND: u64 = 2;
4. Usa Assert para Condiciones Positivas
Sección titulada «4. Usa Assert para Condiciones Positivas»// ✓ Bueno: assert para condiciones que deben ser verdaderasassert!(user.is_active, E_USER_INACTIVE);assert!(amount > 0, E_INVALID_AMOUNT);
// ✗ Confuso: lógica negativaassert!(!(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 claroassert!(balance >= amount, E_INSUFFICIENT_BALANCE);
// ✗ Menos claro: abort directoif (balance < amount) abort E_INSUFFICIENT_BALANCE;
6. Documenta los Códigos de Error
Sección titulada «6. Documenta los Códigos de Error»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;}