Skip to main content

logic-error

Auto-loaded by logic-auditor agent during Phase 2. Provides detection patterns for: division before multiplication, first depositor attacks, precision loss, unchecked blocks, missing slippage, edge cases. Core artifact: Calculation Flow Map.

Ir para a instalação

Informações da origem

Repositório
BitterSecurity/Vigilo
Última atividade na origem
26 de janeiro de 2026 às 12:30
Idioma detectado do SKILL.md
inglês
Estrelas
66
Forks
17

Opções de instalação

Por padrão, está selecionado o prompt que primeiro revisa a origem. Você pode mudar para um comando direto ou baixar uma cópia local.

Revise os arquivos de origem

Leia o SKILL.md e os arquivos complementares exibidos pelo SkillsMP antes de decidir se vai instalar.

Exibindo SKILL.md

SKILL.md
Instruções da origem · Visualização somente leitura
name
logic-error
description
Auto-loaded by logic-auditor agent during Phase 2. Provides detection patterns for: division before multiplication, first depositor attacks, precision loss, unchecked blocks, missing slippage, edge cases. Core artifact: Calculation Flow Map.
user-invocable
false
# Business Logic Vulnerability Analysis **2025 Statistics**: Logic errors caused **$63.8M+** losses, Input validation = **34.6% of vulnerabilities**, Rounding attacks (Bunni) = **$2.4M-$8.3M**. --- ## Why Logic Bugs Happen (Root Causes) ### Root Cause 1: Integer-Only Arithmetic Solidity has NO floating point. Every division truncates. ```solidity // In normal math: 5 / 2 = 2.5 // In Solidity: 5 / 2 = 2 ← Where does 0.5 go? uint256 fee = amount / 10000 * feeRate; // If amount = 5000, feeRate = 100: // 5000 / 10000 = 0 ← Division first! // 0 * 100 = 0 ← Fee bypassed! ``` **Attacker's view**: "Every time they divide, precision is lost. I'll make sure it's lost to ME." ### Root Cause 2: Operation Order Division before multiplication destroys precision. ```solidity // VULNERABLE: Divide first uint256 result = a / b * c; // @audit Precision lost! // SECURE: Multiply first uint256 result = a * c / b; // Preserves more precision // Example with a=100, b=3, c=3: // Wrong order: 100/3*3 = 33*3 = 99 (lost 1) // Right order: 100*3/3 = 300/3 = 100 (exact) ``` **Detection**: Any `X / Y * Z` pattern is suspect. Check if `X * Z / Y` is possible. ### Root Cause 3: Rounding Direction Mismatch Protocol rounds in attacker's favor, not protocol's favor. ```solidity // VULNERABLE: Rounding favors user on withdraw function withdraw(uint256 shares) external { uint256 assets = shares * totalAssets / totalSupply; // @audit Rounds DOWN // Attacker withdraws many small amounts // Each time keeps the rounded-down dust } // SECURE: Round against the user function withdraw(uint256 shares) external { uint256 assets = shares * totalAssets / totalSupply; // Still rounds down // But on DEPOSIT, also round down (user gets fewer shares) // Protocol never loses from rounding } ``` **Rule**: Round DOWN on withdraw (user gets less), round DOWN on deposit (user pays more). ### Root Cause 4: Missing Edge Case Handling Zero, one, max, first, last - all create special behaviors. ```solidity // VULNERABLE: No zero check function distribute(uint256 amount, uint256 recipients) external { uint256 perPerson = amount / recipients; // @audit recipients = 0 → revert // Or recipients = 1000000 → perPerson = 0 } ``` **Attacker's view**: "What happens at the boundaries? That's where bugs hide." --- ## The Calculation Flow Map (Core Artifact) Trace every arithmetic operation: ``` Input (amount, shares, etc.) ↓ Validation (bounds check? zero check?) ↓ Calculation (what order? division when?) ↓ Rounding (which direction? who loses?) ↓ State Update (invariant preserved?) ↓ Output (expected vs actual?) ``` Document each calculation: | Function | Operation | Order | Rounding | Risk | |----------|-----------|-------|----------|------| | mint() | shares = assets * supply / total | Mul first ✓ | Down (protocol wins) | First depositor | | fee() | fee = amount / 10000 * rate | Div first ✗ | Down → zero | Fee bypass | | redeem() | assets = shares * total / supply | Mul first ✓ | Down (user loses) | Check | --- ## Detection Patterns ### Pattern 1: Division Before Multiplication **Root Cause**: Operation Order ```solidity // VULNERABLE: Precision loss uint256 fee = amount / 10000 * feeRate; // If amount < 10000, fee = 0 regardless of feeRate! uint256 share = deposit / totalAssets * totalSupply; // Small deposits get 0 shares! ``` **Attack Flow**: 1. Find function with `a / b * c` pattern 2. Input value where `a < b` 3. Result = 0, bypassing intended logic 4. Repeat to accumulate benefit **Search Queries**: ``` Grep("/.*\\*", glob="**/*.sol") Grep("\\*/", glob="**/*.sol") ``` **Verification Questions**: - Can the division result in zero? - Would reordering to `a * c / b` be safe (no overflow)? - What's the minimum input that gives non-zero result? ### Pattern 2: First Depositor / Vault Inflation Attack **Root Cause**: Rounding + Empty State ```solidity // VULNERABLE: Standard ERC4626 share calculation function deposit(uint256 assets) external returns (uint256 shares) { if (totalSupply == 0) { shares = assets; // @audit First depositor sets the ratio! } else { shares = assets * totalSupply / totalAssets; } } ``` **Attack Flow** (Inflation Attack): 1. Deposit 1 wei → get 1 share 2. Donate 1,000,000 tokens directly to vault (not via deposit) 3. Now: totalAssets = 1,000,001, totalSupply = 1 4. Victim deposits 999,999 → shares = 999,999 * 1 / 1,000,001 = 0 5. Victim gets 0 shares, loses entire deposit 6. Attacker redeems 1 share → gets everything **Search Queries**: ``` Grep("totalSupply.*==.*0|totalSupply\\(\\).*==.*0", glob="**/*.sol") Grep("balanceOf\\(address\\(this\\)\\)", glob="**/*.sol") Grep("ERC4626|vault|shares", glob="**/*.sol") ``` **Verification Questions**: - What happens when totalSupply = 0? - Is there virtual shares/assets offset? - Is there minimum deposit requirement? - Can assets be donated without minting shares? ### Pattern 3: Unchecked Return Values **Root Cause**: Silent Failure Assumption ```solidity // VULNERABLE: USDT returns false instead of reverting IERC20(token).transfer(recipient, amount); // @audit Return not checked! // If transfer fails, execution continues with wrong state // SECURE: Use SafeERC20 SafeERC20.safeTransfer(IERC20(token), recipient, amount); ``` **Search Queries**: ``` Grep("\\.transfer\\(|\\.transferFrom\\(", glob="**/*.sol") Grep("safeTransfer|SafeERC20", glob="**/*.sol") ``` ### Pattern 4: Integer Overflow in Unchecked Blocks **Root Cause**: Bypassing Solidity 0.8+ Safety ```solidity // VULNERABLE: Intentional unchecked can overflow unchecked { balance += amount; // @audit Can wrap to 0 if balance + amount > MAX counter--; // @audit Can wrap to MAX if counter = 0 } ``` **Search Queries**: ``` Grep("unchecked\\s*\\{", glob="**/*.sol") Grep("assembly\\s*\\{", glob="**/*.sol") ``` **Verification Questions**: - Why is unchecked used here? - Can inputs cause overflow/underflow? - Is the unchecked block necessary? ### Pattern 5: Missing Slippage Protection **Root Cause**: No Minimum Output Enforcement ```solidity // VULNERABLE: User accepts any output function swap(uint256 amountIn) external { uint256 amountOut = calculateOutput(amountIn); token.transfer(msg.sender, amountOut); // @audit No minimum check! } // SECURE: Enforce minimum function swap(uint256 amountIn, uint256 minAmountOut) external { uint256 amountOut = calculateOutput(amountIn); require(amountOut >= minAmountOut, "Slippage"); token.transfer(msg.sender, amountOut); } ``` **Search Queries**: ``` Grep("amountOutMin|minAmountOut|minOut|slippage", glob="**/*.sol") Grep("swap|exchange|trade", glob="**/*.sol") ``` ### Pattern 6: Missing Zero/Address Validation **Root Cause**: Missing Input Validation ```solidity // VULNERABLE: Zero address bricks contract function setAdmin(address newAdmin) external onlyOwner { admin = newAdmin; // @audit address(0) = no admin forever } // VULNERABLE: Division by zero function distribute(uint256 total, uint256 count) external { uint256 each = total / count; // @audit count = 0 → revert } // VULNERABLE: Zero amount wastes gas or causes issues function deposit(uint256 amount) external { // @audit amount = 0 → user gets 0 shares, wasted gas } ``` **Search Queries**: ``` Grep("address.*=|= address", glob="**/*.sol") Grep("require.*!=.*0|require.*>.*0", glob="**/*.sol") ``` --- ## Edge Case Testing Matrix For EVERY value-handling function: | Edge Case | Test Value | Common Bug | What to Check | |-----------|------------|------------|---------------| | **Zero** | `0` | Division by zero, empty transfer | Does it revert or return 0? | | **One** | `1` | Rounds to zero, off-by-one | Minimum meaningful input? | | **Max** | `type(uint256).max` | Overflow, gas exhaustion | Does unchecked wrap? | | **First user** | Empty state | Ratio manipulation | Who sets initial ratio? | | **Last user** | Only remaining | Stuck funds, dust | Can final user withdraw all? | | **Boundary** | Just above/below limit | Off-by-one, `<` vs `<=` | Fencepost errors? | --- ## Invariant Verification Checklist | Invariant | How to Verify | Common Violation | |-----------|--------------|------------------| | `totalSupply == sum(balances)` | Trace all mint/burn | Donation attack | | `totalAssets >= totalDebt` | Check after each action | Flash loan attack | | `shares * pricePerShare >= deposit` | Rounding check | Precision loss | | `sum(rewards) <= rewardPool` | Trace all claims | Over-distribution | --- ## Search Query Reference ``` # Find arithmetic operations Grep("/.*\\*|\\*/", glob="**/*.sol") Grep("mulDiv|wadMul|rayMul|FullMath", glob="**/*.sol") # Find potential overflow Grep("unchecked\\s*\\{", glob="**/*.sol") Grep("assembly\\s*\\{", glob="**/*.sol") # Find share calculations Grep("totalSupply|totalAssets|pricePerShare", glob="**/*.sol") Grep("ERC4626|vault|shares", glob="**/*.sol") # Find missing checks Grep("\\.transfer\\(|\\.transferFrom\\(", glob="**/*.sol") Grep("require.*!=|require.*>", glob="**/*.sol") # Find slippage Grep("amountOutMin|minAmount|slippage", glob="**/*.sol") ``` --- ## Rationalization Table (Reject These Excuses) | Excuse | Attacker's Reality | |--------|-------------------| | "It's just rounding" | Bunni lost $2.4M-$8.3M to rounding. Repeated calls accumulate. | | "Users won't send dust" | Attackers absolutely will. Dust inputs are the exploit. | | "Math is too complex" | MEV bots automate arbitrarily complex calculations. | | "First depositor is trusted" | First depositor attack is #1 vault exploit in 2025. | | "Frontend validates" | On-chain must be secure standalone. | | "This edge case is unlikely" | Every edge case is an attacker's opportunity. | | "Solidity 0.8 prevents overflow" | Unchecked blocks and assembly bypass this. |
Ver no GitHub