# input-arithmetic-safety

Detects 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, dust amount exploitation, and Solidity 0.8+ unchecked block edge cases. Use when auditing contracts with fee calculations, share pricing, exchange rates, unchecked blocks, or any public-facing functions that accept user input.

- **Kind:** skill
- **Source:** https://github.com/quillai-network/qs_skills
- **Page:** https://forefy.com/skills/0516ee9e-5a18-4523-80f2-8ed540380fa7
- **API (JSON + files):** https://forefy.com/api/asr/0516ee9e-5a18-4523-80f2-8ed540380fa7

---

## SKILL.md

---
name: input-arithmetic-safety
description: Detects 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, dust amount exploitation, and Solidity 0.8+ unchecked block edge cases. Use when auditing contracts with fee calculations, share pricing, exchange rates, unchecked blocks, or any public-facing functions that accept user input.
---

# Input & Arithmetic Safety

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.

## When to Use

- Auditing any contract with public/external functions accepting user-supplied parameters
- Reviewing DeFi protocols with fee calculations, share pricing, or exchange rates
- Analyzing vault/staking contracts for rounding or first-depositor attacks
- Checking contracts with `unchecked` blocks for overflow/underflow risks
- Verifying arithmetic in token minting, burning, and distribution logic

## When NOT to Use

- Access control analysis (use semantic-guard-analysis)
- Reentrancy detection (use reentrancy-pattern-analysis)
- Full multi-dimensional audit (use behavioral-state-analysis)

## Part 1: Input Validation Analysis

### Critical Missing Validations

**Zero Address Check:**

```solidity
// VULNERABLE: No zero address check
function setAdmin(address newAdmin) external onlyOwner {
    admin = newAdmin; // Can set admin to address(0) — locking out admin forever
}

// SAFE
function setAdmin(address newAdmin) external onlyOwner {
    require(newAdmin != address(0), "Zero address");
    admin = newAdmin;
}
```

**Zero Amount Check:**

```solidity
// VULNERABLE: Allows zero-amount operations
function deposit(uint256 amount) external {
    balances[msg.sender] += amount;
    emit Deposit(msg.sender, amount);
    // Zero deposit: wastes gas, pollutes events, may affect accounting
}

// SAFE
function deposit(uint256 amount) external {
    require(amount > 0, "Zero amount");
    balances[msg.sender] += amount;
}
```

**Array Length Validation:**

```solidity
// VULNERABLE: No length check
function batchTransfer(address[] calldata recipients, uint256[] calldata amounts) external {
    for (uint i = 0; i < recipients.length; i++) {
        transfer(recipients[i], amounts[i]); // Out-of-bounds if arrays differ in length
    }
}

// SAFE
function batchTransfer(address[] calldata recipients, uint256[] calldata amounts) external {
    require(recipients.length == amounts.length, "Length mismatch");
    require(recipients.length <= MAX_BATCH_SIZE, "Batch too large");
    // ...
}
```

**Bounds Checking:**

```solidity
// VULNERABLE: No upper bound on fee
function setFee(uint256 newFee) external onlyOwner {
    fee = newFee; // Owner can set 100% fee, stealing all user funds
}

// SAFE
function setFee(uint256 newFee) external onlyOwner {
    require(newFee <= MAX_FEE, "Fee too high"); // e.g., MAX_FEE = 1000 (10%)
    fee = newFee;
}
```

### Input Validation Detection Algorithm

```
For each public/external function F:
  For each parameter P:
    1. Is P an address? → Check for require(P != address(0))
    2. Is P an amount/value? → Check for require(P > 0) if zero is invalid
    3. Is P an array? → Check for length validation and max size
    4. Is P a percentage/rate? → Check for upper bound
    5. Is P used as an index? → Check for bounds checking
    6. Is P a deadline/timestamp? → Check for require(P > block.timestamp)

  Flag any parameter without appropriate validation as:
    - CRITICAL if parameter controls fund flow or access
    - HIGH if parameter affects protocol state
    - MEDIUM if parameter affects non-critical functionality
```

## Part 2: Arithmetic Vulnerability Analysis

### Pattern 1: Division-Before-Multiplication (Precision Loss)

```solidity
// VULNERABLE: Division first truncates, then multiplication amplifies error
uint256 result = (amount / totalShares) * price;
// If amount = 100, totalShares = 3: 100/3 = 33 (truncated from 33.33)
// 33 * price = less than expected

// SAFE: Multiply first, then divide
uint256 result = (amount * price) / totalShares;
// 100 * price / 3 = more precise (only one truncation at the end)
```

**Detection:**

```
For each arithmetic expression:
  If division (/) appears BEFORE multiplication (*) in the same expression:
    → PRECISION LOSS: division-before-multiplication
  Exception: If the division result is stored and intentionally used as a floored value
```

### Pattern 2: Rounding Direction Exploitation

In financial protocols, rounding direction determines who benefits:

```
Protocol-favorable rounding:
  - Deposits: round DOWN shares (user gets fewer shares)
  - Withdrawals: round DOWN assets (user gets fewer assets)
  - Fees: round UP fee amount (protocol collects more)

User-favorable rounding (VULNERABLE to extraction):
  - Deposits: round UP shares → user gets more than entitled
  - Withdrawals: round UP assets → user extracts more than entitled
  - Fees: round DOWN → protocol collects less
```

```solidity
// VULNERABLE: Rounds in user's favor on withdrawal
function withdraw(uint256 shares) external returns (uint256 assets) {
    assets = (shares * totalAssets()) / totalSupply(); // Rounds DOWN — correct for withdrawal
    // BUT if this rounds UP somehow (e.g., via ceiling division):
    assets = (shares * totalAssets() + totalSupply() - 1) / totalSupply(); // Rounds UP — BAD
}

// SAFE: Use mulDiv with explicit rounding direction
assets = shares.mulDiv(totalAssets(), totalSupply(), Math.Rounding.Down); // For withdrawals
shares = assets.mulDiv(totalSupply(), totalAssets(), Math.Rounding.Up);   // For deposits
```

### Pattern 3: ERC4626 Vault Share Inflation Attack

```solidity
// Attack on first deposit
contract VulnerableVault is ERC4626 {
    function totalAssets() public view returns (uint256) {
        return asset.balanceOf(address(this)); // Manipulable via donation!
    }

    // No virtual shares offset
    function _convertToShares(uint256 assets) internal view returns (uint256) {
        uint256 supply = totalSupply();
        return supply == 0 ? assets : assets.mulDiv(supply, totalAssets());
    }
}
```

**Attack Sequence:**

```
1. Vault is empty (totalSupply = 0, totalAssets = 0)
2. Attacker deposits 1 wei → receives 1 share
3. Attacker donates 1000 tokens directly to vault (not via deposit)
4. totalAssets = 1000e18 + 1, totalSupply = 1
5. Victim deposits 500 tokens:
   shares = 500e18 * 1 / (1000e18 + 1) = 0 (rounds to zero!)
6. Victim gets ZERO shares, their 500 tokens are trapped
7. Attacker withdraws 1 share → gets all 1500+ tokens
```

**Detection:**

```
For ERC4626 vaults:
  1. Does totalAssets() use balanceOf(address(this))? → Donation-attackable
  2. Is there a virtual shares/assets offset? → Missing = VULNERABLE
  3. Is there a minimum first deposit? → Missing = VULNERABLE
  4. Does the vault use OpenZeppelin's _decimalsOffset()? → Present = Mitigated
```

### Pattern 4: Unsafe Integer Casting

```solidity
// VULNERABLE: Silent truncation
uint256 largeValue = 2**200;
uint128 smallValue = uint128(largeValue); // Truncated! No revert in 0.8+

// VULNERABLE: Signed/unsigned confusion
int256 negative = -1;
uint256 converted = uint256(negative); // = type(uint256).max in 0.8+

// SAFE: Use SafeCast
uint128 smallValue = SafeCast.toUint128(largeValue); // Reverts if overflow
```

**Detection:**

```
For each type cast operation:
  If casting from larger to smaller type (e.g., uint256 → uint128):
    Check if preceded by bounds validation
    If no bounds check → UNSAFE CASTING
  If casting between signed and unsigned:
    Check if value can be negative
    If possible → SIGN CONFUSION
```

### Pattern 5: Unchecked Block Risks

```solidity
// Solidity 0.8+: checked math by default, but unchecked{} disables it
unchecked {
    // VULNERABLE: Overflow/underflow silently wraps
    uint256 result = a - b; // If b > a: wraps to huge number
    uint256 sum = a + b;    // If a + b > type(uint256).max: wraps to small number
}

// SAFE use of unchecked (when overflow is impossible):
unchecked {
    ++i; // In a bounded for loop — i cannot overflow uint256
}
```

**Detection:**

```
For each unchecked block:
  For each arithmetic operation inside:
    1. Can the operation overflow/underflow?
    2. Is there a pre-condition that guarantees safety?
    3. If no guarantee → UNCHECKED OVERFLOW/UNDERFLOW risk

  Common safe patterns (don't flag):
    - Loop counter increment: unchecked { ++i; } in for loop with bounded length
    - Post-require subtraction: require(a >= b); unchecked { a - b; }
```

### Pattern 6: Dust Amount Exploitation

```solidity
// VULNERABLE: Tiny amounts bypass fee logic
function swap(uint256 amountIn) external {
    uint256 fee = amountIn * FEE_BPS / 10000;
    // If amountIn = 1 and FEE_BPS = 30: fee = 30/10000 = 0
    // Zero fee! Attacker makes many tiny swaps to avoid fees
    uint256 amountOut = amountIn - fee;
}
```

**Detection:**

```
For each fee/tax calculation:
  If fee = amount * rate / denominator:
    Can amount * rate < denominator? (making fee = 0)
    If yes → DUST AMOUNT EXPLOITATION: zero-fee transactions possible
```

## Workflow

```
Task Progress:
- [ ] Step 1: Audit all public/external function parameters for missing validation
- [ ] Step 2: Find division-before-multiplication patterns
- [ ] Step 3: Verify rounding direction in share/price calculations (protocol-favorable)
- [ ] Step 4: Check ERC4626 vaults for inflation attack protection
- [ ] Step 5: Identify all type casting operations and verify bounds
- [ ] Step 6: Analyze all unchecked blocks for overflow/underflow risks
- [ ] Step 7: Check fee calculations for dust amount exploitation
- [ ] Step 8: Score findings and generate report
```

## Output Format

```markdown
## Input & Arithmetic Safety Report

### Finding: [Title]

**Function:** `functionName()` at `Contract.sol:L42`
**Category:** [Missing Validation | Precision Loss | Rounding | Inflation | Unsafe Cast | Unchecked | Dust]
**Severity:** [CRITICAL | HIGH | MEDIUM | LOW]

**Issue:**
[Description of the input validation or arithmetic vulnerability]

**Vulnerable Code:**
[Code snippet showing the issue]

**Exploit Scenario:**
1. [Step-by-step exploitation]

**Mathematical Proof:**
  Input: [values]
  Expected: [correct result]
  Actual: [incorrect result due to precision/rounding]
  Difference: [loss amount]

**Recommendation:**
[Specific fix — add validation, reorder operations, use SafeCast, add rounding]
```

## Quick Detection Checklist

- [ ] Do all public functions validate address parameters against `address(0)`?
- [ ] Do all amount parameters check for `> 0` where zero is invalid?
- [ ] Are array parameters checked for equal lengths and maximum size?
- [ ] Do all percentage/rate parameters have upper bounds?
- [ ] Is division always performed AFTER multiplication (not before)?
- [ ] Does rounding favor the protocol (down on deposits, down on withdrawals of assets)?
- [ ] Do ERC4626 vaults use virtual shares/assets offset against inflation?
- [ ] Are all downcasts (uint256 → smaller) protected by SafeCast or bounds checks?
- [ ] Are `unchecked` blocks only used where overflow/underflow is mathematically impossible?
- [ ] Can fee calculations produce zero for small but valid amounts?

For precision patterns, see [{baseDir}/references/precision-patterns.md]({baseDir}/references/precision-patterns.md).
For validation checklist, see [{baseDir}/references/validation-checklist.md]({baseDir}/references/validation-checklist.md).

## Rationalizations to Reject

- "Solidity 0.8+ has checked math" → `unchecked` blocks exist; precision loss and rounding are NOT overflow
- "The fee is too small to matter" → Millions of small transactions compound; zero-fee dust swaps are profitable
- "No one would deposit 1 wei" → ERC4626 inflation attack uses exactly this; front-runners are automated
- "The admin wouldn't set a bad value" → Admin key compromise + no bounds = instant parameter manipulation
- "Rounding errors are just 1 wei" → 1 wei per transaction × millions of transactions = significant loss
- "Zero address can't sign transactions" → But setting admin to zero address locks out all admin functions permanently

## references

```

```

## references/precision-patterns.md

# Precision Loss Patterns — Detailed Reference

## Pattern 1: Division-Before-Multiplication

### The Problem

Integer division in Solidity truncates (rounds toward zero). When division occurs before multiplication, the truncation error is amplified.

### Mathematical Proof

```
Let a = 1000, b = 3, c = 7

Division first:  (a / b) * c = (1000 / 3) * 7 = 333 * 7 = 2331
Multiply first:  (a * c) / b = (1000 * 7) / 3 = 7000 / 3 = 2333

Correct answer: 1000 * 7 / 3 = 2333.33...

Error (div first):  2333.33 - 2331 = 2.33 (0.1% loss)
Error (mul first):  2333.33 - 2333 = 0.33 (0.01% loss)
```

**The error from division-first is ~7x larger in this example.**

### Real-World Impact

```solidity
// Reward distribution in staking protocol
// 1M tokens distributed to 7 stakers

// VULNERABLE
uint256 rewardPerUser = totalRewards / numStakers;  // 1000000 / 7 = 142857
uint256 totalDistributed = rewardPerUser * numStakers; // 142857 * 7 = 999999
// LOST: 1 token per distribution round
// Over 365 days: 365 tokens lost

// SAFE
uint256 totalDistributed = totalRewards;
uint256 rewardPerUser = totalRewards / numStakers;
uint256 dust = totalRewards - (rewardPerUser * numStakers);
// Allocate dust to last user or accumulate for next round
```

### Detection Rule

```
For each expression containing both / and *:
  Parse operation order (respecting parentheses)
  If / appears before * in evaluation order:
    → PRECISION LOSS: division-before-multiplication
  Severity based on:
    - Financial context (fee, reward, price) → HIGH
    - Frequency of execution (per-block vs one-time) → Multiplier
    - Magnitude of typical values → Estimate actual loss
```

---

## Pattern 2: Phantom Overflow (Multiply-First Risk)

### The Problem

Multiplying first avoids precision loss but can cause overflow if intermediate values exceed `uint256.max`.

```solidity
// OVERFLOW RISK with multiply-first
uint256 result = (a * b) / c;
// If a = 2^200 and b = 2^100: a * b = 2^300 > 2^256 → OVERFLOW

// SAFE: Use mulDiv
uint256 result = FullMath.mulDiv(a, b, c); // Handles 512-bit intermediate
```

### When to Use mulDiv

```
If either operand could be > 2^128 (including prices with 18 decimals × amounts with 18 decimals):
  Use OpenZeppelin's Math.mulDiv() or Uniswap's FullMath.mulDiv()
  These compute (a × b) / c with 512-bit intermediate precision
```

---

## Pattern 3: Accumulated Rounding Error

### The Problem

Small rounding errors per operation compound over many operations.

```solidity
// Fee calculation per swap
uint256 fee = amount * FEE_RATE / FEE_DENOMINATOR;

// Example: amount = 100, FEE_RATE = 3, FEE_DENOMINATOR = 1000
// fee = 300 / 1000 = 0 (rounds to zero!)

// Over 1 million swaps of 100 tokens each:
// Expected fees: 1M * 100 * 0.3% = 300,000 tokens
// Actual fees: 0 (all rounded to zero)
```

### Mitigation

```solidity
// Option 1: Minimum fee
uint256 fee = amount * FEE_RATE / FEE_DENOMINATOR;
if (fee == 0 && amount > 0) fee = 1; // Minimum 1 wei fee

// Option 2: Accumulate fractional fees
uint256 accumulatedFee += amount * FEE_RATE; // Don't divide yet
if (accumulatedFee >= FEE_DENOMINATOR) {
    uint256 fee = accumulatedFee / FEE_DENOMINATOR;
    accumulatedFee %= FEE_DENOMINATOR;
}

// Option 3: Minimum transaction size
require(amount >= MIN_AMOUNT, "Below minimum");
```

---

## Pattern 4: Price/Share Calculation Precision

### Share Price Calculation

```solidity
// Standard share price: assets per share
uint256 sharePrice = totalAssets / totalShares;

// Problem: If totalAssets = 999 and totalShares = 1000
// sharePrice = 0 (shares appear worthless)

// SAFE: Use higher precision
uint256 sharePrice = totalAssets * PRECISION / totalShares;
// Where PRECISION = 1e18 or 1e27
```

### Deposit/Withdraw Precision

```solidity
// Deposit: How many shares for X assets?
uint256 shares = assets * totalShares / totalAssets;
// Rounds DOWN — user gets fewer shares (protocol-favorable ✓)

// Withdraw: How many assets for X shares?
uint256 assets = shares * totalAssets / totalShares;
// Rounds DOWN — user gets fewer assets (protocol-favorable ✓)

// DANGEROUS: Ceiling division on withdrawal
uint256 assets = (shares * totalAssets + totalShares - 1) / totalShares;
// Rounds UP — user gets more assets (user-favorable ✗)
// Repeated withdraw/deposit cycles drain the vault
```

---

## Pattern 5: Cross-Token Decimal Mismatch

### The Problem

Different tokens have different decimal places (USDC: 6, ETH: 18, WBTC: 8). Calculations mixing these without normalization produce wrong results.

```solidity
// VULNERABLE: Assumes both tokens have 18 decimals
uint256 value = tokenAmount * price / 1e18;

// If tokenAmount is USDC (6 decimals) and price is in 18 decimals:
// 1000000 (1 USDC) * 2000e18 / 1e18 = 2000000000 (way too much!)

// SAFE: Normalize by actual decimals
uint256 value = tokenAmount * price / (10 ** tokenDecimals);
```

### Detection

```
For each arithmetic operation involving token amounts:
  1. Identify the token(s) involved
  2. Check if decimal normalization is applied
  3. Verify the normalization factor matches the token's actual decimals
  4. Flag hardcoded 1e18 assumptions when token decimals could differ
```

---

## Pattern 6: Solidity-Specific Edge Cases

### Modulo Returns Zero for Power-of-Two Denominators

```solidity
// Note: This is correct behavior but can be surprising
uint256 result = 256 % 256; // = 0, not 256
```

### Negative Division Rounds Toward Zero

```solidity
int256 result = -7 / 2; // = -3 (not -4)
// This is "truncation toward zero", not "floor division"
```

### Type Coercion in Mixed Operations

```solidity
uint8 a = 255;
uint8 b = 1;
uint256 c = a + b; // In 0.8+: REVERTS (overflow in uint8 before casting)

// SAFE
uint256 c = uint256(a) + uint256(b); // Cast BEFORE arithmetic
```

---

## Precision Loss Severity Matrix

| Context | Loss per Operation | Frequency | Severity |
|---------|-------------------|-----------|----------|
| Share price calculation | ~1 wei | Per deposit/withdraw | HIGH (compounds) |
| Fee calculation | Up to full fee | Per transaction | CRITICAL if fee = 0 |
| Reward distribution | ~1 token | Per epoch | MEDIUM (dust) |
| Interest calculation | Variable | Per block/second | HIGH (compounds rapidly) |
| Exchange rate | ~1 wei | Per swap | MEDIUM |
| Voting power | ~1 wei | Per delegation | LOW |

## references/validation-checklist.md

# Input Validation Checklist — By Parameter Type

## Address Parameters

| Check | Severity | Code Pattern |
|-------|----------|-------------|
| Non-zero address | HIGH | `require(addr != address(0), "Zero address")` |
| Not self-address | MEDIUM | `require(addr != address(this), "Self reference")` |
| Not msg.sender (for recipients) | LOW | `require(addr != msg.sender, "Self transfer")` |
| Contract vs EOA check | MEDIUM | `require(addr.code.length > 0, "Not a contract")` |
| Whitelisted address | HIGH | `require(whitelist[addr], "Not whitelisted")` |

### Critical Address Parameters

```
- Admin/owner setters: MUST check != address(0)
- Token addresses: MUST check != address(0), ideally verify it's a contract
- Fee recipients: MUST check != address(0) (fees sent to zero = burned)
- Oracle addresses: MUST check != address(0) and verify interface
- Proxy implementation: MUST check != address(0) and is a contract
```

---

## Amount / Value Parameters

| Check | Severity | Code Pattern |
|-------|----------|-------------|
| Non-zero | MEDIUM | `require(amount > 0, "Zero amount")` |
| Upper bound | HIGH | `require(amount <= maxAmount, "Exceeds maximum")` |
| Sufficient balance | HIGH | `require(balances[user] >= amount, "Insufficient")` |
| Minimum threshold | MEDIUM | `require(amount >= minAmount, "Below minimum")` |
| Fits in target type | HIGH | `require(amount <= type(uint128).max, "Overflow")` |

### Critical Amount Parameters

```
- Deposit/withdraw amounts: Non-zero + sufficient balance
- Fee rates: Upper bound (e.g., max 10%)
- Interest rates: Upper bound + reasonable range
- Slippage tolerance: Upper bound (e.g., max 50%)
- Loan amounts: Against collateral ratio
- Mint amounts: Against supply cap
```

---

## Array Parameters

| Check | Severity | Code Pattern |
|-------|----------|-------------|
| Non-empty | MEDIUM | `require(arr.length > 0, "Empty array")` |
| Maximum length | HIGH | `require(arr.length <= MAX_LEN, "Too many")` |
| Matching lengths | CRITICAL | `require(a.length == b.length, "Mismatch")` |
| No duplicates | MEDIUM | Application-specific dedup logic |
| Valid elements | HIGH | Loop validation of each element |

### Critical Array Parameters

```
- Batch transfer recipients + amounts: MUST match lengths
- Merkle proof: Max reasonable length
- Signature arrays (multisig): Max signers, no duplicates
- Token lists: Max length to prevent gas DoS
```

---

## Percentage / Rate Parameters

| Check | Severity | Code Pattern |
|-------|----------|-------------|
| Non-negative | HIGH | Inherent for uint; check for int types |
| Maximum value | CRITICAL | `require(rate <= MAX_RATE, "Rate too high")` |
| Minimum value | MEDIUM | `require(rate >= MIN_RATE, "Rate too low")` |
| Basis points range | HIGH | `require(bps <= 10000, "Invalid BPS")` |
| Sum of parts | HIGH | `require(feeA + feeB + feeC <= TOTAL, "Sum exceeds 100%")` |

### Common Ranges

```
- Fee BPS: 0-10000 (0-100%), typically max 1000 (10%)
- Interest rate: 0-10000 BPS, with per-block/per-second conversion
- Collateral ratio: > 10000 BPS (> 100%), typically 15000 (150%)
- Slippage: 0-5000 BPS (0-50%), typically max 500 (5%)
- Liquidation bonus: 0-5000 BPS, typically 500-1500 (5-15%)
```

---

## Timestamp / Deadline Parameters

| Check | Severity | Code Pattern |
|-------|----------|-------------|
| Not expired | HIGH | `require(deadline >= block.timestamp, "Expired")` |
| Not too far future | MEDIUM | `require(deadline <= block.timestamp + MAX_DELAY)` |
| Reasonable range | MEDIUM | Application-specific bounds |
| Not in the past | HIGH | `require(startTime > block.timestamp, "Past")` |
| Start before end | HIGH | `require(startTime < endTime, "Invalid range")` |

---

## String / Bytes Parameters

| Check | Severity | Code Pattern |
|-------|----------|-------------|
| Non-empty | LOW | `require(bytes(str).length > 0, "Empty")` |
| Maximum length | MEDIUM | `require(bytes(str).length <= MAX_LEN)` |
| Valid encoding | LOW | Application-specific |
| Bytes length | MEDIUM | `require(data.length >= MIN_LEN, "Too short")` |
| Selector check | HIGH | `require(bytes4(data) == expectedSelector)` |

---

## Enum / State Parameters

| Check | Severity | Code Pattern |
|-------|----------|-------------|
| Valid enum value | HIGH | Solidity auto-checks in 0.8+ |
| Valid state transition | HIGH | `require(validTransition[current][next])` |
| Not current state | LOW | `require(newState != currentState, "Same")` |

---

## Function-Level Validation Patterns

### Constructor / Initialize

```solidity
constructor(
    address _admin,          // MUST: != address(0)
    address _token,          // MUST: != address(0), is contract
    uint256 _maxSupply,      // MUST: > 0
    uint256 _feeRate         // MUST: <= MAX_FEE
) {
    require(_admin != address(0), "Zero admin");
    require(_token != address(0) && _token.code.length > 0, "Invalid token");
    require(_maxSupply > 0, "Zero supply");
    require(_feeRate <= MAX_FEE_BPS, "Fee too high");
}
```

### Setter Functions

```solidity
function setConfig(
    address _oracle,         // != address(0), is contract
    uint256 _collateralRatio,// >= MIN_RATIO (e.g., 110%)
    uint256 _liquidationBonus// <= MAX_BONUS (e.g., 15%)
) external onlyAdmin {
    require(_oracle != address(0), "Zero oracle");
    require(_collateralRatio >= MIN_COLLATERAL_RATIO, "Ratio too low");
    require(_liquidationBonus <= MAX_LIQUIDATION_BONUS, "Bonus too high");
}
```

### Financial Functions

```solidity
function deposit(uint256 amount, address receiver) external {
    require(amount > 0, "Zero deposit");
    require(receiver != address(0), "Zero receiver");
    require(amount >= minDeposit, "Below minimum");
    require(totalDeposits + amount <= depositCap, "Cap exceeded");
}
```

---

## Validation Priority Matrix

| Parameter Context | Critical Checks | Priority |
|-------------------|----------------|----------|
| Fund transfer recipient | Non-zero address | P0 (MUST) |
| Admin/owner setter | Non-zero address | P0 (MUST) |
| Fee/rate configuration | Upper bound | P0 (MUST) |
| Batch operation arrays | Length match + max size | P0 (MUST) |
| Deposit/withdraw amount | Non-zero + balance | P0 (MUST) |
| Deadline/expiry | Not expired | P1 (SHOULD) |
| Oracle/external address | Non-zero + is contract | P1 (SHOULD) |
| Minimum thresholds | Minimum amount | P2 (NICE) |
| String/metadata | Max length | P2 (NICE) |

