Skip to content

Research notes

Uniswap v2 kLast vs Current Pool k

A checked v2 ledger turns reserve-product growth into a protocol LP mint, then follows a new deposit and a fee-off checkpoint reset.

Ivory caliper frames enclose two spheres of different sizes, with a lime spacer between them on a black surface.
Illustration for research notes; examples in this article are educational.

In Uniswap v2, current pool k is the product of the stored token reserves, while kLast is a checkpoint written after a liquidity mint or burn when the protocol fee is enabled. Swaps can increase current k without updating kLast. A later mint or burn can issue LP tokens to feeTo from positive square-root growth, provided fees are enabled and kLast is nonzero. The checkpoint is not a running fee balance or a token price.

A pair can therefore report a larger reserve product than its saved checkpoint without anything being broken. The useful task is to reconstruct which event last moved each number, then apply the fee formula before accounting for a new deposit or withdrawal.

Read the checkpoint in the right implementation

This article follows the original Uniswap v2-core v1.0.1 implementation, pinned to commit 4dd5906. Its pair contract stores reserves and kLast separately. A fork can change the fee constants or update sequence, so matching a function name is insufficient.

Keep three quantities distinct:

QuantityHow to obtain itWhat it describes
Current reserve productreserve0 × reserve1 from one stateProduct of the pair's stored reserves
kLastRead the pair's stored valueProtocol-fee checkpoint, or zero
Actual balance productMultiply the two token contracts' balances at the pairProduct of actual balances, which may differ from the reserve product

After adjusting for token units, the reserve ratio describes a token exchange rate. The reserve product supplies the growth comparison. Neither is a dollar valuation. The constant-product versus StableSwap comparison explains the trading curve. Here the question is how v2 measures growth between liquidity events.

Uniswap's current protocol-fee documentation, checked on 28 October 2026, describes protocol fees as active on v2 in its rollout. For a particular pair and historical block, read its factory's feeTo. The disabled-fee cases below explain the behavior when that address is zero.

A swap and a deposit move different records

A swap updates reserves but does not run the protocol LP-token mint. A successful liquidity mint or burn calculates that mint against the reserves from before the liquidity event, then records the product after the event when fees are enabled. This order keeps a new proportional deposit out of the measured growth interval. The v2 whitepaper's protocol-fee section explains why collection waits for deposits and withdrawals.

Event in the original pairStored reservesProtocol-fee checkpoint
Successful swapUpdated to ending balancesUnchanged
Successful liquidity mint or burn, fees onUpdated after the liquidity changeReplaced with ending reserve product
Successful liquidity mint or burn, fees offUpdated after the liquidity changeCleared if previously nonzero
sync()Updated to current balancesUnchanged
Factory changes feeToNo immediate pair updateNo immediate pair update

The contract's reserve and fee update paths establish those transitions. A Sync event alone does not mean a new fee checkpoint was written: swaps also update reserves through the internal routine that emits it.

This is specific to v2. A v3 tick is a price coordinate, covered in Uniswap tick versus price. V3 position fees require the separate accounting described in fee growth versus fees owed. Neither field can substitute for v2's checkpoint.

Why the formula uses square roots and new supply

Let S be total LP-token supply before the protocol mint. Let a be the square root of current reserve product and b the square root of kLast.

When fees are enabled, kLast is nonzero and a > b, the original formula calculates:

m = S × (a − b) / (5a + b)

Here m is newly minted LP-token supply assigned to feeTo. The whitepaper derives the denominator by requiring the protocol's share after minting to equal one sixth of the growth share of current liquidity. In continuous arithmetic, that relationship is:

m / (S + m) = (a − b) / (6a)

The denominator includes the new tokens because minting dilutes existing shares. Taking one sixth of the raw change in k, or dividing a square-root change by six and calling it LP tokens, does not reproduce this relationship. Uniswap v2 whitepaper, section 2.4.

For exact contract amounts, use integer arithmetic. The pinned Math library returns an integer square root; the fee division also truncates. Small positive growth can produce zero new LP units. A successful fee-enabled liquidity event still writes its new checkpoint, so a dashboard should not assume every fractional estimate will eventually become a mint.

Reconcile one complete liquidity event

Consider a hypothetical established pair. All quantities below are raw integer contract units, including LP supply; token decimals change their displayed presentation. There is no dollar valuation or return forecast.

At the saved checkpoint, reserves are 1,300,000 units of each token and total LP supply is 1,300,000. Supply includes the original 1,000 locked raw LP units. Fees remain enabled throughout this example.

Assume ordinary swaps subsequently leave 1,430,000 units of each token. These are stipulated state inputs, not a reconstruction from reported volume. No liquidity changes, direct token transfers or rebases occur between checkpoints.

InputSaved checkpointBefore the next deposit
Reserve of each token1,300,0001,430,000
Reserve product1,690,000,000,0002,044,900,000,000
Square root of reserve product1,300,0001,430,000
LP supply before protocol mint1,300,0001,300,000
Stored kLast1,690,000,000,0001,690,000,000,000

Current k has risen 21%, while its square root has risen 10%. A percentage change in k would therefore overstate the square-root growth used by the fee formula.

The numerator is 1,300,000 × 130,000, or 169,000,000,000. The denominator is 5 × 1,430,000 + 1,300,000, or 8,450,000. Division produces exactly 20,000 new raw LP units, with no remainder.

Immediately after this protocol mint, supply is 1,320,000, while token reserves remain unchanged. The protocol owns 20,000/1,320,000, or approximately 1.51515% of the pool before the new provider's deposit is accounted for.

Check that result independently. The growth share of current liquidity is 130,000/1,430,000, approximately 9.09091%. One sixth is 1.51515%, matching the protocol's diluted ownership. This is not one sixth of the entire pool.

Now a provider adds 143,000 units of each token, equal to 10% of existing reserves. The liquidity calculation uses the supply after the protocol mint:

143,000 × 1,320,000 / 1,430,000 = 132,000

The new provider receives 132,000 raw LP units. Ending reserves are 1,573,000 of each token, and total supply is 1,452,000. The provider owns exactly one eleventh of the enlarged pool, approximately 9.09091%.

Finally, kLast becomes 1,573,000 × 1,573,000, or 2,474,329,000,000. That jump includes the deposit, but the deposit did not enter the fee calculation. The checkpoint now starts the next interval from the enlarged pool.

Disabled fees require an observed reset

The factory's pinned setter implementation changes feeTo without calling every pair. Consequently, a nonzero kLast can remain stored after the factory fee destination becomes zero.

Follow the pair's next successful mint or burn:

  1. It reads the zero destination, skips the protocol mint and clears a nonzero checkpoint.
  2. If fees are later enabled while the checkpoint is zero, the next successful liquidity event establishes a new post-event baseline without charging growth against zero.
  3. Subsequent fee-enabled liquidity events can compare against that baseline.

Those branches appear in the pair's fee calculation routine. Zero kLast therefore does not mean zero reserves, no trading activity or a failed pool.

There is a further consequence of the call order: if the factory switches off and back on before a pair executes a liquidity event, that pair never runs the clearing branch while fees are off. Its old checkpoint can survive, and the next enabled calculation may compare growth across that interval. Do not infer a clean excluded interval from configuration-change timestamps alone; replay the intervening pair calls.

Growth is not automatically fee income

The ordinary-swap example deliberately excludes balance changes outside trading. Uniswap's v2 troubleshooting documentation describes rebasing tokens that change balances independently and positive rebases that create surplus outside reserve accounting.

If surplus is incorporated through sync(), reserves can rise without a swap, while the checkpoint remains unchanged. The next fee calculation can therefore encounter growth that was not generated by ordinary trading. Label a reconstructed number “protocol LP mint implied by these inputs” until the event history supports a stronger attribution.

Nor does larger k establish investor profit. Token prices, inventory changes, dilution and transaction costs affect what a holder owns and what it is worth. The pool fee APR versus net return reconciliation handles that economic comparison. Adding a separate v2 fee estimate to a reserve-based redemption value can count the same embedded fees twice.

Save the evidence beside the calculation

For a reproducible checkpoint audit, retain the block number and hash. When reconstructing an event, also record its transaction position: end-of-block reads can hide earlier calls in the same block. Save the following worksheet beside the result:

RecordCheck before calculating
Chain, pair, factory and implementationConfirm the actual contract and fee constants
Token identities and raw reservesUse both reserve values from the same state
kLast, LP supply and feeToKeep checkpoint, ownership denominator and fee state together
Actual token balancesInvestigate any difference from stored reserves
Intervening swaps, liquidity events and configuration changesIdentify which call last wrote or cleared the checkpoint
Integer roots, numerator, denominator and minted unitsPreserve truncation and avoid rounded display inputs
Event being analyzedSeparate pre-event fee inputs from post-event reserves

When checking a withdrawal, remember the same ordering: the protocol mint can enlarge supply before the withdrawal's proportional token amounts are calculated. Using the old supply can overstate output.

Before trusting a fee display, reconstruct one successful mint or burn from its starting reserves through the protocol mint to its ending checkpoint. That single event should explain the new LP supply, the liquidity change and exactly why kLast moved.

Keep the context with the example.

Each worked example is illustrative, with its sources and assumptions in the article. Read our editorial standards for authorship, verification and corrections.