Lack of input validation describes any situation where a smart contract processes external data—function parameters, calldata, cross-chain messages, or signed payloads—without rigorously enforcing that the data is well-formed, within expected bounds, and authorized for the intended operation. Contracts that assume inputs are benign leave themselves open to malformed or adversarial data that pushes the system into unsafe states, corrupts accounting, or bypasses intended checks.
This applies across all contract types: DeFi (fee bps, slippage, amounts, addresses), NFTs (token IDs, metadata, royalty config), DAOs (proposal payloads, voting parameters), bridges (message payloads, destination chains), and generic composable contracts that accept arbitrary calldata or relayed calls. On non-EVM chains, the same principle holds: untrusted inputs from users, other contracts, or cross-chain channels must be validated before use.
Few areas to focus on:
Attackers exploit:
In 2025, input validation issues often appeared as a contributing factor, e.g., failure to enforce safe ranges on parameters controlling liquidity or interest computations.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract VulnerableConfig {
uint256 public feeBps; // basis points 0–10_000
uint256 public maxDeposit; // upper bound for deposits
address public owner;
constructor() {
owner = msg.sender;
}
function setConfig(uint256 _feeBps, uint256 _maxDeposit) external {
// Missing: access control and bounds checks
feeBps = _feeBps;
maxDeposit = _maxDeposit;
}
}
Issues:
setConfig._feeBps or _maxDeposit:
feeBps could exceed 100%, breaking fee logic.maxDeposit could be set to an unsafe or zero value, disrupting the protocol.// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract SafeConfig {
uint256 public feeBps; // 0–1_000 (max 10% fee)
uint256 public maxDeposit; // upper bound for deposits
address public immutable owner;
error NotOwner();
error InvalidFee();
error InvalidMaxDeposit();
constructor(uint256 initialFeeBps, uint256 initialMaxDeposit) {
owner = msg.sender;
_setConfig(initialFeeBps, initialMaxDeposit);
}
function _setConfig(uint256 _feeBps, uint256 _maxDeposit) internal {
if (_feeBps > 1_000) revert InvalidFee();
if (_maxDeposit == 0) revert InvalidMaxDeposit();
feeBps = _feeBps;
maxDeposit = _maxDeposit;
}
function setConfig(uint256 _feeBps, uint256 _maxDeposit) external {
if (msg.sender != owner) revert NotOwner();
_setConfig(_feeBps, _maxDeposit);
}
}
Security Improvements:
maxDeposit to be non-zero, preventing misconfiguration.checked_shlw (see SC09). However, insufficient input validation was a contributing factor—the protocol allowed extreme liquidity parameters (e.g., ~2^113) without bounds checks. When combined with the flawed arithmetic, these unvalidated inputs produced dangerous edge cases leading to pool drains.