Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,16 @@ The raw-response work, all five levels landing together. The problem: a consumer

The entries below are grouped by what changed, not by the order the levels were built in. The short version of what will not compile is in the table above.

* **AMM deposit and withdrawal arithmetic, taken from rippled rather than from the formula that circulates** (#133). The SDK offered no way to work out what an `AMMDeposit` would credit before submitting it, so consumers reached for the widely quoted `T·(√(1 + b·(1 − f/2)/B) − 1)`. That formula is the right one with the fee applied loosely, and it is exact wherever there is no fee - which is what makes it hard to catch. At a 1% fee it credits **0.41244·T** where the node credits **0.41213·T**: out by 0.08%, always in the direction that promises more tokens than arrive.
* `Xrpl.Sugar.AmmMath` is static and needs no client: `LPTokensForSingleAssetDeposit`, `LPTokensForSingleAssetWithdraw`, `LPTokensForProportionalDeposit`, `AssetsForProportionalDeposit`, `AssetsForProportionalWithdraw`, plus `TradingFeeFraction` and `DiscountedTradingFee` with the `TradingFeeScale` (100 000) and `AuctionSlotFeeDiscount` (10) constants behind them
* the single-asset pair are equations 3 and 7 from rippled's `AMMHelpers.cpp` - `lpTokensOut` and `lpTokensIn` - transcribed rather than derived. The two are not symmetric, and the asymmetry is easy to get backwards: `lpTokensIn` multiplies by the fee where `lpTokensOut` multiplies by `1 − fee`. Swapping them still satisfies the round-trip inequality, which is too loose to notice, so the pair is pinned by the zero-fee identity instead: with no fee the two equations must invert each other exactly, and with the multipliers swapped they miss by a wide margin
* **the auction slot is the trap that makes correct formulas look wrong.** Its holder trades at `DiscountedFee`, a tenth of the pool's fee, and `AMMCreate` hands the slot to whoever created the pool - so the account most likely to be estimating is the one the pool's fee is wrong for. Estimating at the pool's fee in the integration test was out by 0.23%, three times the error of the approximation this replaces, with every equation right. Read the fee from `amm_info`'s auction slot when the account holds it
* **the swap, and the inverse of each equation.** `SwapAssetIn`/`SwapAssetOut` are rippled's own, equation (2) in `AMMHelpers.h`: a payment routed through a pool takes the fee off the input before the curve sees it, which is not the same number as taking it off the output. `SingleAssetDepositForLPTokens` and `SingleAssetWithdrawForLPTokens` are equations 4 and 8 - what an `AMMDeposit` carrying `LPTokenOut` will cost, and what an `AMMWithdraw` carrying `LPTokenIn` returns. Each pair composes to the identity, which is what pins the two derived through a quadratic
* units are the caller's and nothing converts between them, which is now said out loud because it bites: `amm_info` reports the XRP side of a pool in **drops**, so a balance read from it and an amount a caller thinks of in XRP are a million apart, and mixing them reads as a broken formula rather than a unit mistake
* a fee in the wrong units is refused rather than answered. `TradingFee` is in units of 1/100 000 and rippled caps it at 1000 (`kTradingFeeThreshold`, now `AmmMath.TradingFeeThreshold`); reaching for basis points or whole per cent is out by a factor of ten or a hundred, and the arithmetic notices nothing - at 5000 every intermediate value stays finite and a plausible wrong number comes back. The bound is on the fee itself, so it holds even for an amount of zero
* what comes back is a bound rather than the exact credit, and the direction is documented. Under `fixAMMv1_3` rippled rounds the final multiplication against the caller both ways - `lpTokensOut` downward, `lpTokensIn` upward - so a deposit is credited this much or a shade less and a withdrawal costs this much or a shade more. It lands in the last of `STAmount`'s 15 significant digits
* `decimal` throughout, and a square root written for it. `Math.Sqrt` carries 15 significant digits against `decimal`'s 28, and the root is the one step where the formulas need the precision
* checked against a node, not only against the source: five integration tests put deposits, withdrawals and a payment routed through the pool on the standalone stand and compare the estimate with what the node actually moved. Relative errors from 9.7e-17 (the swap) to 6.2e-14 - the precision the node reports balances at. That is the measurement; what the tests enforce is 1e-9, because the figures compared are differences of two reported balances and pinning them to the last digit would buy brittleness rather than coverage. The swap test also covers the one case the others cannot: an account that does **not** hold the auction slot, and therefore trades at the pool's own fee. Unit tests can only prove a formula was copied faithfully - a faithful copy of the wrong equation passes all of them
* **`nft_info` and `nft_history`, the two Clio commands NFT work needs** (#132). Neither had a model, and neither has a substitute on a rippled node.
* **an owner cannot be read out of `nft_sell_offers`**, which is the natural guess. Selling a token does not remove offers for it from the ledger, so offers made by a previous owner keep being returned long after they can no longer be accepted, and the new owner has usually made none - which is exactly the state a token is in right after being bought. Taking the owner from the first offer shows the wrong account
* field names were taken from Clio's own handlers rather than from documentation, and one of them differs: Clio emits `nft_serial`, and its own source notes that the docs call it `nft_sequence`. A test pins the name that arrives on the wire
Expand Down
Loading
Loading