How to Prevent Input Validation Attacks in Solidity

Article author

Missing input validation attacks occur when a Solidity contract accepts user-controlled addresses, amounts, identifiers, prices, or configuration values without proving that they satisfy the contract’s economic and security assumptions. To prevent them, validate every external input at the trust boundary with allowlists, bounds checks, zero-address checks, authorization, and state invariants before assets move or accounting changes.

The impact is concrete: Qubit lost $80.0M on BSC on 2022-01-28 in an incident classified as Missing Input Validation. These failures are not merely malformed transaction issues. An unchecked value can make valid Solidity code execute an invalid business action, such as crediting nonexistent collateral, transferring assets to an untrusted destination, or creating debt beyond a borrower’s real limit.

What is an input validation vulnerability in Solidity, and how does an attack work?

An input validation vulnerability in Solidity exists when a contract treats a caller-provided value as trustworthy even though that value controls permissions, asset accounting, pricing, or state transitions. An attacker supplies a value that is syntactically valid for the ABI but economically invalid for the protocol, then uses the resulting state to withdraw assets, mint claims, or bypass restrictions.

Solidity enforces types, not protocol intent. A uint256 can be any non-reverting number, and an address can identify an EOA, a malicious contract, the zero address, or an unsupported token. The contract must enforce the missing semantic rules.

A typical attack sequence is:

  1. The attacker identifies a public function accepting an unchecked amount, token, recipient, price, or identifier.
  2. The attacker chooses a value that violates the protocol’s intended assumption.
  3. The contract records an unsafe balance, entitlement, debt limit, or authorization state.
  4. The attacker calls a later function that trusts the poisoned state.
  5. The protocol transfers assets or mints a transferable claim based on that false state.
  6. Other users bear the loss when the accounting shortfall becomes visible.

The critical review question is: what fact does this input claim to represent, and where does the contract independently verify that fact? If the answer is “the caller supplied it,” the contract has an improper input validation vulnerability.

Which Solidity patterns cause input validation attacks?

Input validation attacks usually arise where a contract accepts flexibility for integrations or user experience but does not constrain that flexibility to the protocol’s supported domain. The dangerous parameter is often not obviously named price or amount; it may be a token address, route, receiver, tranche ID, proof, or external contract address.

Common unsafe patterns include:

  • Caller-declared collateral value: A borrower provides the value used to calculate their borrowing power.
  • Arbitrary token addresses: A deposit function accepts any ERC-20, including tokens with non-standard transfer behavior.
  • Unchecked recipient addresses: A function accepts address(0), the contract itself, or an address that violates custody rules.
  • Unbounded percentages and ratios: A fee, LTV, discount, slippage, or reward rate exceeds its intended basis-point range.
  • Unvalidated array relationships: Parallel arrays differ in length, duplicate entries are accepted, or a route repeats an asset unexpectedly.
  • State transitions without preconditions: A claim, cancel, redeem, or migration action runs in the wrong lifecycle state.
  • External data accepted as fact: A function trusts caller-provided prices, balances, signatures, or return values without verification.

Validation must be contextual. require(amount > 0) is useful, but it does not prove that amount is affordable, collateralized, within a cap, or consistent with the current state. Likewise, checking that a token address is nonzero does not establish that the token is approved, behaves as expected, or belongs to the correct market.

Soken’s review methodology treats every external parameter as an attempted state transition: identify the invariant it can affect, then verify that the function enforces that invariant before writing storage.

What does an input validation attack example look like in Solidity?

This input validation attack example lets a borrower submit their own collateralValue. The contract checks that the requested loan is no greater than that value, but it never establishes that the borrower deposited collateral worth that amount. A caller can therefore set both values to an arbitrary level and borrow the lender’s funded assets.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

interface IERC20 {
    function transfer(address to, uint256 value) external returns (bool);
}

contract UnsafeLender {
    IERC20 public immutable asset;
    mapping(address => uint256) public debt;

    constructor(IERC20 asset_) {
        require(address(asset_) != address(0), "zero asset");
        asset = asset_;
    }

    function borrow(
        uint256 collateralValue,
        uint256 amount
    ) external {
        require(collateralValue > 0, "no collateral");
        require(amount <= collateralValue, "insufficient collateral");

        // VULNERABLE: borrower supplies the value backing the loan.
        debt[msg.sender] += amount;

        require(asset.transfer(msg.sender, amount), "transfer failed");
    }
}

The fixed design derives borrowing capacity from collateral the contract actually received. The immutable asset addresses act as a narrow allowlist, constructor checks reject zero addresses, the LTV is bounded, and the balance-delta check validates the transfer invariant rather than trusting the requested deposit amount.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

interface IERC20 {
    function balanceOf(address account) external view returns (uint256);
    function transfer(address to, uint256 value) external returns (bool);
    function transferFrom(address from, address to, uint256 value) external returns (bool);
}

contract ValidatedLender {
    IERC20 public immutable loanAsset;
    IERC20 public immutable collateralAsset;
    uint256 public immutable maxLtvBps;

    mapping(address => uint256) public collateral;
    mapping(address => uint256) public debt;

    constructor(IERC20 loanAsset_, IERC20 collateralAsset_, uint256 maxLtvBps_) {
        require(address(loanAsset_) != address(0), "zero loan asset");
        require(address(collateralAsset_) != address(0), "zero collateral asset");
        require(maxLtvBps_ > 0 && maxLtvBps_ <= 10_000, "invalid LTV");
        loanAsset = loanAsset_;
        collateralAsset = collateralAsset_;
        maxLtvBps = maxLtvBps_;
    }

    function depositCollateral(uint256 amount) external {
        require(amount > 0, "zero amount");
        uint256 beforeBalance = collateralAsset.balanceOf(address(this));
        require(collateralAsset.transferFrom(msg.sender, address(this), amount), "transfer failed");
        uint256 received = collateralAsset.balanceOf(address(this)) - beforeBalance;

        // FIX: only account for collateral actually received.
        require(received == amount, "unsupported token behavior");
        collateral[msg.sender] += received;
    }

    function borrow(uint256 amount) external {
        require(amount > 0, "zero amount");
        uint256 limit = (collateral[msg.sender] * maxLtvBps) / 10_000;
        require(debt[msg.sender] + amount <= limit, "borrow limit exceeded");

        debt[msg.sender] += amount;
        require(loanAsset.transfer(msg.sender, amount), "transfer failed");
    }
}

If your protocol accepts flexible assets, routes, or accounting inputs, an adversarial smart-contract audit and penetration-testing engagement can trace whether each claimed value is independently established before it affects funds.

How do you prevent input validation attacks in Solidity?

Preventing input validation attacks requires validating inputs at entry, then preserving the resulting assumptions with explicit invariants throughout the contract lifecycle. The best defence is not a single modifier. It is a design in which every meaningful state change has a clearly defined set of allowed inputs and a testable condition that remains true afterward.

Technique What it stops Gas or complexity cost Limitation
Token and implementation allowlists Unsupported ERC-20s, malicious adapters, arbitrary external calls Storage lookup or immutable-address comparison Approved tokens can still introduce economic risk
Bounds checks Zero amounts, excessive rates, invalid basis points, unsafe deadlines Low, usually a few comparisons Bounds alone do not prove economic correctness
Zero-address and recipient checks Asset burns, unusable ownership, invalid custody destinations Low, one address comparison A nonzero address can still be untrusted
Authorization and lifecycle checks Calls in the wrong state or by the wrong actor Storage read and role design Roles require secure administration
Balance-delta validation False deposit credits and unsupported token transfer behavior Additional token balance reads Requires careful handling of rebasing assets
Invariant validation Broken solvency, supply, debt, and reserve relationships Additional reads and arithmetic Invariants must cover the full economic model

For configurable protocols, validate configuration both when it is created and when it is consumed. A governance-set parameter can be safe at creation but unsafe after a dependency changes. For upgradeable systems, validate initializer parameters with the same rigor as public function inputs.

Security insight: Soken methodology starts from the invariant, not the require statement. If reviewers cannot state what must remain true after a user-controlled input is processed, they cannot determine whether the validation is sufficient.

Which real incidents were classified as missing input validation?

Missing Input Validation has been associated with material smart-contract losses across lending, swapping, and asset-management contexts. The incident category spans different chains and protocol designs, showing that improper input validation is a protocol-logic failure rather than a network-specific Solidity mistake.

Protocol Date Amount lost Chain
Qubit 2022-01-28 $80.0M BSC
Hedgey 2024-04-19 $44.7M Arbitrum, Ethereum
Transit Swap 2022-10-02 $28.9M Ethereum, BSC
Pickle 2020-11-22 $19.7M Ethereum

These incidents share a central lesson: contracts cannot safely infer protocol truth from a value merely because a transaction successfully encoded and passed that value. Asset systems need semantic validation around the specific property that protects value, whether that property is collateral received, a supported asset, an authorized route, or a valid lifecycle transition.

DefiLlama classifies 52 smart-contract incidents from 2020 through 2026 under Missing Input Validation, with combined losses of $330.5M. That breadth is why Soken treats validation review as an economic-security exercise, not as a checklist of superficial require statements.

How can you detect improper input validation vulnerabilities?

You can detect improper input validation vulnerabilities by tracing every externally reachable parameter to the storage writes, external calls, and asset transfers it influences, then testing whether invalid but ABI-valid values can violate an invariant. Static analysis finds some missing checks, but scenario-driven tests are necessary for protocol-specific assumptions.

Use this review checklist:

  1. List every external and public function parameter that influences value, authority, or routing.
  2. Identify whether each address is nonzero, approved, and appropriate for its role.
  3. Check every amount against protocol caps, balances, debt limits, supply limits, and decimals assumptions.
  4. Confirm that prices, collateral values, and balances come from trusted or independently verified sources.
  5. Validate array lengths, uniqueness, ordering, and cross-array index relationships.
  6. Ensure each function permits only valid lifecycle states and authorized callers.
  7. Test balance-delta accounting for fee-on-transfer, rebasing, and unusual ERC-20 behavior.
  8. State and test the post-condition for each asset-moving operation.

Slither can identify patterns such as dangerous external calls, unchecked return values, and weak access controls that often surround validation failures. It cannot infer every protocol invariant, so use Foundry fuzzing to generate zero values, maximum values, duplicate arrays, unexpected addresses, and unsupported tokens.

Invariant tests are particularly effective. For example, test that total outstanding debt never exceeds the configured borrowing capacity, that credited collateral never exceeds assets received, and that only approved assets can alter a market’s accounting. Explore the Soken Hub when building a broader security review process around those properties.

Input validation is the boundary between user intent and protocol truth. A contract should never credit, authorize, price, or transfer based solely on what a caller claims. Derive sensitive values from verified state, restrict accepted domains, and enforce the invariant that makes the resulting state economically safe.

The practical next step is to create a parameter-to-invariant map for every asset-moving function in your codebase. Any parameter without a documented trust source, permitted range, and post-condition deserves immediate review.

Article author

Frequently Asked Questions

What is an input validation vulnerability in Solidity?

A Solidity input validation vulnerability occurs when a contract accepts user-controlled addresses, amounts, identifiers, prices, or configuration values without proving they meet its economic and security assumptions. The contract can then execute a valid code path that produces an invalid business action, including unsafe asset transfers or incorrect accounting.

How can I prevent input validation attacks in Solidity?

To prevent Solidity input validation attacks, validate every external input at the trust boundary before assets move or accounting changes. Combine allowlists, bounds checks, zero-address checks, authorization checks, and state invariants so each user-controlled value conforms to the contract's intended security and economic rules.

What happened in the Qubit input validation attack?

Qubit lost $80.0M on BSC on 2022-01-28 in an incident classified as Missing Input Validation. The Qubit incident shows that unchecked inputs are more than malformed transaction problems: they can cause a contract to recognize nonexistent collateral or perform an invalid economic action.

Why should Solidity contracts check for the zero address?

Solidity contracts should reject a zero address when a user-supplied address must identify a real destination, asset, or authority. For Solidity input validation, a zero-address check prevents a contract from accepting a placeholder value that violates its intended transfer, authorization, or accounting assumptions.

Are bounds checks enough to prevent input validation vulnerabilities?

Bounds checks alone are not enough for Solidity input validation because a value can fall within a numeric range while still violating authorization, allowlist, address, or state requirements. Solidity contracts need layered validation at the trust boundary before assets move or accounting changes, matching each input to the relevant business invariant.

Chat