Class AmmMath
What an AMM pool will do before you ask it to - what a deposit is worth in LP tokens, what a withdrawal costs, what a swap pays out, and each of those read backwards - computed the way rippled computes it.
Inheritance
Namespace: Xrpl.Sugar
Assembly: Xrpl.dll
Syntax
public static class AmmMath
Remarks
The formulas are equations 3 and 7 from rippled's AMMHelpers.cpp, not the ones that
circulate as the "AMM single-sided deposit formula". The circulating one,
T·(√(1 + b·(1 − f/2)/B) − 1), is close and always wrong in the same direction: a
deposit the size of the pool at a 1% fee gives 0.41244·T against rippled's
0.41213·T, an error of 0.08%. LPTokensForSingleAssetDeposit(decimal, decimal, decimal, uint) reproduces
the node's figure.
Two things decide whether an estimate matches what the node actually credits, and neither is in the formulas:
- Whose fee. The holder of the pool's auction slot trades at
DiscountedFee- a tenth of the pool's trading fee - and the node computes their deposits and withdrawals at that rate too. Estimating at the pool's fee is wrong for them; see DiscountedTradingFee(uint). - How fresh the pool state is.
amm_infohas to be read immediately before the calculation. Run against a snapshot taken when a screen opened, the drift looks exactly like an error in the arithmetic.
What comes back is a bound rather than the exact credit, and the direction is known. Under
fixAMMv1_3 rippled rounds the final multiplication against the caller in both
directions - lpTokensOut downward ("minimize tokens out"), lpTokensIn upward
("maximize tokens in") - so a deposit is credited this much or a shade less, and a
withdrawal costs this much or a shade more. The difference lands in the last of
STAmount's 15 significant digits, which is below what differencing two reported LP
token balances can resolve.
Units are the caller's and nothing here converts between them. That matters most for XRP:
amm_info reports the XRP side of a pool in drops, so a balance read from it and an
amount the caller is thinking of in XRP are a million apart. Mixing the two in one call
returns a number that reads as a broken formula rather than as a unit mistake.
Everything is computed in decimal rather than double: 28 significant digits against 15. That is also why the square root here is Newton's method - System.Math.Sqrt(double) would throw away the precision the rest of the calculation keeps.
Fields
| Edit this page View SourceAuctionSlotFeeDiscount
How much cheaper the auction slot holder's fee is than the pool's.
Declaration
public const uint AuctionSlotFeeDiscount = 10
Field Value
| Type | Description |
|---|---|
| uint |
Remarks
rippled's kAuctionSlotDiscountedFeeFraction.
TradingFeeScale
What a TradingFee of 1 is worth as a fraction: 1/100 000, so 1000 is one per cent.
Declaration
public const uint TradingFeeScale = 100000
Field Value
| Type | Description |
|---|---|
| uint |
Remarks
rippled's kAuctionSlotFeeScaleFactor. The field is in units of 1/10 of a basis
point, which is easy to be out by a factor of ten on.
TradingFeeThreshold
The largest TradingFee a pool can have: 1000, one per cent.
Declaration
public const uint TradingFeeThreshold = 1000
Field Value
| Type | Description |
|---|---|
| uint |
Remarks
rippled's kTradingFeeThreshold, and the reason a fee is checked against it here.
The field is in units of 1/10 of a basis point, so a caller who reaches for basis points
or for whole per cent is out by a factor of ten or a hundred - and the arithmetic below
would carry on and return a plausible number rather than say so.
Methods
| Edit this page View SourceAssetsForProportionalDeposit(decimal, decimal, decimal, decimal)
How much of each asset a proportional deposit will actually take.
Declaration
public static (decimal Asset1, decimal Asset2) AssetsForProportionalDeposit(decimal poolBalance1, decimal poolBalance2, decimal deposit1, decimal deposit2)
Parameters
| Type | Name | Description |
|---|---|---|
| decimal | poolBalance1 | |
| decimal | poolBalance2 | |
| decimal | deposit1 | |
| decimal | deposit2 |
Returns
| Type | Description |
|---|---|
| (decimal Asset1, decimal Asset2) |
Remarks
The leftover of the more plentiful asset stays where it is; the node deposits both sides at the same fraction of the pool.
AssetsForProportionalWithdraw(decimal, decimal, decimal, decimal)
What redeeming LP tokens returns when both assets are taken out at the pool's ratio.
Declaration
public static (decimal Asset1, decimal Asset2) AssetsForProportionalWithdraw(decimal poolBalance1, decimal poolBalance2, decimal lpTokens, decimal lpTokenBalance)
Parameters
| Type | Name | Description |
|---|---|---|
| decimal | poolBalance1 | |
| decimal | poolBalance2 | |
| decimal | lpTokens | |
| decimal | lpTokenBalance |
Returns
| Type | Description |
|---|---|
| (decimal Asset1, decimal Asset2) |
Remarks
Equations 1 and 2: a = (t/T)·A and b = (t/T)·B. No fee, for the same
reason as a proportional deposit - the price does not move.
Exceptions
| Type | Condition |
|---|---|
| ArgumentOutOfRangeException | A balance is not positive, the token amount is negative, or it exceeds the pool's. |
DiscountedTradingFee(uint)
The fee the auction slot holder trades at.
Declaration
public static uint DiscountedTradingFee(uint tradingFee)
Parameters
| Type | Name | Description |
|---|---|---|
| uint | tradingFee | The pool's |
Returns
| Type | Description |
|---|---|
| uint |
Remarks
Use this in place of the pool's fee when the account holding the slot is the one depositing or withdrawing - the node does, and an estimate at the pool's fee will not match what it credits.
Exceptions
| Type | Condition |
|---|---|
| ArgumentOutOfRangeException |
|
LPTokensForProportionalDeposit(decimal, decimal, decimal, decimal, decimal)
LP tokens credited for depositing both assets at the pool's own ratio.
Declaration
public static decimal LPTokensForProportionalDeposit(decimal poolBalance1, decimal poolBalance2, decimal deposit1, decimal deposit2, decimal lpTokenBalance)
Parameters
| Type | Name | Description |
|---|---|---|
| decimal | poolBalance1 | |
| decimal | poolBalance2 | |
| decimal | deposit1 | |
| decimal | deposit2 | |
| decimal | lpTokenBalance |
Returns
| Type | Description |
|---|---|
| decimal |
Remarks
A deposit in proportion does not move the price, so no fee applies: the node credits
T · frac and takes A · frac and B · frac, where frac is the
smaller of the two ratios offered - whichever asset runs out first decides how much of
the other is used. Use AssetsForProportionalDeposit(decimal, decimal, decimal, decimal) to find out how much of
each will actually be taken.
Exceptions
| Type | Condition |
|---|---|
| ArgumentOutOfRangeException | A balance is not positive, or an amount is negative. |
LPTokensForSingleAssetDeposit(decimal, decimal, decimal, uint)
LP tokens credited for depositing one asset only.
Declaration
public static decimal LPTokensForSingleAssetDeposit(decimal poolBalance, decimal deposit, decimal lpTokenBalance, uint tradingFee)
Parameters
| Type | Name | Description |
|---|---|---|
| decimal | poolBalance | The pool's balance of the asset being deposited, before the deposit. |
| decimal | deposit | How much of it is being deposited. |
| decimal | lpTokenBalance | The pool's LP token balance, before the deposit. |
| uint | tradingFee | The fee this depositor trades at - see DiscountedTradingFee(uint). |
Returns
| Type | Description |
|---|---|
| decimal |
Remarks
Equation 3: with f1 = 1 − fee, f2 = (1 − fee/2)/f1 and r = b/B,
c = √(f2² + r/f1) − f2
t = T · (r − c) / (1 + c)
The node rounds the last multiplication down, so it credits this or a shade less.
The plus under the root is deliberate and is what rippled computes. The comment above
that equation in AMMHelpers.cpp writes it as √(f2² − b/(B·f1)), but the
code uses +, and so does the derivation of equation 4 immediately below it. With
a minus the radicand goes negative for ordinary inputs, which settles it.
Exceptions
| Type | Condition |
|---|---|
| ArgumentOutOfRangeException | A balance is not positive, the deposit is negative, or the fee exceeds TradingFeeThreshold. |
LPTokensForSingleAssetWithdraw(decimal, decimal, decimal, uint)
LP tokens spent to withdraw one asset only.
Declaration
public static decimal LPTokensForSingleAssetWithdraw(decimal poolBalance, decimal withdraw, decimal lpTokenBalance, uint tradingFee)
Parameters
| Type | Name | Description |
|---|---|---|
| decimal | poolBalance | The pool's balance of the asset being withdrawn, before the withdrawal. |
| decimal | withdraw | How much of it is being withdrawn. |
| decimal | lpTokenBalance | The pool's LP token balance, before the withdrawal. |
| uint | tradingFee | The fee this account trades at - see DiscountedTradingFee(uint). |
Returns
| Type | Description |
|---|---|
| decimal |
Remarks
Equation 7: with fr = b/B and c = fr·fee + 2 − fee,
t = T · (c − √(c² − 4·fr)) / 2
Note that this one uses the fee itself where LPTokensForSingleAssetDeposit(decimal, decimal, decimal, uint)
uses 1 − fee; rippled's lpTokensIn calls getFee rather than
feeMult, and the difference is easy to lose when transcribing.
The node rounds the last multiplication up here rather than down - both directions go
against the caller - so a withdrawal costs this or a shade more.
Exceptions
| Type | Condition |
|---|---|
| ArgumentOutOfRangeException | A balance is not positive, the amount is negative, it exceeds the pool, or the fee exceeds TradingFeeThreshold. |
SingleAssetDepositForLPTokens(decimal, decimal, decimal, uint)
How much of one asset must be deposited to be credited exactly this many LP tokens.
Declaration
public static decimal SingleAssetDepositForLPTokens(decimal poolBalance, decimal lpTokens, decimal lpTokenBalance, uint tradingFee)
Parameters
| Type | Name | Description |
|---|---|---|
| decimal | poolBalance | The pool's balance of the asset being deposited. |
| decimal | lpTokens | The LP tokens wanted. |
| decimal | lpTokenBalance | The pool's LP token balance, before the deposit. |
| uint | tradingFee | The fee this depositor trades at - see DiscountedTradingFee(uint). |
Returns
| Type | Description |
|---|---|
| decimal |
Remarks
Equation 4, which is rippled solving equation 3 for b. With f1 and
f2 as in LPTokensForSingleAssetDeposit(decimal, decimal, decimal, uint), t1 = t/T and
t2 = 1 + t1:
d = f2 - t1/t2
a = 1/t2², b = 2·d/t2 - 1/f1, c = d² - f2²
deposit = B · (-b + √(b² - 4ac)) / 2a
The root is always real: swept across every fee up to the cap and token ratios from
1e-6 to 1000, the discriminant never falls below 1, so the quadratic cannot hand
Xrpl.Sugar.AmmMath.Sqrt(decimal) a negative and rippled's solveQuadraticEq does not guard it
either.
This is what an AMMDeposit carrying LPTokenOut will actually take from
the account, and the direction of the node's rounding reverses here: it maximizes the
deposit, so it takes this much or a shade more. That is consistent rather than
contrary - every one of these roundings favours the pool.
Exceptions
| Type | Condition |
|---|---|
| ArgumentOutOfRangeException | A balance is not positive, the token amount is negative, or the fee exceeds TradingFeeThreshold. |
SingleAssetWithdrawForLPTokens(decimal, decimal, decimal, uint)
How much of one asset comes out for redeeming exactly this many LP tokens.
Declaration
public static decimal SingleAssetWithdrawForLPTokens(decimal poolBalance, decimal lpTokens, decimal lpTokenBalance, uint tradingFee)
Parameters
| Type | Name | Description |
|---|---|---|
| decimal | poolBalance | The pool's balance of the asset being withdrawn. |
| decimal | lpTokens | The LP tokens being redeemed. |
| decimal | lpTokenBalance | The pool's LP token balance, before the withdrawal. |
| uint | tradingFee | The fee this account trades at - see DiscountedTradingFee(uint). |
Returns
| Type | Description |
|---|---|
| decimal |
Remarks
Equation 8, rippled solving equation 7 for b. With t1 = t/T:
withdraw = B · (t1² - t1·(2 - fee)) / (t1·fee - 1)
Both halves of that fraction are negative for any real input, which is why the result
is not. What an AMMWithdraw carrying LPTokenIn pays out; the node
minimizes the withdrawal, so it pays this or a shade less.
Exceptions
| Type | Condition |
|---|---|
| ArgumentOutOfRangeException | A balance is not positive, the token amount is negative or exceeds the pool's, or the fee exceeds TradingFeeThreshold. |
SwapAssetIn(decimal, decimal, decimal, uint)
What comes out of the pool for swapping this much of the other asset in.
Declaration
public static decimal SwapAssetIn(decimal poolIn, decimal poolOut, decimal assetIn, uint tradingFee)
Parameters
| Type | Name | Description |
|---|---|---|
| decimal | poolIn | The pool's balance of the asset being swapped in. |
| decimal | poolOut | The pool's balance of the asset being swapped out. |
| decimal | assetIn | How much is being swapped in, fee included. |
| uint | tradingFee | The fee this account trades at - see DiscountedTradingFee(uint). |
Returns
| Type | Description |
|---|---|
| decimal |
Remarks
rippled's swapAssetIn, and what a payment routed through an AMM pays the taker.
The node writes it as
out = poolOut - (poolIn · poolOut) / (poolIn + in·(1 - fee))
which is the form used here, rearranged to poolOut·x/(poolIn + x) with
x = in·(1 - fee). The two are the same expression; the difference is that the
node's form subtracts two nearly equal numbers for a small swap and loses digits to
the cancellation, while this one has nothing to cancel.
The fee comes off the input before the curve sees it, so the whole of
assetIn still enters the pool - the fee stays there for the
liquidity providers rather than being taken away.
Exceptions
| Type | Condition |
|---|---|
| ArgumentOutOfRangeException | A balance is not positive, the amount is negative, or the fee exceeds TradingFeeThreshold. |
SwapAssetOut(decimal, decimal, decimal, uint)
What must be swapped in to take exactly this much of the other asset out.
Declaration
public static decimal SwapAssetOut(decimal poolIn, decimal poolOut, decimal assetOut, uint tradingFee)
Parameters
| Type | Name | Description |
|---|---|---|
| decimal | poolIn | The pool's balance of the asset being swapped in. |
| decimal | poolOut | The pool's balance of the asset being swapped out. |
| decimal | assetOut | How much is wanted out. |
| uint | tradingFee | The fee this account trades at - see DiscountedTradingFee(uint). |
Returns
| Type | Description |
|---|---|
| decimal |
Remarks
rippled's swapAssetOut, the inverse of SwapAssetIn(decimal, decimal, decimal, uint):
in = ((poolIn · poolOut) / (poolOut - out) - poolIn) / (1 - fee)
rearranged here to poolIn·out / ((poolOut - out)·(1 - fee)) for the same reason
as above. The cost climbs without bound as assetOut approaches the
pool's balance, which is the constant product refusing to be emptied; asking for the
whole of it, or more, is rejected rather than answered with a division by zero.
Exceptions
| Type | Condition |
|---|---|
| ArgumentOutOfRangeException | A balance is not positive, the amount is negative or is not less than the pool, or the fee exceeds TradingFeeThreshold. |
TradingFeeFraction(uint)
The trading fee as a fraction of 1.
Declaration
public static decimal TradingFeeFraction(uint tradingFee)
Parameters
| Type | Name | Description |
|---|---|---|
| uint | tradingFee | The pool's |
Returns
| Type | Description |
|---|---|
| decimal |
Exceptions
| Type | Condition |
|---|---|
| ArgumentOutOfRangeException |
|