📋 What Is It?
Lack of Input Validation occurs when a contract trusts caller-supplied parameters - addresses, amounts, arrays, indexes, IDs, fees, deadlines, or raw calldata - and acts on them without checking they are within the values the logic expects. On a public chain anyone can call any function with any ABI-valid argument, so every unvalidated parameter is an open control. A single missing require can send tokens to address(0) (burning them), set a fee above 100%, brick the contract with an unusable owner, or redirect an external call to a malicious target - all permanent once mined.
⚠️ Common Exploits
- Zero-Address: Recipient/owner unchecked → burned funds or bricked contract
- Unchecked Amounts: No
> 0or balance/allowance bound - Mismatched Arrays: Unequal or uncapped parallel arrays in loops
- Unbounded Fees: Percentage set above 100% seizes all value
- Bad Index/ID: Out-of-range read, double-claim, no ownership check
- Arbitrary Target: Caller-supplied token/target → arbitrary external call
- Expiry/Signature Bypass: Stale deadline or unbound signer honoured
🔴 Attack Flow
↓
2. Craft calldata directly, bypassing any front-end
↓
3. Pass a malicious value (0x0, huge fee, fake token)
↓
4. Contract acts on it with no check
↓
5. IMPACT: Funds burned, contract bricked, or state corrupted!
❌ Vulnerable Code
✅ Secure Code
✓ Prevention Checklist
- Reject
address(0)for owners/recipients/tokens - Require amounts
> 0and within balance/allowance - Enforce equal array length and a
MAX_BATCHcap - Bounds-check indexes and IDs; verify ownership
- Give every fee/percentage an explicit upper bound
- Allow-list caller-supplied tokens and call targets
- Validate deadlines, signers, and nonces before acting
- Check
code.lengthwhen a contract is required - Wrap token calls with
SafeERC20 - Validate early, before any state change (CEI)
- Prefer custom errors over string reverts
- Write negative tests proving bad inputs revert
🔍 Detection & Tools
Analysis Tools:
Libraries:
How to Test:
- Fuzz every external function:
forge test --fuzz-runs 10000 - Negative tests:
transfer(address(0), x)must revert - Assert
setFeeBps(20_000)reverts with the expected error - Feed mismatched/oversized airdrop arrays and confirm revert
- Run
slither .to flag missing zero-address/bounds checks
🌍 Real-World
- Zero-address burns (recurring class): Transfer, mint, or ownership functions accepted a recipient without checking
address(0); funds sent there are irretrievable, and setting ownership to zero made privileged functions permanently uncallable. OpenZeppelin's ERC-20/721 base contracts add this check for exactly this reason. - Arbitrary token / target abuse: A function accepted a caller-supplied token or call target with no allow-list; attackers supplied a contract they controlled to force arbitrary external calls, approvals, or fake return values.
- Unbounded / mismatched array loops: Airdrops and multi-sends looped over caller arrays without equal-length or size checks; mismatched lengths paid wrong amounts, and oversized arrays exceeded the block gas limit - a denial of service.
- Common thread: Each traces back to a caller-supplied value acted on without a check - often mislabeled later as access-control or re-entrancy, but a missing
requireopened the door.
📌 Quick Tips
- DO NOT trust the front-end to validate
- DO NOT store/send to an unchecked address
- DO NOT act on a caller-supplied token/target blindly
- DO validate at the top of every function
- DO bound amounts, fees, and array lengths
- DO allow-list critical addresses
- DO fuzz and write negative tests