Author: Alexey Malanov, Principal Blockchain Security Expert at CertiK
Introduction
CertiK recently conducted an extensive thirdparty audit of an Automated Market Maker (AMM) built on the XRP Ledger. In this technical blogpost, we will present some of the key findings from our audit and discuss how Ripple and CertiK collaborated to address these findings. Our objective is to provide valuable insights to current and prospective builders of other AMMs, as well as the developers, users, and stakeholders of the XRP Ledger AMM.
Total Findings: 31
0 Critical, 0 Major, 8 Medium, 12 Minor, 11 Informational
29 Findings Resolved, 2 Minor Issues Acknowledged
About the XRP Ledger AMM
Ripple recognized the need for a noncustodial automated market maker (AMM) to add flexibility to the XRP Ledger. Ripple has chosen the most popular approach $\Gamma_A * \Gamma_B = k$ , that means that the product of asset reserves (pool balances) is preserved by swap operations.
For example, doubling one reserve in a pool via swap, leads to halving of another one.
In general:
Opening brackets we get:
where,
$\Gamma_A$
: Reserve of asset A
$\Gamma_B$
: Reserve of asset B
$\Delta_A$
: The amount of asset A that comes into the pool
$\Delta_B$
: The amount of asset B that comes out in return
Taking into account the
$TFee$
taken from the incoming amount, from (2) we get the main formula of the swap:
It is also possible to deposit and withdraw liquidity proportionally to current reserves:
If:
Then the user gets:
where,
$\Gamma_{LPTokens}$
: Supply of LPTokens before the swap. Initially set as
$\Gamma_{LPTokensInitial} = \sqrt{\Gamma_A \Gamma_B}$
with the first deposit (pool creation).
$\Delta_{LPTokens}$
: LPTokens the user gets for the deposit of
$(\Delta_A,\Delta_B)$
.
For example, if the user doubles both asset reserves, they will get 100% of original $\Gamma_{LPTokens}$ , that is 50% of the final total.
Detailed Description and Implementation
Ripple has provided a detailed description of the features in the AMM, which can be found in their repository here.
APP01 and APP02: Suboptimal Single Asset Deposit Calculations – Severity: Medium – Status: Resolved
The XRP Ledger AMM allows users to perform a single asset deposit with a AMMDeposit(tfSingleAsset)
transaction. This means that some part of the deposit is virtually swapped into another asset and then the usual twoasset deposit is performed. However, our audit revealed that the formulas used by Ripple for single asset deposits had some miscalibrations. Fees were always taken for the half of the deposited asset, and pool value change due to fees was not accounted for. The variable amount should be virtually swapped instead with the corresponding fee taken. As a result, users would benefit from using AMMDeposit(tfSingleAsset)
followed by AMMWithdraw(tfLPToken)
instead of a normal swap, as it would save on fees.
Perfect Single Asset Deposits
While the single asset deposit feature is not commonly described or implemented in traditional AMMs, it holds significant utility. Let's discuss the proposed logic for a perfect single asset deposit:
1. We want to split the amount $\Delta_B$ into two parts: $x + (\Delta_B  x)$ and swap $x$ for $\Delta_A = \Gamma_A \frac{x(1TFee)}{\Gamma_B + x(1TFee)}$ (equation (3)).
2. We need to determine $x$ such that the final pool reserves ratio $\frac{\Gamma_A}{\Gamma_B+\Delta_B}$ is equal to both the ratio of assets deposited $\frac{\Delta_A}{\Delta_B  x}$ and the pool reserves ratio after the swap $\frac{\Gamma_A  \Delta_A}{\Gamma_B + x}$ (equation (4)).
3. Using the equation $\frac{\Gamma_A}{\Gamma_B + \Delta_B} = \frac{\Delta_A}{\Delta_Bx}$ and substituting $\Delta_A$ , we get the equation $(\Delta_B  x)(\Gamma_B + x(1TFee)) = (\Gamma_B + \Delta_B)x(1TFee)$ .
4. Solving this equation gives us $x = \Gamma_B \left(\sqrt{\left(\frac{1TFee/2}{1TFee}\right)^2 + \frac{\Delta_B}{\Gamma_B(1TFee)}}  \frac{1TFee/2}{1TFee}\right)$ .
5. After swapping $x$ of AssetB for $\Delta_A$ of AssetA, we perform the complete assets deposit with $\Delta_{LPTokens} = \Gamma_{LPTokens} \frac{\Delta_A}{\Gamma_A  \Delta_A} = \Gamma_{LPTokens} \frac{\Delta_Bx}{\Gamma_B + x}$ (equation (5)).
The final formula for the perfect single asset deposit is:
Example
Let's consider an example with a significant
$TFee$
.
Let
$\Delta_B = \Gamma_B$
,
$TFee = 50\%$
.
The formula used by the XRP Ledger AMM gave $\frac{\Delta_{LPTokens}}{\Gamma_{LPTokens}} = \sqrt{ 1+\frac{\Delta_B(1 TFee/2) }{\Gamma_B} }  1 = \sqrt{1+10.25}  1 \approx 32.3\%$ of LPTokens.
The proposed approach recommends:
swapping $\frac{x}{\Gamma_B} = \sqrt{ \frac{10.25}{10.5}^2 + \frac{1}{10.5} }  \frac{10.25}{10.5} = \sqrt{1.5^2 + 2}  1.5 \approx 56.2\%$ of AssetB
for $\frac{\Delta_A}{\Gamma_A} = \frac{\Delta_B 0.5}{\Gamma_B + \Delta_B 0.5} \approx \frac{56.2\%/2}{100\% + 56.2\%/2} \approx 21.9\%$ of AssetA
then depositing $(21.9\%, 43.8\%)$ into the pool with $(78.1\%, 156.2\%)$ gives $\frac{\Delta_{LPTokens}}{\Gamma_{LPTokens}} = \frac{\Delta_Bx}{\Gamma_B+x} \approx \frac{43.8\%}{156.2\%} \approx 28\%$ of LPTokens.
In other words, a single asset deposit (followed by immediate twoasset withdrawal) is more advantageous than a normal swap and would be preferred by users.
There is no difference with zero
$TFee$
.
Let
$\Delta_B = \Gamma_B$
,
$TFee = 0\%$
.
The formula used by the XRP Ledger AMM gave $\frac{\Delta_{LPTokens}}{\Gamma_{LPTokens}} = \sqrt{ 1 + \frac{\Delta_B}{\Gamma_B} }  1 = \sqrt{2}  1 \approx 41.4\%$
The proposed approach recommends:
swapping $\frac{x}{\Gamma_B} = \sqrt{ 1 + \frac{\Delta_B}{\Gamma_B} }  1 = \sqrt{2}  1 \approx 41.4\%$ of AssetB for $29.3\%$ of AssetA first
then depositing $(29.3\%, 58.6\%)$ into pool with $(70.7\%, 141.4\%)$
giving the pool in state $(100\%, 200\%)$ and $\frac{\Delta_{LPTokens}}{\Gamma_{LPTokens}} \approx \frac{58.6\%}{141.4\%} \approx 41.4\%$ of LPTokens  same amount
And with a reasonable
$TFee$
.
Let
$\Delta_B = \Gamma_B$
,
$TFee = 1\%$
.
The formula used by the XRP Ledger AMM gave $\frac{\Delta_{LPTokens}}{\Gamma_{LPTokens}} = \sqrt{ 1 + \frac{0.995\Delta_B}{\Gamma_B} }  1 = \sqrt{1.995}  1 \approx 41.244\%$
The proposed approach recommends:
swapping $\frac{x}{\Gamma_B} = \sqrt{ \left(\frac{0.995}{0.99}\right)^2 + \frac{\Delta_B}{0.99\Gamma_B} }  \frac{0.995}{0.99} \approx \sqrt{2.02}  1.005 \approx 41.63\%$ of AssetB for $29.2\%$ of AssetA first
then depositing $(29.2\%, 58.4\%)$ into the pool with $(70.8\%, 141.6\%)$
resulting in the pool state of $(100\%, 200\%)$ and $\frac{\Delta_{LPTokens}}{\Gamma_{LPTokens}} \approx \frac{58.4\%}{141.6\%} \approx 41.21\%$ of LPTokens.
In summary, with a reasonable $TFee$ , the difference in LPTokens between the two approaches is negligible (41.244% vs. 41.21%). Therefore, addressing this issue aims to provide consistency rather than substantial impact.
Additional Observations
We also observed that balancer.fi initially employed the same approach as Ripple in the XRP Ledger AMM.
Also CertiK regularly conducts audits on Solidity projects that perform single asset deposits via Uniswaplike AMMs. They do it the same wrong way we have just described: half of the asset is first swapped via the pool for ETH, then another half and swapped ETH are deposited via addLiquidityETH()
.
// swap tokens for ETH
swapTokensForEth(half);
// how much ETH did we just swap into?
uint256 newBalance = address(this).balance.sub(initialBalance);
// add liquidity to uniswap
addLiquidity(otherHalf, newBalance);
SafeMoon's implementation of swapAndLiquify()
Since during the first swap the pool reserve ratio has changed, "halves" no longer can be deposited completely, small share of ETH will be returned back by AMM. If the contract doesn't provide a way to withdraw those ETHs, they will be locked in the contract forever.
AMD01: Protection from FrontRunning – Severity: Medium – Status: Resolved
Our audit also highlighted the importance of protecting against frontrunning attacks targeting users of the XRP Ledger AMM. Consider a scenario where an attacker can steal the funds of a liquidity provider.
Let the pool have (1000 TokenA, 1000 TokenB) with a fair price 1:1. Let the trading fee be 0 for simplicity.
The Victim wants to
AMMDeposit(tfTwoAsset, 100 TokenA, 100 TokenB)
. They expect to get 10% of existing LPTokens.However, the Attaker swaps 9000 TokenB for 900 TokenA leaving the pool in state (100 TokenA, 10000 TokenB).
Since twoassets deposit must preserve the spot price (pool reserves ratio),
AMMDeposit
only takes (1 TokenA, 100 TokenB), moves the pool in the state (101 TokenA, 10100 TokenB), and gives 1% of LPTokens in return.The Attacker swaps back 909 TokenA for 9090 TokenB, leaving the pool in the state (1010 TokenA, 1010 TokenB) with the original price.
The Attacker gets
9090  9  9000 = 81
TokenB profit. That was effectively stolen from the Victim.The Victim gets 99 TokenA and 1% LPTokens instead of the expected 10% LPTokens.
This is a wellknown potential issue for all AMMs.
Other AMM implementations, such as Uniswap, allow the user to set the minimum amounts accepted:
function addLiquidity(
address tokenA,
address tokenB,
uint amountADesired,
uint amountBDesired,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
Uniswap’s declaration of addLiquidity()
.
If amountAMin
/amountBMin
conditions are not satisfied, the transaction is rejected. This effectively protects the user from frontrunning and sudden pool spotprice changes for other reasons.
The initial design of the AMM had no similar protection. AMMDeposit(tfTwoAsset)
allowed specifying amount
and amount2
, but they were used as maximum values consumed by the deposit operation.
Ripple heeded our advice and mitigated the issue as recommended.
AMV01 and AMV02: Voting on the Trading Fee – Severity: Minor – Status: Acknowledged
In the XRP Ledger AMM, the TradingFee parameter is subject to voting. Any account holding the corresponding LPTokens has the ability to cast a vote using the AMMVote
transaction. The voting power is determined by the balance of LPTokens held. During the processing of AMMVote
, the weights of all the votes are recalculated, and the TradingFee for the AMM instance is updated. The system remembers the votes of the top eight largest holders.
However, the voting mechanism can be susceptible to manipulation in a number of ways. Let's consider a few scenarios:

Exploiting Fees: A wellcapitalized actor can exploit the system by conducting trades without incurring fees. Here's an example:
 The "Griefer" holds significant balances of TokenA and TokenB but wishes to rebalance their portfolio.
 The Griefer calls
AMMDeposit(tfTwoAsset)
with substantial amounts.  Leveraging their significant LPTokens balance, the Griefer submits
AMMVote(0%)
to minimize the impact of fees.  The Griefer executes the desired swap operation with minimal fees.
 Finally, the Griefer performs
AMMWithdraw(tfTwoAsset)
to retrieve their tokens, effectively utilizing quick liquidity deposits to avoid or minimize fees.

Vote Manipulation: A wellcapitalized actor can exert influence over the voting process to the detriment of other voters. Let's examine a scenario:
 Suppose the Griefer has a weight of 81, while other traders have weights of 10 each, totaling 70.
 According to the distribution, the Griefer should have only 54% voting power.
 However, by dividing their LPTokens equally among 8 accounts and casting votes from each account, the Griefer can push out other voters, obtaining 100% voting power.
 Once the Griefer has achieved this, they can consolidate the LPTokens back into a single account.

Voting Slot Occupation: Voting slots are only updated if a voter becomes an active participant. Here's a scenario illustrating this:
 The Griefer obtains a significant amount of LPTokens through
AMMDeposit
.  They divide the LPTokens equally into 8 parts, distributing them among 8 accounts, and cast
AMMVote(maximum fee)
from each account.  As a result, all 8 voting slots are now occupied by accounts with substantial weights.
 Afterward, the Griefer combines the LPTokens and performs
AMMWithdraw
, retrieving their funds.  Consequently, until someone obtains a significant LPTokens balance, no one can refresh the votes array.
 The Griefer obtains a significant amount of LPTokens through

Determining the final fee is achieved by calculating a weighted average of all the votes. However, this can lead to a situation where voters struggle to set their desired fee value due to the influence of other voters. For instance:
 Suppose a voter with a weight of 10 wishes to set the fee to 0.5%.
 At that moment, other voters with a cumulative weight of 10 have voted for a 1% fee.
 In order to attain the desired 0.5% fee, the voter is compelled to submit
AMMVote(0%)
.  If, over time, other voters change their desired fee to 0%, the voter must resubmit
AMMVote(1%)
to maintain the desired 0.5% fee.  As a result, the voter cannot directly set the desired fee but must instead strike a balance that accommodates the preferences of other voters. This dynamic can be likened to a tug of war game.
Despite these strategies enabling users to utilize the voting feature in ways that were not originally intended, we have consulted with Ripple and decided against introducing additional constraints or altering the design. Among the scenarios mentioned, only the third one could be addressed without significant modifications, and appropriate measures were taken.
Nevertheless, analyzing potential strategies and scenarios is crucial for conducting a comprehensive risk assessment of the system.
Conclusion
The audit of the XRP Ledger AMM revealed findings specific to the project, with varying levels of severity. Of 32 identified issues, 30 were resolved as recommended, and two minor issues were acknowledged. It is common for large projects to have some implementation quirks that can be refined. Our collaboration with Ripple ensured that these findings were thoroughly evaluated and appropriate actions were taken to improve the overall security, consistency, and user experience of the XRP Ledger AMM.
By presenting these findings and the collaborative efforts between Ripple and CertiK, we aim to provide valuable insights to developers and stakeholders, enhancing the understanding of the XRP Ledger AMM's inner workings and promoting its continuous improvement.
Disclaimer: The findings and recommendations in this blogpost are based on the audit conducted by CertiK at the time of the evaluation. As technology evolves, it is essential to keep pace with updates and advancements in the field. We encourage ongoing security assessments and collaborations between project teams and security experts to ensure the highest level of security for blockchainbased systems.
Top comments (0)