Smardex fictive reserves change price impact as pool conditions evolve

Smardex fictive reserves are pricing balances that determine how a swap moves a pool's price. The algorithm uses them alongside actual token reserves and a moving price average. Their size and recalculation rules shape execution, so the real token balances alone don't determine a trade's output.

Last updated ยท
Sections in this article
The ratio of fictive reserves sets the marginal price before fees, while their scale influences how far a swap moves that price.

Real token balances and fictive pricing balances

Each pool maintains separate fictive balances for its two tokens and pays swap output with actual tokens. The virtual amounts shape the pricing curve; they don't add tokens to the contract or create another withdrawable balance.

Reserve accounting also separates fees owed elsewhere. In the Ethereum v2 pair, getReserves returns token balances after subtracting pending fee-recipient amounts. A raw token balance can therefore differ from the reserve figure used for pricing. That difference comes from fee accounting, independently of the fictive-reserve adjustment.

The same marginal price with different depth

Two attributes of fictive reserves affect execution: their ratio gives the marginal exchange price before fees, while their size controls the curve's response to a finite trade. Marginal price describes the rate before the trade itself moves the curve.

The reserve ratio

Dividing fictive output reserves by fictive input reserves expresses output-token units per input-token unit, after decimal conversion. Larger swaps receive an average rate along the curve as they move the price. The actual reserve ratio can differ from this marginal price after trading shifts inventory.

Initial scaling and curve size

The first liquidity mint initializes each fictive reserve at half its corresponding token balance, with integer rounding. That shared scaling preserves the starting reserve ratio apart from rounding. It also makes a given input larger relative to the balances that the pricing calculation uses.

Within a single pricing segment, ignoring fees and integer rounding, output follows q * F_out / (F_in + q) . Here q is input quantity and F_in and F_out are the segment's fictive balances in matching token units. At the same starting ratio, smaller balances mean greater price movement for a given input. Later recalculations change those balances, so the initial half-reserve setting isn't a permanent pricing rule.

Trade direction and pool inventory

Recalculation responds to the relationship between real inventory and the price implied by fictive balances. The algorithm values the real tokens at that price, then changes effective depth according to the swap's direction. Its formulas can therefore give different curve depths to opposite directions in an imbalanced pool. Deeper effective liquidity lets the pool release more of the token that it seeks to sell for a given price movement. Shallower liquidity makes that movement steeper. This design aims to retain more of a rising token early in a price move and sell it later at higher prices.

The moving-average boundary

The Ethereum v1 and v2 pricing libraries cap elapsed time at 300 seconds when updating the price average. The update keeps the previous average when the current timestamp matches its last-update timestamp. At a later timestamp, the formula blends the previous average with the pre-swap fictive-reserve price, using the capped interval.

The average supplies a boundary for reserve recalculation. Keeping it fixed within a timestamp limits the advantage of resetting the pricing curve during an immediate reverse trade.

A swap that crosses the average

The swap calculation can apply different fictive balances to successive portions of one trade. Crossing the moving-average price determines whether the calculation needs that internal split. Trade direction matters because an input can move the price toward the boundary or away from it.

Before the boundary

A trade starting away from the average uses the existing curve while moving toward it without reaching it. A trade moving away also stays on that curve. When the starting price approximately equals the average, reserve recomputation can occur before the trade proceeds.

Beyond the boundary

A sufficiently large trade toward the average first reaches that boundary on the existing curve, subject to integer rounding. The calculation then recomputes fictive reserves from the resulting balances and prices the remaining amount with those revised reserves. The portions contribute to one overall input or output amount.

Smardex performs this split inside the swap calculation. It doesn't require two separately submitted user trades. Applying one constant-product curve to the whole amount can miss the boundary and return a different answer.

Fees and trade-size quotes

Swap fees reduce the input that enters the pricing formula. They also affect the updated reserves, because the liquidity provider (LP) fee contributes to pool value. Reproducing the swap's amounts requires an estimate that includes the charged fee components.

In the Ethereum v2 design, the factory owner can change pair fees within the contract's limits. That makes the fee setting a live input. A fee percentage copied from another pool or implementation can give the wrong output even when fictive balances match.

Token approval for a first swap gives a contract spending permission; it doesn't freeze these pricing inputs.

A decision checklist for a changing quote

For an exact-input swap, choose the minimum output that would make the trade acceptable before interpreting a quote. Compare the quoted output with that same floor whenever the quote changes. The pricing rule stays the same while its inputs move.

  • Keep the input amount and token direction unchanged when comparing the original and refreshed quotes.
  • Use a consistent block-state snapshot for each quote's reserves, updated average and applicable fees.
  • Include the moving-average boundary calculation when the selected trade crosses it.
  • Keep the minimum output tied to the amount required, even if the newer quote falls.
  • Proceed only while the refreshed output meets that floor; a lower quote calls for stopping or reconsidering the trade.

An intervening swap can change fictive balances and move the refreshed output below the chosen minimum. The Ethereum direct router reverts an exact-input swap when final output misses its minimum. Refreshing a quote predicts an amount; execution settles it.

Liquidity additions and withdrawals

Liquidity operations scale fictive reserves with changes in LP-token supply. An additional deposit into an existing pool raises the virtual balances proportionally to the minted liquidity; a withdrawal reduces them proportionally to the burned liquidity. These operations change curve depth without requiring a price-changing swap. Deposit quantities follow the real reserve proportions. Since those proportions can differ from the fictive-reserve price, the deposited tokens needn't have equal market values. The swap-pricing ratio alone therefore doesn't determine the quantities required for a liquidity contribution.

After fee accounting, a successful LP-token burn distributes actual token balances proportionally to the burned share of supply.

Liquidity additions and withdrawals (Smardex fictive reserves) - illustration

View full-size image

Impermanent loss and the holding benchmark

Impermanent loss compares a liquidity position's value with the value of holding its originally deposited tokens. It can exist even when both values rise. Impermanent gain describes outperformance against that holding benchmark.

Fictive-reserve pricing changes the inventory path that produces that comparison. The sequence of swaps and relative prices therefore matters, alongside the fees that accrue. A reserve snapshot can't establish an LP position's return relative to holding. That requires matching the starting assets and valuation period, then valuing the final claim on the same basis.

Pair pricing and routed swaps

Fictive reserves describe the Smardex pool leg of a route. The Universal Router can combine Smardex swaps, other supported swap commands and token wrapping within one transaction. A whole-route quote therefore reflects additional mechanisms when it includes other legs. A difference between routed quotes doesn't isolate the effect of fictive reserves unless the compared pool path, input amount and fee treatment match.

Details worth knowing about Smardex fictive reserves

Why does a fictive-reserve price look different when the pool tokens use different decimals?

A raw reserve ratio needs decimal conversion to express a price in whole-token units. Each reserve counts its own token's smallest units. Normalize both balances using their respective token decimals before dividing them. Otherwise, a display can show a large apparent price difference that comes entirely from unit scaling.

How does integer rounding affect a Smardex exact-output quote?

The library adds one smallest input-token unit after integer division when calculating required input for a constant-product segment. Output calculations for a fixed input use truncating division. Reversing an exact-output calculation therefore won't necessarily recover the identical amount, especially when a swap spans more than one pricing segment.

Is the moving price average an external oracle for a Smardex pool?

The moving average derives from the pool's fictive-reserve prices and elapsed time. It doesn't supply an independently observed market price. Its function is to govern the swap algorithm's recalculation boundary.

What identifies the token order in getFictiveReserves?

The pair's token0 and token1 fields identify the order of its returned fictive reserves. The first reserve belongs to token0 and the second belongs to token1. An input-to-output calculation must align those values with the selected trade direction, which can run in either order.

Which condition triggers FICTIVE_RESERVES_TOO_LOW?

The Ethereum v2 pair rejects an update when either resulting fictive reserve equals zero. The FICTIVE_RESERVES_TOO_LOW error identifies that specific guard. It doesn't establish that both actual token balances are empty, and raising a router's slippage tolerance doesn't change this reserve-update requirement.

Will calling getAmountOut change the pool's fictive reserves?

The direct router's getAmountOut function returns calculated values without changing the pool. Its return data includes predicted reserve balances as well as output. Those values describe the supplied state and parameters; the pair's swap function performs the actual state-changing trade.

Can a Smardex pool's price return to its starting point while real reserves differ?

Yes, matching start and end fictive-reserve prices can coexist with different actual reserve balances. Recalculation changes the inventory path, and trading fees also affect pool balances. A return to the starting price therefore doesn't establish that the pool retraced its token movements or recovered its original composition.