XRP Ledger security record

As of 3 Nov 2026, XRP Ledger has 2 audit reports by 1 security firm on Trustblock; the latest is by CertiK, dated 15 Aug 2023. Those reports list no open critical findings.

Critical findings
No open critical findings
Across 2 reports.
Open the latest report
Your label, live
Get the label for your site

Record last updated . It holds audits, findings and contracts up to Nov 2026, and incidents up to Oct 2026. Trustblock does not review code.

Audits2

Newest first

AuditSecurity firmDateScopeCritical findingsReport
XRPL AMM AddendumSecurity firm: CertiKDate: Scope: Not recorded (see the report)No open critical findingsOpen · skynet.certik.com
XRPL AMMSecurity firm: CertiKDate: Scope: Not recorded (see the report)No open critical findingsOpen · skynet.certik.com

Findings35

Statuses are the security firm’s own, from each report.

SeverityOpenAcknowledgedFixed
Critical000
High000
Medium008
Low2124
Medium`AMMDeposit` doesn't provide a protection from prXRPL AMM · CertiK · 21 Jun 2023Fixed

`AMMDeposit(tfTwoAsset)` allows specifying `amount` and `amount2`. They are used as maximum values consumed by the deposit operation. However, that doesn't allow the user to set the protection from front-running. Other implementations of AMM, like Uniswap, allow the user to set the minimum amounts accepted: ```solidity function addLiquidity( address tokenA, address tokenB, uint amountADesired, uint amountBDesired, uint amountAMin, uint amountBMin, address to, uint deadline ``` If `amountAMin`/`amountBMin` conditions are not satisfied, the transaction is rejected. This effectively protects the user from front-running and sudden pool spot-price changes for other reasons. Other modes of `AMMDeposit` also don't provide protection from price fluctuations.

Open the report
Medium`applyBid()` treats `minBid` incorrectlyXRPL AMM · CertiK · 21 Jun 2023Fixed

```cpp=242 if (computedPrice >= *bidMin && computedPrice <= *bidMax) return computedPrice; JLOG(ctx_.journal.debug()) << "AMM Bid: not in range " << computedPrice << *bidMin << " " << *bidMax; ``` `AMMBid` allows setting `sfBidMin` and `sfBidMax`. `computedPrice` is calculated based on the current slot state. The code doesn't allow raising the bid via setting `sfBinMin` if `sfBidMax` is also specified.

Open the report
Medium`generateFibSeqOffer()` can return negative valueXRPL AMM · CertiK · 21 Jun 2023Fixed

```cpp=81 cur.out = toAmount<TOut>( getIssue(balances.out), cur.out * fib[ammContext_.curIters() - 1], Number::rounding_mode::downward); cur.in = swapAssetOut(balances, cur.out, tradingFee_); ``` `generateFibSeqOffer()` is supposed to generate AMM offers with the offer size based on Fibonacci sequence. The starting `cur.in = InitialFibSeqPct * initialBalances_.in = 0.025% of initialBalances_.in`. `cur.out` is approximately the same. Then `cur.out` is multiplied by `fib` elements based on the iteration. Since `fib[17] = 4181`, `cur.out` will get bigger than `balances.out`. That is unexpected by `swapAssetOut()` - `cur.in` will become negative or overflow.

Open the report
Medium`withdrawByTokens()` is inaccurateXRPL AMM · CertiK · 21 Jun 2023Fixed

It is possible to perform a single asset withdrawal via `AMMWithdraw(tfOneAssetLPToken)`. However, the formula (8) is inaccurate. It is more advantageous for the user to perform `AMMWithdraw(tfLPToken)` and then swap the redundant token.

Open the report
MediumFreeze status is not checked properly in `AMMWithXRPL AMM · CertiK · 21 Jun 2023Fixed

In `AMMWithdraw::preclaim()` if `sfAmount`/`sfAmount2` are set, it is checked that * account is authorized to hold the tokens and the trustline exists * AMM account or currency is not frozen * account is not frozen However, if `tfLPToken` is set, it is not checked that account is authorized to hold the tokens. If `tfWithdrawAll` is set, all three checks are skipped. As a result, the frozen account can get tokens from AMM or tokens can be extracted from frozen AMM.

Open the report
MediumSingle asset deposit fees are inaccurateXRPL AMM · CertiK · 21 Jun 2023Fixed

It is possible to perform a single asset deposit via `AMMDeposit(tfSingleAsset)`. However, the formulas [(3) and (4)](https://github.com/XRPLF/XRPL-Standards/discussions/78) are inaccurate. Fees are always taken for half of the deposited asset and pool value change due to fees is not accounted for. The variable amount should be virtually swapped with the corresponding fee taken. So the users will prefer `AMMDeposit(tfSingleAsset)` then `AMMWithdraw(tfLPToken)` instead of normal swap to save some fees. Current logic behind single asset deposit formula **(3)**: $$\Delta_{LPTokens} = \Gamma_{LPTokens} * \left[ \left(1+ \frac{\Delta_{B}-TFee*(1-W_B)*\Delta_B}{\Gamma_B} \right) ^ {W_B} - 1 \right] $$ 1. The deposited amount $$ \Delta_B $$ is split into $$ W_B\Delta_B + (1-W_B)\Delta_B = W_B\Delta_B + W_A\Delta_B$$ 2. Then $$W_A\Delta_B$$ of AssetB is virtually swapped for AssetA 3. Pool spot-price $$ SP = \frac{\frac{\Gamma_A}{W_A}}{\frac{\Gamma_B}{W_B}} $$ (see **(II)**) is used to get $$ \Delta_A = W_B\Delta_B \frac{\Gamma_A}{\Gamma_B}$$ 4. Two assets $$\left( \Delta_A, W_B\Delta_B \right) = \left( W_B\Delta_B \frac{\Gamma_A}{\Gamma_B} , W_B\Delta_B \right)$$ are deposited to pool with $$\left( \Gamma_A, \Gamma_B \right)$$ reserves 5. The pool ratio $$ \frac{\Gamma_A}{\Gamma_B} $$ (and therefore pool spot-price) remains the same since $$ \frac{\Gamma'_A}{\Gamma'_B} = \frac{\Gamma_A + W_B\Delta_B \frac{\Gamma_A}{\Gamma_B}}{\Gamma_B + W_B\Delta_B} = \frac{\Gamma_A}{\Gamma_B} $$ 6. Since we virtually swapped $$W_A\Delta_B$$, only $$ TFee W_A $$ was taken, that's why $$ \Delta_B $$ in **(3)** is reduced by $$ \left( 1-TFee(1-W_B)\right) $$ However, the pool state changes during the swap operation, so we can't perform the swap using the spot-price and then perform a deposit using the same spot-price. In case $$ \Delta_B $$ is comparable to $$ \Gamma_B $$ and $$ Tfee $$ is significant, the resulting $$ \Delta_{LPTokens} $$ will be higher than obtainable via real swap and `AMMDeposit(tfTwoAsset)`. See the example in the Scenario section. The proposed logic of single asset deposit is: 1. Let $$ W_A = W_B = 0.5 $$ 2. We want to split $$ \Delta_B $$ into two parts $$ x + (\Delta_B - x) $$ and swap $$ x $$ for $$ \Delta_A = \Gamma_A \frac{x(1-TFee)}{\Gamma_B + x(1-TFee)} $$ (see **(9)**) 3. The $$x$$ should be so that the final pool reserves ratio $$\frac{\Gamma_A}{\Gamma_B+\Delta_B} $$ is equal to the ratio of assets deposited $$ \frac{\Delta_A}{\Delta_B - x} $$ and is equal to the pool reserves ratio after the swap $$ \frac{\Gamma_A - \Delta_A}{\Gamma_B + x} $$ 4. Using $$ \frac{\Gamma_A}{\Gamma_B + \Delta_B} = \frac{\Delta_A}{\Delta_B-x} $$ and substituting $$ \Delta_A $$ we get $$ (\Delta_B - x)(\Gamma_B + x(1-TFee)) = (\Gamma_B + \Delta_B)x(1-TFee) $$ 5. Solving it gives $$ x = \Gamma_B (\sqrt{ \left( \frac{1-TFee/2}{1-TFee} \right) ^2 + \frac{\Delta_B}{\Gamma_B(1-TFee)}} - \frac{1-TFee/2}{1-TFee} )$$ 6. After the swap of $$ x $$ of AssetB for $$ \Delta_A $$ of AssetA, we perform all assets deposit with $$ \Delta_{LPTokens} = \Gamma_{LPTokens} \frac{\Delta_A}{\Gamma_A - \Delta_A} = \Gamma_{LPTokens} \frac{\Delta_B-x}{\Gamma_B + x} $$ The final formula instead of **(3)** would be: $$ \Delta_{LPTokens} = \Gamma_{LPTokens} \frac{\Delta_B-x}{\Gamma_B + x} = \Gamma_{LPTokens} \frac{ \frac{\Delta_B}{\Gamma_B} - (\sqrt{ \left( \frac{1-TFee/2}{1-TFee} \right) ^2 + \frac{\Delta_B}{\Gamma_B(1-TFee)}} - \frac{1-TFee/2}{1-TFee} ) }{1 + {\sqrt{ \left( \frac{1-TFee/2}{1-TFee} \right) ^2 + \frac{\Delta_B}{\Gamma_B(1-TFee)}} - \frac{1-TFee/2}{1-TFee} } } $$

Open the report
MediumSingle asset withdrawal is unfair for usersXRPL AMM · CertiK · 21 Jun 2023Fixed

It is possible to perform a single asset withdrawal via `AMMWithdraw(tfSingleAsset)`. However, the formulas [(7) and (8)](https://github.com/XRPLF/XRPL-Standards/discussions/78) are inaccurate. It is more advantageous for the user to perform `AMMWithdraw(tfLPToken)` and then swap the redundant token. We propose to follow this logic: 1. Our goal is to get $$ \Delta_B = b_{withdrawn} + b_{swapped} $$ 2. Assuming we first withdraw $$ x $$ fraction of $$ \Gamma_{LPTokens} $$ 3. That gives $$ (x \Gamma_A, x\Gamma_B) $$ and pool in state $$ ((1-x)\Gamma_A, (1-x)\Gamma_B) $$ 4. Using **(9)** we can swap $$ x\Gamma_A $$ of AssetA for $$ b_{swapped} = \Gamma'_B \frac{x\Gamma_A(1-TFee)}{\Gamma'_A + x\Gamma_A(1-TFee)} $$ of AssetB 5. Using $$ \Delta_B = b_{withdrawn}+ b_{swapped} = x\Gamma_B + \Gamma'_B \frac{x\Gamma_A(1-TFee)}{\Gamma'_A + x\Gamma_A(1-TFee)} $$ we get $$ \Delta_B - x\Gamma_B = (1-x)\Gamma_B \frac{x\Gamma_A(1-TFee)}{(1-x)\Gamma_A + x\Gamma_A(1-TFee)} $$ 6. Simplifying that we get $$ (\frac{\Delta_B}{\Gamma_B} - x)(1-x + x(1-TFee)) = (1-x)x(1-TFee)$$ 7. Which gives $$ x^2 + x(TFee - 2 - \frac{\Delta_B}{\Gamma_B}Tfee) + \frac{\Delta_B}{\Gamma_B} = 0 $$ 8. Updated **(7')** is $$ \frac{\Delta_{LPTokens}}{\Gamma_{LPTokens}}= x = \frac{ \frac{\Delta_B}{\Gamma_B}Tfee + 2 - TFee - \sqrt{(\frac{\Delta_B}{\Gamma_B}Tfee + 2 - TFee)^2 - 4\frac{\Delta_B}{\Gamma_B}}}{2} $$ With current formulas, users should never `AMMWithdraw(tfSingleAsset)` to prevent extra spending.

Open the report
MediumZero `MinSlotPrice` motivates participant to do nXRPL AMM · CertiK · 21 Jun 2023Fixed

`MinSlotPrice` is set to zero. So, if `bidMin` is not specified, `sfPrice` is also set to zero according to the formula. There is no motivation for arbitrageurs to raise the price. They can always buy a slot for zero, perform the arbitrage and give up the slot to the next participant for free.

Open the report
LowPotential overflow in `cleanupOnAccountDelete()`XRPL AMM Addendum · CertiK · 15 Aug 2023Fixed

```cpp=1567 if (maxNodesToDelete && ++deleted > *maxNodesToDelete) ``` `maxNodesToDelete` is `std::optional<uint16_t>`, `deleted` is `uint16_t`. If `maxNodesToDelete == 65535` and there are enough entries in the account directory, the `deleted` will overflow and the code will not work as expected. `maxDeletableAMMTrustLines` passed to `cleanupOnAccountDelete()` is `size_t` set `512`. The ledger allows more than 65535 entries in the account directory according to `View.cpp::confineOwnerCount()`.

Open the report
LowTyposXRPL AMM Addendum · CertiK · 15 Aug 2023Fixed

There are typos in the code: - "truslines" is supposed to be "trustlines" - "view" is supposed to be "View"

Open the report
Show 10 more
LowUsage of Magic NumbersXRPL AMM Addendum · CertiK · 15 Aug 2023Fixed

Magic numbers are used as-is in code.

Open the report
Low`asset_frozen` and `asset2_frozen` are `out` fielXRPL AMM · CertiK · 21 Jun 2023Fixed

```h=168 JSS(asset_frozen); // in: amm_info JSS(asset2_frozen); // in: amm_info ``` `asset_frozen` and `asset2_frozen` fields are described as `in`, however, they are in fact `out`. `EPrice` is used not only by `AMMDeposit`, but also by `AMMWithdraw`.

Open the report
Low`bidMin == bidMax` is forbiddenXRPL AMM · CertiK · 21 Jun 2023Fixed

```cpp=151 if (bidMin && bidMax && bidMin >= bidMax) ``` It is disallowed to have `bidMin == bidMax` in `AMMBid::preclaim()`, however, that is a valid scenario: the user wants to pay exactly `bidMin`, not more.

Open the report
Low`invalidAMMAmount()` argument `nonNegative` nameXRPL AMM · CertiK · 21 Jun 2023Fixed

`invalidAMMAmount()` has the argument `nonNegative` which is `false` by default. The comment states: ```cpp=60 * If zero is false and amount is beast::zero then invalid amount. ``` The code is: ```cpp=101 if (!nonNegative && amount <= beast::zero) return temBAD_AMOUNT; return tesSUCCESS; ``` As a result, the meaning of `!nonNegative` is `mustBePositive`. However, in math "nonNegative" means "zero or positive". Providing `true` as an argument to the function allows the `amount` to be zero. Using negations in bool variable names and negation of the variable itself decreases the code readability.

Open the report
Low`multiply()` is not usedXRPL AMM · CertiK · 21 Jun 2023Fixed

```cpp=652 auto const amount2Withdraw = amount2Balance * frac; ``` `multiply(STAmount, STAmount, Issue)` performs some checks and Number normalization, however, it is not used in `AMMWithdraw::equalWithdrawTokens()`.

Open the report
Low`sfDiscountedFee` is `UINT32`XRPL AMM · CertiK · 21 Jun 2023Fixed

`sfDiscountedFee` is stored as `UINT32`, but `sfTradingFee` as `UINT16`. Discounted fee is supposed to be less or equal to normal trading fee. It is unclear why bigger type used.

Open the report
Low`sfVoteSlots` not updated if not enough LPTokensXRPL AMM · CertiK · 21 Jun 2023Fixed

The TradingFee of the AMM instance is a votable parameter. Any account that holds the corresponding LPTokens can cast a vote using the new `AMMVote` transaction. During `AMMVote` processing the weights of all the votes are recomputed, and the TradingFee for the AMM instance is updated. Up to 8 voting slots supported. However, `sfVoteSlots` is only updated if a voter is part of it. Consider the scenario: - The Griefer obtains a big amount of LPTokens via `AMMDeposit`. - The Griefer separates the amount into 8 equal parts, distributes it among 8 accounts, and performs `AMMVote(1%)` from each. - All 8 `sfVoteSlots` are now occupied by an account with a significant weight. - The Griefer combines the LPTokens and performs `AMMWithdraw`. - As a result, **no one can refresh the votes array** until they get a significant LPTokens balance.

Open the report
Low`std::chrono` is not used in `ammAuctionTimeSlot(XRPL AMM · CertiK · 21 Jun 2023Fixed

`std::chrono` is not used in `ammAuctionTimeSlot()`.

Open the report
Low`tFee` is declared as `uint32_t`XRPL AMM · CertiK · 21 Jun 2023Fixed

In `changeSpotPriceQuality()` and `withdrawByTokens()` the argument `tFee` is declared as `uint32_t`. In other code it is `uint16_t`.

Open the report
Low`tfWithdrawAll` is used in `AMMDeposit::preclaim(XRPL AMM · CertiK · 21 Jun 2023Fixed

`tfWithdrawAll` flag is checked in `AMMDeposit::preclaim()`, however, it can't be used with `AMMDeposit`.

Open the report
Show 10 more
LowAuction price decay is negligibleXRPL AMM · CertiK · 21 Jun 2023Fixed

The current slot price is calculated by the formula: $$ f(t) = B * 1.05*(1-t^{60}) + M $$ According to the description: "The slot price approaches `M` as the slot approaches expiration (i.e., as `t` approaches 1)." However, 1. During the first 18 slots the price drops gradually from $$ \approx B * 1.05 + M $$ (with `t = 0.05`) to $$ \approx B * 1.048 + M $$ (with `t = 0.90`) 2. Then drops significantly to $$ \approx B + M $$ (with `t = 0.95`) 3. And then drops to $$ M $$, which is currently set to zero So, the speed of descent is highly uneven.

Open the report
LowCode can be simplifiedXRPL AMM · CertiK · 21 Jun 2023Fixed

```cpp=192 return (xrpLiquid(ctx.view, accountID, !sle, ctx.j) >= deposit) ? TER(tesSUCCESS) : (!sle ? tecINSUF_RESERVE_LINE : tecAMM_UNFUNDED); ``` Expressions with ternary conditional operators are harder to read. The expression can be simplified using regular `if`s and `return`.

Open the report
LowInaccurate commentsXRPL AMM · CertiK · 21 Jun 2023Fixed

```cpp=122 // Returns the amount an account can spend of the currency type saDefault, or // returns saDefault if this account is the issuer of the the currency in // question. Should be used in favor of accountHolds when questioning how much // an account can spend while also allowing currency issuers to spend // unlimited amounts of their own currency (since they can always issue more). [[nodiscard]] STAmount accountHolds( ReadView const& view, ``` The comment describes `accountFunds()` function, not `accountHolds()`. ```cpp=38 * specified by Asset1In. This is essentially an equal asset deposit * and a swap. ``` In fact, that is a swap and a deposit. ```cpp=48 * a. amount of asset1 if specified in Asset1In specifies the maximum * amount of asset1 that the trader is willing to deposit ``` `amount` is always specified in this mode, however, it can be zero. ```cpp=80 /** Attempt to create the AMM instance. */ ``` The same comment is used in `AMMWithdraw.h`, `AMMDeposit.h`, and other files. It should be removed or updated. ```cpp=535 * Use equation 1 to compute the amount of , given the amount in Asset1In. ``` `t` is missing. ```cpp=153 * @param ctx ``` There is no argument `ctx`.

Open the report
LowInaccurate error messageXRPL AMM · CertiK · 21 Jun 2023Fixed

```cpp=497 JLOG(j.fatal()) << "Invariant failed: account root created " "by a non-Payment or by an unsuccessful transaction"; ``` `ValidNewAccountRoot::finalize()` error message assumes the account can only be created by `ttPAYMENT` transaction. However, `ttAMM_CREATE` should also be mentioned. ```cpp=309 JLOG(j_.debug()) << "AMM Instance: success " << *ammAccount << " " << ammKeylet.key << " " << lptIss << " " << amount << " " << amount2; auto addOrderBook = ``` Using `lpTokens` instead of `lptIss` is preferable. ```cpp=245 << "AMM Bid: not in range " << computedPrice << *bidMin << " " ``` " " is absent between values.

Open the report
LowLocal Variable ShadowingXRPL AMM · CertiK · 21 Jun 2023Fixed

```cpp=567 auto const [withdrawAmount, withdraw2Amount] = getAmounts(frac); ``` ```cpp=577 auto const [withdrawAmount, withdraw2Amount] = getAmounts(frac); ``` `withdrawAmount` and `withdraw2Amount` are shadowed by the local variable with the same names.

Open the report
LowMisleading `time_interval` returned if auction haXRPL AMM · CertiK · 21 Jun 2023Fixed

```cpp=153 auto const timeSlot = ammAuctionTimeSlot( ledger->info().parentCloseTime.time_since_epoch().count(), auctionSlot); auction[jss::time_interval] = timeSlot ? *timeSlot : 0; ``` `ammAuctionTimeSlot()` returns 0-19 in case the auction has not yet expired and `std::nullopt` otherwise. In case of expired `auction[jss::time_interval]` is set 0. The same value is set during the first 1.2 hours. That could be misleading.

Open the report
LowRounding error is not handled during auction slotXRPL AMM · CertiK · 21 Jun 2023Fixed

```cpp=218 auto res = redeemIOU(sb, account_, saBurn, lpTokens.issue(), ctx_.journal); if (res != tesSUCCESS) { JLOG(ctx_.journal.debug()) << "AMM Bid: failed to redeem."; return res; } ammSle->setFieldAmount(sfLPTokenBalance, lptAMMBalance - saBurn); ``` `saBurn` LPTokens are redeemed from `account_` and then `sfLPTokenBalance` is reduced. However, these values can be different due to rounding error.

Open the report
LowShort initializer list in `generateFibSeqOffer()`XRPL AMM · CertiK · 21 Jun 2023Fixed

```cpp=73 constexpr std::uint32_t fib[AMMContext::MaxIterations] = { 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811, 514229, 832040}; ``` `fib[29]` is zero because the initializer list has only 29 elements. This can be unexpected.

Open the report
LowThe slot owner always looses money if outbid immeXRPL AMM · CertiK · 21 Jun 2023Fixed

```cpp=296 // Refund the previous owner. If the time slot is 0 then // the owner is refunded full amount. auto const refund = fractionRemaining * pricePurchased; if (refund > *payPrice) ``` Despite the comment states that the owner is refunded full amount during slot 0, in fact, `fractionRemaining` is maximum 0.95 and the owner always looses money even if outbid in the first second.

Open the report
LowUsage of Magic NumbersXRPL AMM · CertiK · 21 Jun 2023Fixed

Different magic numbers are used as-is in code.

Open the report
Show 2 more
LowUser has to `AMMVote` based on other votesXRPL AMM · CertiK · 21 Jun 2023Fixed

The TradingFee of the AMM instance is a votable parameter. Any account that holds the corresponding LPTokens can cast a vote using the new `AMMVote` transaction. During `AMMVote` processing the weights of all the votes are recomputed, and the TradingFee for the AMM instance is updated. Up to 8 voting slots supported. The voter doesn't know what `sfTradingFee` to set. Consider the scenario: - Imagine that a Voter with weight 10 wants to set the fee to 0.5%. - At that moment all other voters with a sum weight of 10 voted for a 1% fee. - So, Voter has to submit `AMMVote(0%)` to get the desired 0.5%. - After some time other voters change the desired fee to 0%. - The Voter has to re-submit `AMMVote(1%)` to get the desired 0.5%. - As a result, the voter **can't set the desired fee** but rather sets the value that balances the other votes instead. - This can lead to more `AMMVote` submitted than expected.

Open the report
LowWrong argument namesXRPL AMM · CertiK · 21 Jun 2023Fixed

The argument `deposit` of `AMMDeposit::deposit()` should be named `view`. `lptTokensAMMBalance` should be named `lptAMMBalance`.

Open the report

Contracts

No deployed address is recorded for these audits.

The reports name a repository or files. Open a report to see its scope.

Show this on your site

Is this your project? Show this record on your own website. The label states what the record holds, links to this page and updates by itself. It is free and needs no sign-in.

Content

Facts

Automatic: the three strongest facts of this project’s own record (its last audit and two more), highlighted. They update with the record. Picking a fact turns Automatic off.

Fixed as
Language

Style

Layout
Theme
Accent
Corners

Format

Size
Date
Embed as
HTML
<head>
<script src="https://widget.trustblock.run/project-widget.min.js" defer></script>
</head>
<body>
<div
data-tb-project-widget
data-tb-project-slug="xrp-ledger"
></div>
</body>

Paste the code where the label should appear. It uses no API credit and updates by itself.

Something wrong on this page? A missing report, a wrong date, a finding that was fixed since. Tell us and we check it against its source.

Tell us