Aritmética y Desbordamiento
Los programas en cadena utilizan enteros de tamaño fijo con aritmética de desbordamiento en operaciones no comprobadas. Los errores de desbordamiento y redondeo agotan las bóvedas, emiten acciones sin respaldo o bloquean cuentas permanentemente.
Receta
Tarjeta de receta de referencia rápida - lista para copiar y pegar.
let shares = deposit_amount
.checked_mul(total_shares)
.and_then(|v| v.checked_div(total_assets))
.ok_or(ErrorCode::MathOverflow)?;
// Preferir intermediarios u128 para matemáticas de tokens
let out = (amount_in as u128)
.checked_mul(reserve_out as u128)
.and_then(|n| n.checked_div((reserve_in as u128) + (amount_in as u128)))
.ok_or(ErrorCode::MathOverflow)? as u64;Cuándo usar esto:
- Emisión/quema de acciones en bóvedas.
- Cálculos de swaps AMM dentro de programas.
- Acumulación de índices de interés en préstamos.
- Cualquier
+,-,*,/en montos de tokens.
Ejemplo de Trabajo
use anchor_lang::prelude::*;
#[account]
pub struct Pool {
pub total_assets: u64,
pub total_shares: u64,
}
pub fn deposit(pool: &mut Pool, amount: u64) -> Result<u64> {
let shares = if pool.total_shares == 0 {
amount
} else {
(amount as u128)
.checked_mul(pool.total_shares as u128)
.and_then(|n| n.checked_div(pool.total_assets as u128))
.ok_or(ErrorCode::MathOverflow)? as u64
};
pool.total_assets = pool
.total_assets
.checked_add(amount)
.ok_or(ErrorCode::MathOverflow)?;
pool.total_shares = pool
.total_shares
.checked_add(shares)
.ok_or(ErrorCode::MathOverflow)?;
Ok(shares)
}
#[error_code]
pub enum ErrorCode {
MathOverflow,
}Lo que esto demuestra:
- La división después de la multiplicación utiliza el margen de
u128. - El primer depositante emite 1:1 para evitar la división por cero.
- Las actualizaciones de estado utilizan
checked_addpara detectar desbordamientos.
Análisis Profundo
Cómo Funciona
- Las compilaciones BPF pueden no detenerse en desbordamientos para operadores simples en modo de lanzamiento.
checked_*devuelveNoneen caso de desbordamiento - mapear a error del programa.- La división trunca hacia cero - favorece al protocolo en la política de redondeo.
- Los cálculos de tarifas deben usar puntos base:
amount * bps / 10_000.
Política de Redondeo
| Operación | Predeterminado seguro | Por qué |
|---|---|---|
| Emitir acciones | Redondear hacia abajo | Prevenir emisión excesiva |
| Quemar acciones | Redondear deuda hacia arriba | Prevenir subcolateralización |
| Tarifas | Redondear tarifa hacia arriba | Solvencia del protocolo |
| Pago al usuario | Redondear pago hacia abajo | Prevenir drenaje de bóveda |
Notas de Rust
use anchor_lang::solana_program::native_token::LAMPORTS_PER_SOL;
const BPS: u128 = 10_000;
let fee = (amount as u128)
.checked_mul(fee_bps as u128)
.and_then(|v| v.checked_add(BPS - 1)) // truco de división ceil
.and_then(|v| v.checked_div(BPS))
.ok_or(ErrorCode::MathOverflow)? as u64;Trampas
+simple en saldos - Desbordamiento silencioso cerca deu64::MAX. Solución:checked_adden todas partes en los saldos de los usuarios.- Dividir antes de multiplicar - Pierde precisión y permite ataques de polvo. Solución: Multiplicar primero en un tipo más amplio.
- Cero acciones totales - División por cero o ataque de inflación en el primer depósito. Solución: Bloqueo de liquidez mínima o desplazamiento virtual.
- El truncamiento de la conversión -
u128 as u64trunca silenciosamente. Solución:u64::try_fromcon verificación de rango. - Confusión con signos - Uso de precios
i64sin límites. Solución: Documentar rangos; usarchecked_*también en signos. - Diferentes decimales de token - Mezcla de minutas con 6 y 9 decimales. Solución: Normalizar a precisión común antes de las matemáticas.
Alternativas
| Alternativa | Usar cuándo | No usar cuándo |
|---|---|---|
Intermediarios u128 | Matemáticas estándar de tokens | Rutas críticas de CU (aún así, a menudo vale la pena) |
| Crates de punto fijo | Curvas complejas | Matemáticas simples de acciones |
| Cálculo fuera de cadena | Solo vista | Liquidación autorizada |
Preguntas Frecuentes
¿El modo de depuración de Rust detecta desbordamientos en cadena?
No, las BPF desplegadas usan semántica de lanzamiento; nunca confíes en las detenciones de depuración.
¿Qué es el ataque del primer depositante?
El atacante manipula un depósito diminuto + donación para inflar el precio de las acciones; mitigar con reservas virtuales o depósito mínimo.
¿Debo usar saturating_add?
Solo cuando el capping es el comportamiento previsto; los saldos de las bóvedas deben generar un error en caso de desbordamiento, no saturarse.
¿Cómo interactúan las tarifas de bps con el desbordamiento?
Calcular en u128: amount * bps / 10_000 con operaciones comprobadas en cada paso.
¿Alguna vez están bien los flotantes?
Nunca en la liquidación en cadena; los flotantes no son deterministas entre arquitecturas.
¿Cómo audito las matemáticas?
Mapear cada operación aritmética; buscar +, *, / sin checked_ en el código fuente del programa.
¿Anchor ayuda con las matemáticas?
No, tú implementas matemáticas seguras; ver Aritmética Segura.
¿Cómo encajan los exponentes de las oráculos?
Escalar los precios a un exponente común con pow10 comprobado antes de combinar los montos.
¿Puede el fuzzing encontrar desbordamientos?
Sí, usa el fuzzing de Trident con cantidades de entrada extremas (Fuzzing de Programas).
¿Qué costo de CU añade u128?
Modesto en comparación con el costo de explotación; prefiere la seguridad a menos que el perfilado demuestre que es un cuello de botella.
Relacionado
- Aritmética Segura - Patrones de Rust
- Ataques de Validación de Cuentas - exploits económicos
- Catálogo de Ataques de Sealevel - ataques de inflación
- Fuzzing de Programas - descubrimiento de casos límite
Versiones de la pila: Esta página fue escrita para Agave 4.1.1, Solana CLI 3.0.10, Anchor 0.32.1, anchor-lang 0.32.1, Rust 1.91.1, @solana/kit 7.0.0, Surfpool 0.12.0, y LiteSVM 0.6.x.