Opcode Summary

PropertyValue
Opcode0x04
MnemonicDIV
Gas5
Stack Inputa, b
Stack Outputa // b (unsigned integer division, floor)
BehaviorUnsigned 256-bit integer division. Result truncates toward zero (floor). Division by zero returns 0 — it does NOT revert.

Threat Surface

DIV is the EVM’s sole unsigned integer division opcode, and its threat surface is fundamentally different from ADD/MUL overflow. Two properties make it dangerous:

  1. Division by zero returns 0 silently. Unlike virtually every other computing environment, the EVM does not trap, revert, or signal on division by zero. a / 0 == 0 for all a. Solidity >= 0.8.0 inserts a pre-check that reverts before the DIV opcode executes, but unchecked blocks, inline assembly, and raw bytecode bypass this protection entirely. Any code path that reaches DIV with a zero denominator produces a silent wrong answer.

  2. Truncation toward zero loses precision, and precision loss is directional. 1 / 2 == 0, 999 / 1000 == 0, and (a * b) / c != a * (b / c) in integer arithmetic. Every truncation rounds down, systematically favoring one side of a transaction. In DeFi, this directional bias is the root cause of rounding exploits worth hundreds of millions of dollars — attackers structure transactions so that the protocol consistently rounds in the attacker’s favor, extracting value one dust amount at a time or in a single catastrophic manipulation.

These two properties combine to make DIV the most economically exploited arithmetic opcode in DeFi history. While ADD overflow was the dominant vulnerability class from 2016-2020, division rounding and precision loss have become the dominant arithmetic exploit vector in the post-Solidity-0.8.0 era.


Smart Contract Threats

T1: Division by Zero — Silent Failure (Critical)

When b == 0, the EVM’s DIV returns 0 instead of reverting. This means any computation that divides by a potentially-zero value produces a silently incorrect result. Common dangerous patterns:

  • Price oracles: price = totalValue / totalSupply returns 0 when totalSupply == 0, making assets appear free
  • Exchange rates: rate = reserveA / reserveB collapses to 0 if a pool is drained, bypassing solvency checks
  • Fee calculations: feePerUnit = totalFees / participantCount returns 0 when there are no participants, losing all fees
  • Ratio computations: Any formula where the denominator depends on external state (balances, supply, oracle responses)

Solidity 0.8.0+ inserts ISZERO + JUMPI before every DIV to revert on zero denominator. However:

  • unchecked { } blocks suppress this check
  • Inline assembly (assembly { result := div(a, b) }) uses raw DIV with no check
  • Vyper, Huff, and other languages have their own policies
  • Pre-compiled or hand-optimized bytecode may omit the check for gas savings

T2: Truncation / Rounding Exploitation in Token Vaults (Critical)

The most economically devastating DIV threat in modern DeFi. When vaults calculate shares-per-deposit or assets-per-share, integer division always truncates (rounds down). An attacker who can inflate the exchange rate can force victims’ deposits to round down to zero shares:

// ERC4626-style vault
function deposit(uint256 assets) external returns (uint256 shares) {
    shares = assets * totalSupply / totalAssets;  // DIV truncates
    // If totalAssets is manipulated to be very large relative to totalSupply,
    // 'shares' rounds to 0 -- victim deposits real assets and receives nothing
}

Attack pattern (vault inflation / donation attack):

  1. Attacker deposits 1 wei, receives 1 share
  2. Attacker donates a large amount of underlying assets directly to the vault (not via deposit)
  3. Exchange rate is now (1 + donated) / 1 — astronomically high
  4. Victim deposits, but victimAssets * 1 / (1 + donated) truncates to 0 shares
  5. Attacker redeems their 1 share for the victim’s deposited assets plus the original donation

This attack class affects Compound V2, ERC4626 vaults, Yearn vaults, and every share-based accounting system.

T3: Operation Ordering — Divide-First vs. Multiply-First (High)

The order of multiplication and division in integer arithmetic fundamentally changes results:

// WRONG: Divide first, lose precision
uint256 bad = (a / PRECISION) * b;  // Truncation before multiplication
 
// CORRECT: Multiply first, preserve precision
uint256 good = (a * b) / PRECISION;  // Full precision until final truncation
// BUT: a * b might overflow if both are large

This is not an academic distinction. In AMM constant-product calculations, lending protocol interest accrual, and liquidity math, divide-first patterns systematically leak value. The leaked value accumulates and can be extracted by an attacker who structures many small transactions.

Uniswap V3 addresses this with FullMath.mulDiv(), which computes a * b / c using 512-bit intermediate precision, avoiding both overflow and premature truncation.

T4: Price Manipulation via Precision Loss in AMMs (High)

AMM pricing formulas like outputAmount = (reserveOut * amountIn) / (reserveIn + amountIn) lose precision at boundary conditions. When reserves are manipulated to extreme ratios (via flash loans), the truncation in DIV can be amplified:

  • Small trades may return 0 output tokens due to truncation, effectively donating to the pool
  • Price calculations that feed into oracle reads can be biased by rounding direction
  • Batch operations compound rounding errors across many sub-operations

In Balancer V2’s ComposableStablePool, token scaling operations used _upscale() which always rounded down. When invariant (D) calculations accumulated these rounding errors across multiple tokens, the BPT price was artificially deflated, enabling attackers to buy pool tokens cheap and arbitrage the difference.

T5: Dust Amount Extraction via Repeated Rounding (Medium)

When a protocol rounds in the user’s favor on withdrawal (or against the protocol on any operation), an attacker can extract dust by repeating the operation:

// Vulnerable: rounds up on withdrawal
function withdraw(uint256 shares) external returns (uint256 assets) {
    assets = (shares * totalAssets + totalSupply - 1) / totalSupply;  // Rounds up
    // Attacker withdraws 1 share at a time, getting ceil() each time
    // vs. withdrawing all at once and getting a single ceil()
}

Each individual withdrawal extracts up to 1 wei more than proportional. Over thousands of transactions (feasible on L2s with low gas), the extracted dust becomes significant. The attack is bounded by gas costs on L1 but essentially free on L2 rollups.

T6: Denominator Manipulation for Solvency Bypass (High)

When division-by-zero returns 0 and the result feeds into a solvency or collateral check, the check can be completely bypassed:

// Vulnerable solvency check
function isSolvent(address user) internal view returns (bool) {
    uint256 ltv = borrowedAmount * 1e36 / collateralValue;
    return ltv <= maxLTV;  // If collateralValue manipulation makes ltv = 0, always passes
}

If an attacker can manipulate collateralValue to cause the division to produce 0 (either via actual zero or via truncation of a very large denominator), the user appears infinitely solvent and can borrow without adequate collateral.


Protocol-Level Threats

P1: No DoS Vector (Low)

DIV costs a fixed 5 gas with no dynamic component. It operates purely on the stack with no memory, storage, or external access. It cannot be used for gas griefing.

P2: Consensus Safety (Low)

DIV is deterministic: a / b (unsigned floor division) and a / 0 == 0 are unambiguous. All EVM client implementations agree on these semantics. No known consensus divergence has occurred due to DIV.

P3: Compiler Behavioral Differences (Medium)

Different compiler versions and languages handle division differently at the language level, while the underlying DIV opcode is identical:

  • Solidity < 0.8.0: No division-by-zero check; raw DIV executes and returns 0
  • Solidity >= 0.8.0: Compiler inserts ISZERO + revert before DIV (unless unchecked)
  • Vyper: Has its own division-by-zero checking strategy
  • Huff / raw assembly: No checks unless manually added

Auditors reviewing bytecode must know which compiler produced it to understand whether division-by-zero protections exist.

P4: No State Impact (None)

DIV modifies only the stack. It cannot cause state bloat, storage writes, or memory expansion.


Edge Cases

Edge CaseResultSecurity Implication
a / 0Returns 0Critical: silent wrong answer; no revert, no exception
0 / b (b != 0)Returns 0Safe; mathematically correct
0 / 0Returns 0Silently wrong; should be undefined
1 / 2Returns 0Truncation; any small/large division yields 0
999999 / 1000000Returns 0Near-100% precision loss for ratios < 1
MAX_UINT256 / 1Returns MAX_UINT256Safe; identity division
MAX_UINT256 / MAX_UINT256Returns 1Safe; identity
MAX_UINT256 / 2Returns (2^256 - 1) / 2 = 2^255 - 1Truncates the 0.5 remainder
small / large (small < large)Returns 0Always 0; dangerous in share/price calculations
(a * b) / c vs a * (b / c)Different resultsOperation ordering changes output due to intermediate truncation

Real-World Exploits

Exploit 1: Balancer V2 — $128M Drained via Rounding Error (November 2025)

Root cause: Accumulated rounding-down errors in token decimal scaling, compounded through batch swap operations.

Details: On November 3, 2025, attackers drained $128.64 million from Balancer V2’s ComposableStablePool contracts across six blockchain networks in under 30 minutes. The vulnerability lay in the _upscaleArray function, which normalizes tokens of different decimal precisions (e.g., 6-decimal USDC to 18-decimal internal precision). This function always rounded down via integer division.

When token balances reached specific rounding boundaries (8-9 wei range), Solidity’s integer division truncated values, creating a systematic precision loss. The attacker compounded this through 65+ micro-swaps executed within a single constructor call, using batchSwap operations that stacked microscopic undercharges. Each individual rounding error was negligible, but the cumulative effect artificially deflated the pool’s invariant (D), which directly determines BPT price via BPT price = D / totalSupply.

The attacker bought underpriced BPT and redeemed it for full-value underlying tokens. Balancer’s centralized Vault architecture — where a single contract holds all pool tokens — meant one vulnerability in pool math impacted every ComposableStablePool simultaneously.

Ironically, a developer comment in the code read: “the impact of this rounding is expected to be minimal.” It was off by $128 million.

DIV’s role: Every _upscale and invariant calculation used integer division that truncated toward zero. The attacker’s batch swaps were specifically structured to maximize the cumulative truncation error across many sequential divisions.

References:


Exploit 2: ResupplyFi — $9.56M via Exchange Rate Collapse to Zero (June 2025)

Root cause: Integer division producing zero in an exchange rate calculation, bypassing solvency checks entirely.

Details: On June 25, 2025, an attacker exploited ResupplyFi’s lending protocol hours after a new ResupplyPair contract was deployed. The attack combined a classic ERC4626 first-deposit manipulation with a division-by-zero consequence:

  1. Attacker deposited 1 wei of collateral and minted a single vault share
  2. Attacker donated 2,000 crvUSD directly to the vault, inflating the share price to ~2 * 10
  3. ResupplyFi’s exchange rate formula computed 1e36 / oraclePrice. With the manipulated price at ~2e36, integer division produced: 1e36 / 2e36 = 0
  4. With an exchange rate of 0, the loan-to-value (LTV) calculation also evaluated to 0
  5. An LTV of 0 meant the attacker appeared infinitely solvent
  6. The attacker borrowed the full $10 million reUSD limit using 1 wei of collateral

The attack used flash loans from Morpho and Curve swaps to fund the initial donation. Stolen funds were distributed across multiple addresses and laundered through Tornado Cash.

DIV’s role: The critical moment was 1e36 / 2e36 = 0 — integer division truncating a sub-1 result to zero. This zero propagated through the solvency check, turning it into a no-op. The EVM’s DIV didn’t revert because the denominator wasn’t zero; it was just larger than the numerator, producing a legitimate but devastating zero result.

References:


Exploit 3: Yearn Finance yETH — $9M via Rounding-Down in Invariant Solver (November 2025)

Root cause: Unsafe arithmetic in the _calc_supply() invariant solver where rounding-down errors in division zeroed out critical product terms, enabling supply manipulation.

Details: On November 30, 2025, an attacker exploited the yETH Weighted Stable Pool. The pool’s bonding curve calculations used division operations without adequate precision guards. In the invariant solver’s iterative computation, rounding-down errors from integer division caused internal product terms to reach zero. Once a product term was zeroed, subsequent calculations diverged from the intended curve, allowing the attacker to mint far more yETH than their deposit warranted.

The attack had two phases:

  1. Primary exploit (~$8.1M): Flash-loaned funds were used to create extreme pool ratios where the invariant solver’s division-based rounding errors were maximized. The zeroed product terms allowed the attacker to drain wstETH, rETH, and cbETH from the pool.

  2. Secondary exploit (~$0.9M): After draining the pool to zero supply, a non-disabled bootstrap path reactivated. The attacker deposited just 16 wei across eight tokens, and the protocol — using cached virtual balances that weren’t reset — minted approximately 2.35 * 10^56 yETH tokens, which were swapped for real assets.

DIV’s role: The invariant solver’s iterative formula performed divisions at each step. When pool ratios were extreme, these divisions truncated intermediate values to zero, breaking the mathematical invariant the pool relied on for correct pricing.

References:


Exploit 4: Wise Lending — $466K via ERC4626 Vault Inflation (January 2024)

Root cause: Exchange rate inflation through donation, combined with withdrawal rounding, enabling iterative rate manipulation.

Details: An attacker exploited Wise Lending’s ERC4626 vault implementation through a sophisticated donation attack:

  1. Deposited a minimal amount to receive initial shares
  2. Donated assets worth 5,000,000x the initial deposit directly to the vault, inflating the exchange rate
  3. Wise Lending had a “throttle” mechanism intended to prevent large donations, but the attacker stayed just below the limit
  4. On withdrawal, the vault’s assets = shares * totalAssets / totalSupply calculation rounded down, creating unaccounted-for tokens that effectively acted as additional donations
  5. By iterating deposit-donate-withdraw cycles, the attacker ratcheted the exchange rate from 1:1 to 4:1
  6. Used the artificially valued vault shares as collateral to borrow $466K from other pools (USDC, WETH, USDT, DAI), then burned the inflated shares

DIV’s role: The share calculation assets * totalSupply / totalAssets truncated via DIV at each step. The truncation error on withdrawal created orphaned assets that increased the rate further, compounding across iterations.

References:


Exploit 5: Compound V2 First-Deposit Bug — Systemic Vulnerability Across Forks

Root cause: Integer division in cToken minting formula rounds to zero when the exchange rate is artificially inflated.

Details: This is a vulnerability class rather than a single incident, affecting Compound V2 and dozens of its forks (Cream Finance, Rari Capital, Hundred Finance, etc.). The core issue:

The cToken exchange rate is calculated as exchangeRate = underlyingBalance * 1e18 / totalSupply. The minted cTokens for a deposit are mintAmount = depositAmount / exchangeRate. When an attacker:

  1. Mints the smallest possible cToken amount on a fresh market (totalSupply == 0)
  2. Transfers underlying tokens directly to the cToken contract (bypassing mint())
  3. The exchangeRate becomes enormous: (deposit + donation) * 1e18 / 1
  4. A victim’s mintAmount = victimDeposit / enormousRate truncates to 0 via DIV
  5. Victim’s tokens are absorbed by the contract; attacker redeems their shares for everything

This pattern was reported across multiple audit contests (Sherlock, Code4rena, Immunefi) and has been independently discovered and exploited on numerous Compound forks. The fix involves “dead shares” (minting an initial non-redeemable amount) or using virtual assets/shares as in OpenZeppelin’s ERC4626 implementation.

DIV’s role: The mintAmount = depositAmount / exchangeRate calculation is a direct application of the DIV opcode. The truncation to zero is not a bug in DIV — it’s working exactly as specified — but the consequence of truncation in a financial context is catastrophic.

References:


Attack Scenarios

Scenario A: Vault Inflation / Donation Attack (Share Price Manipulation)

contract VulnerableVault {
    uint256 public totalShares;
    uint256 public totalAssets;
 
    function deposit(uint256 assets) external returns (uint256 shares) {
        if (totalShares == 0) {
            shares = assets;
        } else {
            shares = assets * totalShares / totalAssets;  // DIV truncates here
        }
        totalShares += shares;
        totalAssets += assets;
        // ... transfer assets from sender
    }
 
    function withdraw(uint256 shares) external returns (uint256 assets) {
        assets = shares * totalAssets / totalShares;
        totalShares -= shares;
        totalAssets -= assets;
        // ... transfer assets to sender
    }
}
 
// Attack:
// 1. attacker.deposit(1)          -> gets 1 share, totalAssets=1, totalShares=1
// 2. attacker transfers 1e18 tokens directly to vault contract
//    now totalAssets=1e18+1, totalShares=1
// 3. victim.deposit(5e17)         -> shares = 5e17 * 1 / (1e18+1) = 0 (truncated!)
//    victim receives 0 shares, but 5e17 was added to totalAssets
// 4. attacker.withdraw(1)         -> gets (1e18+1+5e17) * 1 / 1 = 1.5e18+1
//    attacker profits 5e17 (the victim's entire deposit)

Scenario B: Division-by-Zero Solvency Bypass

contract VulnerableLending {
    function borrow(uint256 amount, uint256 collateralTokenId) external {
        uint256 collateralValue = oracle.getPrice(collateralTokenId);
 
        // If oracle returns 0 (manipulated or stale), ltv = amount * 1e18 / 0
        // EVM DIV returns 0, NOT revert
        // With unchecked or assembly: ltv = 0, passes any maxLTV check
        uint256 ltv;
        assembly {
            ltv := div(mul(amount, 0xDE0B6B3A7640000), collateralValue)  // 1e18
        }
 
        require(ltv <= MAX_LTV, "Undercollateralized");
        // ltv == 0 always passes! Attacker borrows unlimited with zero collateral value
        _executeBorrow(msg.sender, amount);
    }
}

Scenario C: Dust Extraction via Repeated Rounding

contract VulnerableStaking {
    uint256 public totalStaked;
    uint256 public totalRewards;
 
    function claimReward(uint256 staked) external returns (uint256 reward) {
        // Rounds UP: gives attacker 1 wei extra per claim
        reward = (staked * totalRewards + totalStaked - 1) / totalStaked;
        totalRewards -= reward;
        // ... transfer reward
    }
}
 
// Attack on L2 (gas is cheap):
// Attacker stakes 1 wei, calls claimReward(1) thousands of times.
// Each call extracts ceil(totalRewards / totalStaked) instead of floor.
// Over many iterations, the attacker extracts significantly more than their
// proportional share. On L2 with ~$0.001 per tx, this is profitable.

Scenario D: Multiply-Before-Divide vs. Divide-First Precision Attack

// AMM fee calculation -- two implementations with different results
contract AMM {
    uint256 constant FEE_BPS = 30; // 0.3%
    uint256 constant BPS = 10000;
 
    // VULNERABLE: divides first, loses precision
    function getAmountOutBad(uint256 amountIn, uint256 reserveIn, uint256 reserveOut)
        external pure returns (uint256)
    {
        uint256 amountInAfterFee = amountIn - (amountIn / BPS) * FEE_BPS;
        // amountIn / BPS truncates first; for amountIn=33, result is 0 fee
        return reserveOut * amountInAfterFee / (reserveIn + amountInAfterFee);
    }
 
    // SAFE: multiplies first, preserves precision
    function getAmountOutGood(uint256 amountIn, uint256 reserveIn, uint256 reserveOut)
        external pure returns (uint256)
    {
        uint256 amountInAfterFee = amountIn * (BPS - FEE_BPS) / BPS;
        return reserveOut * amountInAfterFee / (reserveIn + amountInAfterFee);
    }
}
// For amountIn=33: Bad returns MORE tokens (lower effective fee)
// Attacker splits large trade into many 33-token trades to avoid fees

Mitigations

ThreatMitigationImplementation
T1: Division by zeroUse Solidity >= 0.8.0 (automatic revert on zero denominator)Default behavior; compiler inserts ISZERO check before DIV
T1: Assembly/uncheckedExplicit zero-check before every division in raw coderequire(denominator != 0) or if (denominator == 0) revert()
T2: Vault inflationUse virtual assets and virtual shares to prevent first-depositor manipulationOpenZeppelin ERC4626: _decimalsOffset() adds virtual shares/assets
T2: Vault inflationMint “dead shares” on vault initializationFirst mint sends minimum shares to address(0) or address(1)
T3: Operation orderingAlways multiply before dividing(a * b) / c not (a / c) * b; use mulDiv for overflow safety
T3: Overflow in mul-firstUse 512-bit intermediate precisionUniswap V3 FullMath.mulDiv() or OpenZeppelin Math.mulDiv()
T4: AMM roundingRound against the user / in protocol’s favorUse mulDivUp for withdrawals; mulDivDown for deposits
T5: Dust extractionEnforce minimum operation sizes; rate-limit claimsrequire(amount >= MIN_AMOUNT) or per-block claim limits
T5: L2 dust attacksAdd gas-equivalent cost floor for repeated operationsMinimum fee per operation that exceeds potential dust profit
T6: Solvency bypassValidate oracle outputs are non-zero and within sanity boundsrequire(price > 0 && price < MAX_REASONABLE_PRICE)
GeneralRound in the protocol’s favor on every user-facing operationDeposits: round shares down; Withdrawals: round assets down; Fees: round up

Compiler/EIP-Based Protections

  • Solidity 0.8.0+ (2020): Automatic revert on division by zero for all / and % operations. This eliminated the silent-zero-return class of bugs at the language level.
  • OpenZeppelin Math library: Provides mulDiv(a, b, denominator) and mulDiv(a, b, denominator, Rounding.Ceil) for precise division with controlled rounding direction and overflow-safe 512-bit intermediates.
  • Uniswap V3 FullMath: mulDiv() and mulDivRoundingUp() for 256-bit multiplication with 512-bit intermediate, then division — the gold standard for DeFi arithmetic.
  • OpenZeppelin ERC4626: Uses a configurable _decimalsOffset() to add virtual shares and assets, making inflation attacks economically infeasible by requiring donations proportional to 10^(decimals + offset).

Safe Rounding Direction Reference

The correct rounding direction is a security policy decision, not a mathematical preference:

OperationRound DirectionRationale
Mint shares on depositDown (floor)User receives fewer shares; prevents free share dust
Calculate assets on withdrawalDown (floor)User receives fewer assets; prevents draining
Protocol fee accrualUp (ceil)Protocol receives at least the minimum fee
Liquidation threshold checkUp (ceil)More aggressive liquidation protects solvency
Interest owed by borrowerUp (ceil)Borrower always pays at least the minimum

Severity Summary

Threat IDCategorySeverityLikelihoodReal-World Precedent
T1Smart ContractCriticalMediumResupplyFi ($9.56M), Reserve EasyAuction DoS
T2Smart ContractCriticalHighBalancer (466K), Compound V2 forks
T3Smart ContractHighHighBalancer V2 _upscale, Yearn yETH ($9M)
T4Smart ContractHighMediumBalancer ($128M via batch swap compounding)
T5Smart ContractMediumMedium (L2) / Low (L1)Various audit findings; economically viable on L2
T6Smart ContractHighMediumResupplyFi ($9.56M solvency bypass)
P1ProtocolLowN/A
P2ProtocolLowN/A
P3ProtocolMediumLowBehavioral differences across compiler versions

OpcodeRelationship
SDIV (0x05)Signed division; same truncation and division-by-zero behavior, but rounds toward zero (not floor) for negative values. Two’s complement edge case: type(int256).min / -1 overflows.
MOD (0x06)Unsigned modulus; returns the remainder that DIV discards. a == (a / b) * b + (a % b). Also returns 0 on modulus-by-zero.
MUL (0x02)Multiplication; the critical counterpart to DIV. Multiply-before-divide preserves precision but risks overflow. mulDiv libraries solve both.
MULMOD (0x09)(a * b) % N with 512-bit intermediate; avoids overflow in multiply-then-modulus. Used in precision-sensitive DeFi math.
ADDMOD (0x08)(a + b) % N with overflow-safe intermediate; used alongside MULMOD for modular arithmetic.
ISZERO (0x15)Used in the Solidity 0.8.0+ division-by-zero check: ISZERO(denominator) + JUMPI to revert before DIV executes.
LT (0x10)Used to detect when small / large == 0 would cause unacceptable precision loss; guards against truncation-to-zero in share calculations.