2 funciones requieren revisión inmediata — superficie de ataque crítica
trustProtocol() en StrategyManager.sol usa una lista de confianza dinámica con control de acceso ambiguo; y execute() permite llamadas arbitrarias a contratos "de confianza".
Inicializador sin protección detectado
VaultCore.initialize() es callable por cualquier dirección antes del primer setup. Si el deploy no se inicializa en la misma transacción, un atacante podría tomar control del vault.
Puntos de Entrada Públicos (Sin Restricción)
Funciones con cambio de estado accesibles por cualquier dirección — prioridad máxima en análisis de superficie de ataque
8 funciones
| Función | Archivo | Riesgo | Notas |
|---|---|---|---|
| initialize(address, address, uint256, uint256) | VaultCore.sol:L44 | ALTO | ⚠ Inicializador sin protección en primera llamada — reentrancia en deploy |
| deposit(uint256, address) | VaultCore.sol:L57 | MEDIO | Entrada principal de capital — revisar lógica de cálculo de shares (posible manipulación de firstDeposit) |
| withdraw(uint256, address) | VaultCore.sol:L74 | MEDIO | Salida de capital — ReentrancyGuard aplicado; revisar rounding en cálculo de fees |
| burn(uint256) | GreenToken.sol:L33 | BAJO | Auto-burn de tokens propios; sin restricción de rol |
| transfer(address, uint256) | GreenToken.sol:L47 | BAJO | Override ERC20 con blacklist check; patrón correcto |
| transferFrom(address, address, uint256) | GreenToken.sol:L54 | BAJO | Override ERC20 con blacklist check; patrón correcto |
| approve(address, uint256) | GreenToken.sol:L61 | BAJO | Override ERC20; revisión correcta de blacklist |
| voteForPause() | EmergencyPause.sol:L60 | MEDIO | Cualquier pauser registrado puede votar — revisar quién puede ser añadido como pauser |
Puntos de Entrada con Control de Roles
Funciones restringidas a roles específicos — verificar implementación real de cada modificador
19 funciones
Admin / Owner (DEFAULT_ADMIN_ROLE · onlyOwner)
| Función | Archivo | Restricción | Notas |
|---|---|---|---|
| setStrategyManager(address) | VaultCore.sol:L101 | onlyRole(DEFAULT_ADMIN_ROLE) |
Actualiza estrategia — cambio de dirección crítico |
| setFeeReceiver(address) | VaultCore.sol:L110 | onlyRole(DEFAULT_ADMIN_ROLE) |
Redirige fees a nueva dirección |
| unpause() | VaultCore.sol:L126 | onlyRole(DEFAULT_ADMIN_ROLE) |
Solo admin puede reactivar el vault |
| addStrategy(address, uint256) | StrategyManager.sol:L42 | onlyOwner |
Registra nueva estrategia y la marca como trusted |
| removeStrategy(address) | StrategyManager.sol:L51 | onlyOwner |
Elimina estrategia del pool activo |
| execute(address, bytes) | StrategyManager.sol:L73 | onlyOwner |
⚠ Llamada arbitraria a contratos trusted — riesgo de delegatecall encubierto |
| setKeeper(address) | StrategyManager.sol:L81 | onlyOwner |
Actualiza dirección del keeper |
| setSlippageTolerance(uint256) | StrategyManager.sol:L88 | onlyOwner |
Max 5% — verificar enforcement en swaps |
| emergencyDrain(address, uint256) | StrategyManager.sol:L94 | onlyOwner |
⚠ Drena tokens arbitrarios al owner — función de alto riesgo sin timelock |
| setBlacklist(address, bool) | GreenToken.sol:L38 | onlyRole(DEFAULT_ADMIN_ROLE) |
Puede congelar fondos de cualquier usuario |
| setMaxSupply(uint256) | GreenToken.sol:L45 | onlyRole(DEFAULT_ADMIN_ROLE) |
Puede aumentar suministro máximo sin límite superior |
Manager (MANAGER_ROLE)
| Función | Archivo | Restricción | Notas |
|---|---|---|---|
| rebalance(uint256) | VaultCore.sol:L95 | onlyRole(MANAGER_ROLE) |
Redistribuye capital entre estrategias |
| setFees(uint256, uint256) | VaultCore.sol:L104 | onlyRole(MANAGER_ROLE) |
Max performanceFee 30%, withdrawalFee 5% — verificar checks |
Guardian / Pauser
| Función | Archivo | Restricción | Notas |
|---|---|---|---|
| emergencyWithdraw(address, address) | VaultCore.sol:L89 | hasRole(GUARDIAN_ROLE) |
Retiro forzado a receptor arbitrario — verificar que receiver sea controlado |
| pause() | VaultCore.sol:L122 | onlyRole(GUARDIAN_ROLE) |
Pausa el vault instantáneamente |
| triggerEmergencyPause() | EmergencyPause.sol:L46 | onlyGuardian (guardian || admin) |
Pausa global del protocolo |
| addPauser(address) | EmergencyPause.sol:L70 | onlyAdmin |
Añade votantes al mecanismo de pausa colectiva |
Keeper (KEEPER_ROLE · msg.sender == keeper)
| Función | Archivo | Restricción | Notas |
|---|---|---|---|
| harvest() | VaultCore.sol:L89 | hasRole(KEEPER_ROLE) |
Llama a strategy.harvest() y actualiza totalAssets |
| harvest() | StrategyManager.sol:L59 | onlyKeeper (keeper || owner) |
Itera estrategias activas con llamada dinámica — revisar reentrancy con strategies externas |
Minter / Burner (GreenToken)
| Función | Archivo | Restricción | Notas |
|---|---|---|---|
| mint(address, uint256) | GreenToken.sol:L27 | MINTER_ROLE | Limitado por maxSupply — quién tiene este rol importa |
| burnFrom(address, uint256) | GreenToken.sol:L39 | BURNER_ROLE | Quema tokens de cualquier dirección sin aprobación |
Admin (EmergencyPause — 2-step transfer)
| Función | Archivo | Restricción | Notas |
|---|---|---|---|
| liftEmergencyPause() | EmergencyPause.sol:L52 | onlyAdmin |
Solo admin puede desactivar pausa global |
| transferAdmin(address) | EmergencyPause.sol:L77 | onlyAdmin |
Patrón 2-step — correcto |
| updateGuardian(address) | EmergencyPause.sol:L89 | onlyAdmin |
Reemplaza guardian sin timelock |
| resetVotes(address[]) | EmergencyPause.sol:L96 | onlyAdmin |
Admin puede resetear votos de pausa — posible abuso para bloquear mecanismo de pausa colectiva |
Pending Admin (EmergencyPause)
| Función | Archivo | Restricción | Notas |
|---|---|---|---|
| acceptAdmin() | EmergencyPause.sol:L84 | msg.sender == pendingAdmin |
Completa la transferencia de admin — verificar que pendingAdmin no sea address(0) |
Restricción Ambigua — Revisión Manual Requerida
Funciones con patrones de control de acceso que requieren verificación de la implementación real del modificador
2 funciones
| Función | Archivo | Patrón detectado | Motivo de revisión |
|---|---|---|---|
| trustProtocol(address, bool) | StrategyManager.sol:L99 | require(trusted[msg.sender] || msg.sender == owner) |
⚠ Lista de confianza dinámica: cualquier protocolo ya trusted puede añadir nuevos. Posible escalada de privilegios — verificar si trusted y trustedProtocols son el mismo mapping. |
| execute(address, bytes) | StrategyManager.sol:L73 | onlyOwner + trustedProtocols[target] |
⚠ Función de ejecución arbitraria: owner puede llamar a cualquier función en contratos "trusted". Si trustProtocol() tiene vulnerabilidad, un atacante puede añadir contratos maliciosos y luego ejecutar código arbitrario. |
Solo-Contrato (Puntos de Integración Interna)
Callbacks y hooks que solo deberían ser llamados por contratos externos específicos — definen fronteras de confianza
4 funciones
| Función | Archivo | Llamante esperado | Verificación |
|---|---|---|---|
| executeOperation(address[], uint256[], uint256[], address, bytes) | VaultCore.sol:L130 | AAVE Flash Loan Pool | tx.origin != msg.sender + msg.sender == strategyManager — verificar dirección AAVE en mainnet |
| rebalance(uint256) | StrategyManager.sol:L69 | VaultCore.sol | onlyVault — correcto; verificar que vault no pueda ser reemplazado en el constructor |
| uniswapV3SwapCallback(int256, int256, bytes) | StrategyManager.sol:L103 | Uniswap V3 Pool | tx.origin != msg.sender + trustedProtocols[msg.sender] — depende de que la lista de trusted sea correcta |
| executeOperation(address, uint256, uint256, address, bytes) | StrategyManager.sol:L113 | AAVE Flash Loan Pool | tx.origin != msg.sender — falta validar dirección del pool AAVE |
Archivos Analizados
Todos los contratos en scope con su conteo de puntos de entrada
src/VaultCore.sol
12 entry points
Solidity ^0.8.20
src/StrategyManager.sol
12 entry points
⚠ Revisión urgente
src/GreenToken.sol
7 entry points
ERC20 + Roles
src/EmergencyPause.sol
7 entry points
Multi-role