$npx -y skills add quillai-network/quillshield_skills --skill input-arithmetic-safetyDetects input validation failures and arithmetic vulnerabilities in smart contracts. Covers missing zero-address and zero-amount checks, division-before-multiplication precision loss, rounding direction exploitation, ERC4626 vault share inflation attacks, unsafe integer casting,
| 1 | # Input & Arithmetic Safety |
| 2 | |
| 3 | Detect **input validation failures** (the #1 direct exploitation cause at 34.6% of all contract exploits) and **arithmetic vulnerabilities** that persist even with Solidity 0.8+ checked math — precision loss, rounding exploitation, unsafe casting, and share price manipulation. |
| 4 | |
| 5 | ## When to Use |
| 6 | |
| 7 | - Auditing any contract with public/external functions accepting user-supplied parameters |
| 8 | - Reviewing DeFi protocols with fee calculations, share pricing, or exchange rates |
| 9 | - Analyzing vault/staking contracts for rounding or first-depositor attacks |
| 10 | - Checking contracts with `unchecked` blocks for overflow/underflow risks |
| 11 | - Verifying arithmetic in token minting, burning, and distribution logic |
| 12 | |
| 13 | ## When NOT to Use |
| 14 | |
| 15 | - Access control analysis (use semantic-guard-analysis) |
| 16 | - Reentrancy detection (use reentrancy-pattern-analysis) |
| 17 | - Full multi-dimensional audit (use behavioral-state-analysis) |
| 18 | |
| 19 | ## Part 1: Input Validation Analysis |
| 20 | |
| 21 | ### Critical Missing Validations |
| 22 | |
| 23 | **Zero Address Check:** |
| 24 | |
| 25 | ```solidity |
| 26 | // VULNERABLE: No zero address check |
| 27 | function setAdmin(address newAdmin) external onlyOwner { |
| 28 | admin = newAdmin; // Can set admin to address(0) — locking out admin forever |
| 29 | } |
| 30 | |
| 31 | // SAFE |
| 32 | function setAdmin(address newAdmin) external onlyOwner { |
| 33 | require(newAdmin != address(0), "Zero address"); |
| 34 | admin = newAdmin; |
| 35 | } |
| 36 | ``` |
| 37 | |
| 38 | **Zero Amount Check:** |
| 39 | |
| 40 | ```solidity |
| 41 | // VULNERABLE: Allows zero-amount operations |
| 42 | function deposit(uint256 amount) external { |
| 43 | balances[msg.sender] += amount; |
| 44 | emit Deposit(msg.sender, amount); |
| 45 | // Zero deposit: wastes gas, pollutes events, may affect accounting |
| 46 | } |
| 47 | |
| 48 | // SAFE |
| 49 | function deposit(uint256 amount) external { |
| 50 | require(amount > 0, "Zero amount"); |
| 51 | balances[msg.sender] += amount; |
| 52 | } |
| 53 | ``` |
| 54 | |
| 55 | **Array Length Validation:** |
| 56 | |
| 57 | ```solidity |
| 58 | // VULNERABLE: No length check |
| 59 | function batchTransfer(address[] calldata recipients, uint256[] calldata amounts) external { |
| 60 | for (uint i = 0; i < recipients.length; i++) { |
| 61 | transfer(recipients[i], amounts[i]); // Out-of-bounds if arrays differ in length |
| 62 | } |
| 63 | } |
| 64 | |
| 65 | // SAFE |
| 66 | function batchTransfer(address[] calldata recipients, uint256[] calldata amounts) external { |
| 67 | require(recipients.length == amounts.length, "Length mismatch"); |
| 68 | require(recipients.length <= MAX_BATCH_SIZE, "Batch too large"); |
| 69 | // ... |
| 70 | } |
| 71 | ``` |
| 72 | |
| 73 | **Bounds Checking:** |
| 74 | |
| 75 | ```solidity |
| 76 | // VULNERABLE: No upper bound on fee |
| 77 | function setFee(uint256 newFee) external onlyOwner { |
| 78 | fee = newFee; // Owner can set 100% fee, stealing all user funds |
| 79 | } |
| 80 | |
| 81 | // SAFE |
| 82 | function setFee(uint256 newFee) external onlyOwner { |
| 83 | require(newFee <= MAX_FEE, "Fee too high"); // e.g., MAX_FEE = 1000 (10%) |
| 84 | fee = newFee; |
| 85 | } |
| 86 | ``` |
| 87 | |
| 88 | ### Input Validation Detection Algorithm |
| 89 | |
| 90 | ``` |
| 91 | For each public/external function F: |
| 92 | For each parameter P: |
| 93 | 1. Is P an address? → Check for require(P != address(0)) |
| 94 | 2. Is P an amount/value? → Check for require(P > 0) if zero is invalid |
| 95 | 3. Is P an array? → Check for length validation and max size |
| 96 | 4. Is P a percentage/rate? → Check for upper bound |
| 97 | 5. Is P used as an index? → Check for bounds checking |
| 98 | 6. Is P a deadline/timestamp? → Check for require(P > block.timestamp) |
| 99 | |
| 100 | Flag any parameter without appropriate validation as: |
| 101 | - CRITICAL if parameter controls fund flow or access |
| 102 | - HIGH if parameter affects protocol state |
| 103 | - MEDIUM if parameter affects non-critical functionality |
| 104 | ``` |
| 105 | |
| 106 | ## Part 2: Arithmetic Vulnerability Analysis |
| 107 | |
| 108 | ### Pattern 1: Division-Before-Multiplication (Precision Loss) |
| 109 | |
| 110 | ```solidity |
| 111 | // VULNERABLE: Division first truncates, then multiplication amplifies error |
| 112 | uint256 result = (amount / totalShares) * price; |
| 113 | // If amount = 100, totalShares = 3: 100/3 = 33 (truncated from 33.33) |
| 114 | // 33 * price = less than expected |
| 115 | |
| 116 | // SAFE: Multiply first, then divide |
| 117 | uint256 result = (amount * price) / totalShares; |
| 118 | // 100 * price / 3 = more precise (only one truncation at the end) |
| 119 | ``` |
| 120 | |
| 121 | **Detection:** |
| 122 | |
| 123 | ``` |
| 124 | For each arithmetic expression: |
| 125 | If division (/) appears BEFORE multiplication (*) in the same expression: |
| 126 | → PRECISION LOSS: division-before-multiplication |
| 127 | Exception: If the division result is stored and intentionally used as a floored value |
| 128 | ``` |
| 129 | |
| 130 | ### Pattern 2: Rounding Direction Exploitation |
| 131 | |
| 132 | In financial protocols, rounding direction determines who benefits: |
| 133 | |
| 134 | ``` |
| 135 | Protocol-favorable rounding: |
| 136 | - Deposits: round DOWN shares (user gets fewer shares) |
| 137 | - Withdrawals: round DOWN ass |