¿Qué hace Seguridad en DeFi AMM?
Patrones críticos de vulnerabilidad e implementaciones robustas para contratos AMM en Solidity, vaults de LP y funciones de swap.
Cuándo usarlo
- Escribir o auditar un contrato AMM o pool de liquidez en Solidity
- Implementar flujos de swap, depósito, retiro, mint o burn que manejen saldos de tokens
- Revisar cualquier contrato que use
token.balanceOf(address(this))en cálculos de participaciones o reservas - Agregar configuradores de tarifas, pausadores, actualizaciones de oráculos u otras funciones administrativas a un protocolo DeFi
Cómo funciona
Utilícelo como una biblioteca de patrones más lista de verificación. Revise cada punto de entrada de usuario según las categorías a continuación y prefiera los ejemplos robustos sobre variantes hechas a mano.
Seguridad de ejecución
Los comandos de shell en esta habilidad son ejemplos locales de auditoría. Ejecútelos solo en un checkout de confianza o en un entorno aislado desechable, y no inserte nombres de contratos, rutas, URLs RPC, claves privadas o indicadores suministrados por el usuario no confiables en los comandos de shell. Pregunte antes de instalar herramientas o ejecutar trabajos largos de fuzzing/análisis estático que puedan consumir recursos locales o pagos significativos.
Nunca incluya secretos, claves privadas, frases semilla, tokens API o credenciales de firma de mainnet en ejemplos de comandos, registros o informes.
Ejemplos
Reentrancy: aplicar orden CEI
Vulnerable:
function withdraw(uint256 amount) external {
require(balances[msg.sender] >= amount);
token.transfer(msg.sender, amount);
balances[msg.sender] -= amount;
}
Seguro:
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
using SafeERC20 for IERC20;
function withdraw(uint256 amount) external nonReentrant {
require(balances[msg.sender] >= amount, "Insuficiente");
balances[msg.sender] -= amount;
token.safeTransfer(msg.sender, amount);
}
No escriba su propio guardia cuando exista una biblioteca robusta.
Ataques de donación o inflación
Usar token.balanceOf(address(this)) directamente para cálculos de participaciones permite que un atacante manipule el denominador enviando tokens al contrato fuera de la ruta prevista.
// Vulnerable
function deposit(uint256 assets) external returns (uint256 shares) {
shares = (assets * totalShares) / token.balanceOf(address(this));
}
// Seguro
uint256 private _totalAssets;
function deposit(uint256 assets) external nonReentrant returns (uint256 shares) {
uint256 balBefore = token.balanceOf(address(this));
token.safeTransferFrom(msg.sender, address(this), assets);
uint256 received = token.balanceOf(address(this)) - balBefore;
shares = totalShares == 0 ? received : (received * totalShares) / _totalAssets;
_totalAssets += received;
totalShares += shares;
}
Realice un seguimiento contable interno y mida los tokens realmente recibidos.
Manipulación de oráculos
Los precios spot son manipulables con préstamos flash. Prefiera TWAP.
uint32[] memory secondsAgos = new uint32[](2);
secondsAgos[0] = 1800;
secondsAgos[1] = 0;
(int56[] memory tickCumulatives,) = IUniswapV3Pool(pool).observe(secondsAgos);
int24 twapTick = int24(
(tickCumulatives[1] - tickCumulatives[0]) / int56(uint56(30 minutes))
);
uint160 sqrtPriceX96 = TickMath.getSqrtRatioAtTick(twapTick);
Protección contra slippage
Cada ruta de swap necesita slippage proporcionado por el llamador y una fecha límite.
function swap(
uint256 amountIn,
uint256 amountOutMin,
uint256 deadline
) external returns (uint256 amountOut) {
require(block.timestamp <= deadline, "Expirado");
amountOut = _calculateOut(amountIn);
require(amountOut >= amountOutMin, "Slippage excedido");
_executeSwap(amountIn, amountOut);
}
Matemáticas de reserva seguras
import {FullMath} from "@uniswap/v3-core/contracts/libraries/FullMath.sol";
uint256 result = FullMath.mulDiv(a, b, c);
Para cálculos grandes de reservas, evite el naive a * b / c cuando exista riesgo de desbordamiento.
Controles administrativos
import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol";
contract MyAMM is Ownable2Step {
function setFee(uint256 fee) external onlyOwner { ... }
function pause() external onlyOwner { ... }
}
Prefiera la aceptación explícita para la transferencia de propiedad y restrinja cada ruta privilegiada.
Lista de verificación de seguridad
- Los puntos de entrada expuestos a reentrancy usan
nonReentrant - Se respeta el orden CEI
- El cálculo de participaciones no depende de
balanceOf(address(this))sin procesar - Las transferencias ERC-20 usan
SafeERC20 - Los depósitos miden los tokens realmente recibidos
- Las lecturas de oráculos usan TWAP u otra fuente resistente a manipulación
- Los swaps requieren
amountOutMinydeadline - Los cálculos de reservas sensibles a desbordamiento usan primitivas seguras como
mulDiv - Las funciones administrativas tienen control de acceso
- Existe una pausa de emergencia y está probada
- Se ejecutan análisis estático y fuzzing antes de producción
Herramientas de auditoría
pip install slither-analyzer
slither . --exclude-dependencies
echidna-test . --contract YourAMM --config echidna.yaml
forge test --fuzz-runs 10000