Errores
📚 Manejo de errores
🎯 Parte Teórica: Fundamentos del Manejo de Errores
¿Por qué es importante el manejo de errores? 🤔
En los contratos inteligentes de Soroban, el manejo de errores es crítico porque:
🛡️ Seguridad: Evita comportamientos inesperados que podrían ser explotados
💰 Protección de fondos: Previene pérdidas de tokens o activos
🎯 Experiencia del usuario: Proporciona mensajes claros sobre qué salió mal
📊 Debugging: Facilita la identificación y solución de problemas
🔧 Tipos de Errores en Soroban
1. Errores del Sistema (Host Errors) 🏠
Son errores que maneja automáticamente el entorno de Soroban:
Falta de memoria
Límites de gas excedidos
Operaciones matemáticas inválidas (división por cero)
2. Errores Personalizados (Custom Errors) ✨
Son errores que tú defines para tu lógica de negocio:
Permisos insuficientes
Parámetros inválidos
Estados de contrato incorrectos
🛠️ Herramientas para Manejar Errores
Result<T, E> - Tu mejor amigo 🤝
// Tipo Result básico
Result<Valor_Exitoso, Tipo_de_Error>
// En Soroban usamos principalmente:
Result<ReturnType, Error>
Macros útiles 🎨
panic!()
- Para errores irrecuperables (¡usar con cuidado!)assert!()
- Para validaciones críticaslog!()
- Para debugging (solo en testnet)
📋 Estrategias de Manejo de Errores
1. Validación Temprana ⚡
Valida todos los parámetros al inicio de la función
2. Errores Específicos 🎯
Crea tipos de error específicos para cada situación
3. Propagación Controlada 📤
Usa ?
para propagar errores de forma limpia
4. Logging Inteligente 📝
Registra errores para facilitar el debugging
💡 Ejemplos Prácticos:
🌱 Ejemplo : Contrato Básico SIN Errores Personalizados
Empecemos con algo simple que usa solo los errores que Soroban ya tiene integrados:
#![no_std]
use soroban_sdk::{contract, contractimpl, contracttype, panic_with_error, Env, Symbol};
// 📦 Estructura simple para guardar datos
#[contracttype]
pub struct UserData {
pub name: Symbol,
pub balance: i64,
}
#[contract]
pub struct SimpleContract;
#[contractimpl]
impl SimpleContract {
// 💾 Función para guardar datos de usuario
pub fn set_user(env: Env, user_id: Symbol, name: Symbol, balance: i64) {
// ✅ Validación simple usando assert! (si falla, panic automático)
assert!(balance >= 0, "Balance no puede ser negativo");
// 💾 Crear y guardar datos
let user_data = UserData { name, balance };
env.storage().persistent().set(&user_id, &user_data);
}
// 👀 Función para leer datos con Option (sin errores personalizados)
pub fn get_user(env: Env, user_id: Symbol) -> Option<UserData> {
// 🔍 Intentar obtener datos del storage
// Si no existe, devuelve None automáticamente
env.storage().persistent().get(&user_id)
}
// 💰 Función para actualizar balance con validaciones básicas
pub fn update_balance(env: Env, user_id: Symbol, new_balance: i64) -> Option<i64> {
// ✅ Validación simple
if new_balance < 0 {
// 🚨 Forma simple de manejar error: devolver None
return None;
}
// 📋 Intentar obtener datos existentes
let mut user_data: UserData = match env.storage().persistent().get(&user_id) {
Some(data) => data,
None => return None, // Usuario no existe
};
// ✅ Actualizar y guardar
user_data.balance = new_balance;
env.storage().persistent().set(&user_id, &user_data);
// 🎉 Devolver el nuevo balance
Some(new_balance)
}
// ➕ Función que puede fallar por overflow (usa checked_add)
pub fn add_to_balance(env: Env, user_id: Symbol, amount: i64) -> Option<i64> {
// 📋 Obtener datos del usuario
let mut user_data: UserData = env.storage().persistent().get(&user_id)?; // 🎯 Usa ? con Option (si es None, devuelve None)
// 🧮 Suma segura (evita overflow)
let new_balance = user_data.balance.checked_add(amount)?;
// ✅ Solo actualizar si todo salió bien
user_data.balance = new_balance;
env.storage().persistent().set(&user_id, &user_data);
Some(new_balance)
}
// 🔍 Función helper que demuestra diferentes formas de manejar "no encontrado"
pub fn get_balance_or_zero(env: Env, user_id: Symbol) -> i64 {
// 🎯 Usar unwrap_or para proporcionar valor por defecto
env.storage()
.persistent()
.get::<Symbol, UserData>(&user_id)
.map(|data| data.balance) // Si existe, tomar el balance
.unwrap_or(0) // Si no existe, usar 0
}
}
🎯 Conceptos Clave de este Ejemplo:
✅ Herramientas Básicas de Manejo de Errores:
Option<T>
📦Some(value)
cuando todo está bienNone
cuando algo falla o no existePerfecto para "encontrado/no encontrado"
assert!(condición, mensaje)
⚡Valida condiciones críticas
Si falla, causa panic automático
Ideal para validaciones que nunca deberían fallar
checked_add()
,checked_sub()
🧮Operaciones matemáticas seguras
Devuelven
Option
:Some(result)
oNone
si hay overflowPrevienen errores numéricos silenciosos
Operador
?
con Option 🎯Si es
Some
, continúa con el valorSi es
None
, termina la función devolviendoNone
Hace el código más limpio
unwrap_or(default)
🛡️Proporciona un valor por defecto si es
None
Evita crashes cuando un valor faltante es aceptable
📋 Cuándo Usar Cada Herramienta:
Option
: Para datos que pueden o no existirassert!
: Para condiciones que NUNCA deberían ser falsaschecked_*
: Para operaciones matemáticas que pueden overflow?
: Para propagar "falta de datos" de forma limpiaunwrap_or
: Cuando un valor faltante tiene un reemplazo lógico
🏗️ Ejemplo : Contrato Básico con Errores Personalizados
Este ejemplo muestra un contrato simple para gestionar un contador con validaciones:
#![no_std]
use soroban_sdk::{contract, contracttype, contractimpl, contracterror, Env, Symbol};
// 📝 IMPORTANTE: Definir errores ANTES del contrato
#[contracterror]
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
#[repr(u32)]
pub enum CounterError {
ValueTooHigh = 1,
Unauthorized = 2,
NotInitialized = 3,
}
#[contracttype]
pub struct CounterData {
pub value: u32,
pub owner: Symbol,
pub max_value: u32,
}
#[contract]
pub struct CounterContract;
#[contractimpl]
impl CounterContract {
// ✅ FUNCIONA: Devuelve Result directamente
pub fn init(env: Env, owner: Symbol, max_value: u32) -> Result<(), CounterError> {
if max_value == 0 {
return Err(CounterError::ValueTooHigh);
}
let data = CounterData {
value: 0,
owner,
max_value,
};
env.storage().persistent().set(&Symbol::new(&env, "counter"), &data);
Ok(())
}
// ✅ FUNCIONA: Manejo manual de errores
pub fn increment(env: Env, caller: Symbol, amount: u32) -> Result<u32, CounterError> {
// Obtener datos
let data_key = Symbol::new(&env, "counter");
let mut data: CounterData = match env.storage().persistent().get(&data_key) {
Some(d) => d,
None => return Err(CounterError::NotInitialized),
};
// Verificar permisos
if data.owner != caller {
return Err(CounterError::Unauthorized);
}
// Verificar overflow
let new_value = match data.value.checked_add(amount) {
Some(v) => v,
None => return Err(CounterError::ValueTooHigh),
};
// Verificar límite
if new_value > data.max_value {
return Err(CounterError::ValueTooHigh);
}
// Actualizar
data.value = new_value;
env.storage().persistent().set(&data_key, &data);
Ok(new_value)
}
// ✅ FUNCIONA: Lectura simple
pub fn get_value(env: Env) -> Result<u32, CounterError> {
let data: CounterData = match env.storage().persistent().get(&Symbol::new(&env, "counter")) {
Some(d) => d,
None => return Err(CounterError::NotInitialized),
};
Ok(data.value)
}
}
Lecciones Importantes del Ejemplo :
1. Manejo de Estado Explícito 🎯
// 🔍 Cada get() se maneja explícitamente
let mut data: CounterData = match env.storage().persistent().get(&data_key) {
Some(d) => d, // ✅ Datos encontrados
None => return Err(CounterError::NotInitialized), // ❌ Error específico
};
2. Validaciones Paso a Paso 🛡️
// 🔐 Validación 1: Permisos
if data.owner != caller {
return Err(CounterError::Unauthorized);
}
// 🧮 Validación 2: Overflow
let new_value = match data.value.checked_add(amount) {
Some(v) => v,
None => return Err(CounterError::ValueTooHigh),
};
// 📊 Validación 3: Límites de negocio
if new_value > data.max_value {
return Err(CounterError::ValueTooHigh);
}
3. Errores Específicos y Útiles 📋
#[contracterror]
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
#[repr(u32)]
pub enum CounterError {
ValueTooHigh = 1, // 🚫 Para overflow O límites excedidos
Unauthorized = 2, // 🔒 Para problemas de permisos
NotInitialized = 3, // ❌ Para contrato no inicializado
}
🎉 Conclusión
El manejo de errores en Soroban es fundamental para crear contratos seguros y confiables. Recuerda:
🎯 Sé específico con tus errores
🛡️ Valida todo lo que puedas
🧪 Prueba todos los escenarios
📝 Documenta tu código
🚀 Itera y mejora continuamente
Last updated
Was this helpful?