🔍 Seguridad · Análisis Dimensional de Código

Informe de Auditoría Dimensional — CultivaDeFi Protocol

📅 2026-06-16
🗂️ Archivos en scope: 3
📝 Anotaciones añadidas: 24
🐛 Bugs confirmados: 3
✓ analisis-dimensional-codigo v1.0.0
Critical
1
Bug confirmado
High
2
Desajuste de escala
Medium
1
Incertidumbre resuelta
Cobertura
100%
3/3 archivos validados
Pipeline de Ejecución
✓ Done
Paso 1
Escaneo y Vocabulario
3 archivos en scope
3 unidades base descubiertas
✓ Done
Paso 2
Anotación de Anclajes
24 anotaciones añadidas
3/3 archivos ANNOTATED
✓ Done
Paso 3
Propagación Dimensional
18 inferidas CERTAIN
4 mismatches detectados
✓ Done
Paso 4
Detección de Bugs
3 bugs confirmados
1 falso positivo
Vocabulario Dimensional Descubierto
Unidades Base
tok wei UoA bps sec
Unidades Derivadas
UoA/tok tok/sec tok² UoA/wei
Prefijos de Precisión
D18 D27 D8 D4 WAD RAY
Ejemplos de Código Anotado
StakingPool.sol — Función calculateReward (anotación + BUG) CRITICAL: DIM-001
// ANTES (sin anotaciones): function calculateReward( uint256 stakedAmount, uint256 rewardRate, uint256 duration ) public pure returns (uint256) { return stakedAmount * rewardRate * duration / 1e18; // ← BUG }   // DESPUÉS (con análisis dimensional): function calculateReward( uint256 stakedAmount, /* D18{tok} */ uint256 rewardRate, /* D27{tok/sec} — RAY precision */ uint256 duration /* D0{sec} */ ) public pure returns (uint256 /* D18{tok} */) { // ⚠️ MISMATCH: stakedAmount(D18) * rewardRate(D27) * duration(D0) / 1e18 → D27{tok²/sec} // Resultado INCORRECTO. Debe dividirse por 1e27, no 1e18. return stakedAmount * rewardRate * duration / 1e27; /* → D18{tok} ✓ */ }
PriceOracle.sol — Función getCollateralValue (BUG D8→D18) HIGH: DIM-002
// Chainlink devuelve precio en D8, pero se usa como D18 sin conversión: uint256 chainlinkPrice = oracle.latestAnswer(); /* D8{UoA/tok} */   // ANTES — mezcla D8 con D18 sin escalar: uint256 collateralValue = (amount /* D18{tok} */ * chainlinkPrice) / 1e18; // ⚠️ DIM-002: resultado en D8{UoA} — desfase 1e10 respecto a D18{UoA}   // FIX — escalar Chainlink D8 → D18 antes de multiplicar: uint256 scaledPrice = chainlinkPrice * 1e10; /* D18{UoA/tok} */ uint256 collateralValue = (amount * scaledPrice) / 1e18; /* D18{UoA} ✓ */
RewardDistributor.sol — Cálculo de porcentaje con basisPoints HIGH: DIM-003
uint256 basisPoints; /* D4{bps} — sin dimensión física, 0-10000 */ uint256 totalSupply; /* D18{tok} */   // ANTES — basisPoints se multiplica directamente sin normalizar: uint256 share = (amount /* D18{tok} */ * basisPoints) / totalSupply; // ⚠️ DIM-003: dimensión resultante D4{tok·bps/tok} → magnitud 10000x mayor   // FIX — dividir por 10000 para normalizar basisPoints: uint256 share = (amount * basisPoints) / (10000 * totalSupply / 1e18); // ó equivalente: amount * basisPoints / 10000 → D18{tok} ✓
Findings Confirmados
ID Severidad Archivo Línea Descripción Tipo
DIM-001 CRITICAL StakingPool.sol 87 División por 1e18 en cálculo de recompensa donde el producto tiene escala D27 (RAY). Resultado 1e9× menor de lo esperado. stakedAmount(D18) × rewardRate(D27) → requiere ÷1e27, no ÷1e18 Scale Mismatch
DIM-002 HIGH PriceOracle.sol 134 Precio Chainlink D8 consumido como D18 sin escalar. Valor de colateral subestimado 10,000,000,000× (1e10). Falta multiplicar chainlinkPrice × 1e10 antes de usar en cálculo D18 Precision Loss
DIM-003 HIGH RewardDistributor.sol 62 basisPoints (D4, 0-10000) multiplicado directamente con amounts D18 sin normalizar. Distribuye 10000× más tokens de los previstos. Falta división por 10000 para convertir bps a ratio adimensional Unit Mismatch
DIM-004 MEDIUM StakingPool.sol 201 Acumulador accRewardPerShare pierde precisión al hacer truncamiento D27→D18 en loop. Acumulación de error en periodos largos. Incertidumbre resuelta: CERTAIN tras propagación, severidad contenida Precision Truncation
ℹ️ 1 mismatch Step 3 refutado como falso positivo (DIM-P01, StakingPool.sol:155 — variable loop index sin dimensión financiera).
Cobertura de Archivos
Archivo Prioridad Step 2 Step 3 Step 4 Anotaciones Findings
StakingPool.sol CRITICAL ANNOTATED PROPAGATED VALIDATED 11 DIM-001, DIM-004
PriceOracle.sol HIGH ANNOTATED PROPAGATED VALIDATED 8 DIM-002
RewardDistributor.sol HIGH ANNOTATED PROPAGATED VALIDATED 5 DIM-003
Funciones anotadas: 14 / 14
Variables anotadas: 31 / 37
Archivos sin bloqueados: 0 / 3
Output JSON Estructurado (Final Summary)
{
  "mode": "full-auto",
  "project_root": "/cultiva-defi/contracts",
  "vocabulary": {
    "base_units": ["tok", "wei", "UoA", "bps", "sec"],
    "derived_units": ["UoA/tok", "tok/sec", "UoA/wei"],
    "precision_prefixes": ["D8", "D18", "D27", "D4"]
  },
  "annotations": {
    "total_added": 24,
    "by_confidence": { "CERTAIN": 18, "INFERRED": 5, "UNCERTAIN": 1 }
  },
  "findings": {
    "critical": 1,
    "high": 2,
    "medium": 1,
    "false_positives_refuted": 1,
    "details": [
      { "id": "DIM-001", "severity": "CRITICAL", "file": "StakingPool.sol:87", "type": "Scale Mismatch D18×D27÷1e18" },
      { "id": "DIM-002", "severity": "HIGH", "file": "PriceOracle.sol:134", "type": "Precision Loss D8→D18 missing ×1e10" },
      { "id": "DIM-003", "severity": "HIGH", "file": "RewardDistributor.sol:62", "type": "Unit Mismatch bps×D18 without ÷10000" },
      { "id": "DIM-004", "severity": "MEDIUM", "file": "StakingPool.sol:201", "type": "Precision Truncation D27→D18 in accumulator loop" }
    ]
  },
  "coverage": {
    "in_scope_files": 3,
    "anchor_reviewed_files": "3/3",
    "propagation_reviewed_files": "3/3",
    "validation_reviewed_files": "3/3",
    "annotated_functions": "14/14",
    "annotated_variables": "31/37",
    "unprocessed_files": []
  }
}