Uniswap V2's 1000-Wei Minimum Liquidity Lock, Explained
Why Uniswap V2 burns 1000 wei of LP tokens forever on the first mint of any pair. The attack it prevents, the code that implements it, and what happens to forks that strip it.
TL;DR
- Uniswap V2 burns 1000 wei of LP tokens to
address(0)on the first mint of any pair. Permanent dust, by design. - Without it, the first depositor can inflate the share value so the next depositor's tokens round to zero LP shares, leaving funds stuck in the pool.
- The attack works because of how the
mint()math computes shares: it rounds down, and the divisor is the depositor-controlled token balance. - The fix is one line:
_mint(address(0), MINIMUM_LIQUIDITY)on the first deposit. - Most teams forking Uniswap V2 either strip this lock or never understand why it's there. The test suite in our reconstruction module fails until you put it back.
Why this matters
The 1000-wei lock looks like premature optimization, or worse, a mistake. It's neither. It's a defense against a class of attack that breaks any constant-product AMM where the first depositor sets the initial price.
If you're forking Uniswap V2 (or any of its descendants, SushiSwap, PancakeSwap, ApeSwap, dozens of others), and your team strips this defense because it "looks unnecessary", you're shipping a pool that can be drained by a sophisticated first-depositor. This isn't a theoretical attack. Variations of share inflation have drained Hundred Finance ($7M), Midas Capital ($600K+), and several smaller forks since 2022.
Understanding this one line of code is a litmus test for whether your team understands why Uniswap V2 looks the way it looks.
The mechanism
When liquidity is added to a Uniswap V2 pair for the first time, two things happen that don't happen on subsequent deposits.
First, the LP token amount minted to the depositor is calculated as:
// From UniswapV2Pair.sol, mint() function
if (_totalSupply == 0) {
liquidity = Math.sqrt(amount0 * amount1) - MINIMUM_LIQUIDITY;
_mint(address(0), MINIMUM_LIQUIDITY); // permanently lock the first MINIMUM_LIQUIDITY tokens
} else {
liquidity = Math.min(
amount0 * _totalSupply / _reserve0,
amount1 * _totalSupply / _reserve1
);
}
Two things to notice here. The first depositor's LP amount is the geometric mean (sqrt(amount0 * amount1)) of their two-token contribution, minus MINIMUM_LIQUIDITY (which is 1000 wei). The subtracted amount is then minted to address(0), the burn address. It can never be redeemed.
For all subsequent depositors, the LP amount is proportional to their contribution relative to the existing reserves. The math is min(amount0 * totalSupply / reserve0, amount1 * totalSupply / reserve1). Notice the min(): it picks the smaller of the two ratios, which forces depositors to contribute proportionally to current reserves or accept dilution.
The 1000-wei lock at first mint is what makes the proportional math safe at every subsequent step.
The attack the lock prevents
Without the 1000-wei lock, here's what an attacker can do to a fresh pool:
Step 1. Attacker calls mint() with the smallest possible deposit: 1 wei of token0 and 1 wei of token1. The pool is empty, so totalSupply == 0, and the LP math gives them sqrt(1 * 1) = 1 LP token. They now own 100% of the pool.
Step 2. Attacker transfers a large amount of token0 directly to the pair contract using transfer(), bypassing mint(). Say they transfer 10,000 token0. The pair's actual balanceOf(token0) is now 10,001 wei, but reserve0 (stored in the contract) is still 1 wei because no sync() has been called. The attacker hasn't received any new LP tokens.
Step 3. A normal user comes along and tries to deposit, say, 9,999 token0 and 1 token1 worth (which is roughly 1:1 in pool ratio terms, since reserves are still 1:1).
The mint math runs:
liquidity = min(
9999 * 1 / 1, // 9999
1 * 1 / 1 // 1
) = 1
But wait, by the time the user's transfer arrives at the pair contract, _update() will sync reserves. The actual reserves used in the calculation depend on the protocol's internal accounting at that moment. The point is: with attacker-donated tokens skewing the relationship between balanceOf and reserve, the math gives small LP amounts to legitimate depositors.
In the worst case, the legitimate user's deposit rounds to 0 LP tokens because of integer division. Their tokens go into the pool, no shares come out, and the attacker, who still owns 100% of the LP supply, can burn() and walk away with the user's deposit.
The 1000-wei lock breaks this attack. By the time the attacker is done with step 1, they own only sqrt(1) - 1000 = -999, which underflows. The transaction reverts. The attacker can't establish a position with such a tiny initial deposit because the geometric-mean-minus-1000 math forbids it.
The minimum first deposit
With the 1000-wei lock, the smallest possible first mint is sqrt(amount0 * amount1) > 1000. That means the attacker has to deposit at least 1001 * 1001 = ~1.002M wei worth of each token to even establish a position. That makes the donation attack economically uninteresting: the attacker now has real capital at risk, and the user's "skim" attack window collapses.
It also means real first depositors lose 1000 wei worth of LP tokens forever. For a USDC/ETH pool, that's 1000 wei = $0.000000001 of dust. Negligible cost. Permanent defense.
Why teams strip it
When auditors review forks of Uniswap V2, the 1000-wei lock is one of the most commonly modified or removed pieces of code. The reasons fork authors give:
- "It's hardcoded magic, replace with a parameter." (Then they forget to set it.)
- "It's wasteful, our token has 6 decimals so 1000 wei is meaningful." (They didn't do the geometric-mean math.)
- "It's a Uniswap-specific quirk, not needed for our protocol." (It is needed. They didn't trace the attack.)
The fix is to leave the line alone, even if it looks like 7 characters of magic. If you're forking, understand what each piece does before you delete it.
Related questions
What is MINIMUM_LIQUIDITY in Uniswap V2? A constant equal to 1000 wei. On the first mint of any pair, this amount is permanently sent to address(0) to prevent share-inflation attacks.
Why 1000 specifically? The number is chosen so that the geometric-mean threshold (sqrt(amount0 * amount1) > 1000) requires meaningful first deposits. Smaller values would still defend against trivial 1-wei attacks but wouldn't deter slightly more capitalized attacker setups.
Does Uniswap V3 have the same lock? No. V3 uses concentrated liquidity and tick math, which has different attack surfaces. The 1000-wei lock is a V2-specific defense against share inflation in the constant-product model.
What happens if the pair is initialized but has zero liquidity later (after burns)? Once totalSupply > 0, the proportional math takes over. Even if all but the burned 1000 wei get withdrawn, the lock prevents the pool from returning to a state where a new "first depositor" can re-establish dominance.
Where to see it in the source
The line lives in UniswapV2Pair.sol::mint(), originally written by Hayden Adams and Noah Zinsmeister at Uniswap Labs in 2020. The constant is defined at the top of the contract:
uint public constant MINIMUM_LIQUIDITY = 10**3;
Any fork that doesn't replicate this constant and the _mint(address(0), MINIMUM_LIQUIDITY) call on first mint is shipping a vulnerability. If you're auditing one, this is among the first things to check.
Tagged