Pay $250k Bug Bounty to f(x) Protocol for Discovery of Curve Swap Router Bug

Summary

On April 30, 2024, an f(x) user lost ~$725,000 from slippage while swapping stETH to fxUSD because of incorrect price quoting on the f(x) swap UI. The UI uses the Curve swap router as part of its determination of the optimal swap path. The Curve router API delivered a price quote that differed from the execution price. The bug has been corrected and the user compensated in full from the f(x) treasury.

Curve Grants intends to pay a bounty to f(x) for the discovery of a bug associated with this incident for $250,000 worth of CRV, the maximum bounty size offered by Curve. Payment will be made to the f(x) treasury.

The Incident

On April 30, 2024, a user placed a swap order of 314.89 stETH for fxUSD on the f(x) swap UI. The UI compared rates between the protocol direct minting, DEX aggregators, and Curve to offer the best rate. The open source Curve SDK was used to route swaps on Curve. The user was quoted a favorable rate when routing through Curve (as quoted by the f(x) swap interface):

However, the actual rate at the time had very high slippage through Curve, which was correctly displaying high slippage on the Curve UI:

The affect user executed this tx that resulted in a loss of $726,039 compared to the quoted price by the f(x) swap interface.

The trade routed through

  1. Curve stETH/ETH
  2. Curve LLAMMA crvUSD/WETH
  3. Curve crvUSD/fxUSD


Source: BlockSec Explorer

The user received 221,448.52 fxUSD.

f(x) Response

The f(x) team responded to the incident by compensating the affected user in full from their treasury. The user was compensated 726,039 fxUSD in this tx.

They f(x) team coordinated with the Curve team to identify the cause of the issue. An announcement about the incident was posted to the AladdinDAO Dicord channel.

Source of the Problem

f(x) had been using Curve-js version 2.46.4 whereas the Curve UI uses the latest version (version 2.57.3 at the time of the incident). There had been a substantial update in v2.47.0 and v2.48.0 that changed the behavior of the respective routers. This versioning mismatch was responsible for incorrect router behavior on the f(x) swap interface and explains why the Curve UI was giving a different quote. Curve recommends anyone integrating Curve-js to always use the latest version available.

Note that f(x) had previously contacted Curve while attempting to upgrade the router version to the latest version just two weeks before the incident. Their team had been experiencing issues with upgrading successfully, and unfortunately were not able to successfully upgrade before this incident occurred.

Additional Bug Discovery

Investigation into the incident revealed a bug existing in the most recent version of the Curve router. It had not caused any user losses but was, nonetheless, considered a risk that required patching.

In the most recent router implementation, it still might have been possible that the route could change between the quote and the swap. (Note that this is a possible, albeit very unlikely scenario.) As seen below, getBestRouteAndOutput uses a 5 minute cache for route and a 15 second cache for output. If the cache were to expire, it could happen that the route or output would update during the swap.


Source: Curve-js router.ts

The caching has been modified to take the route and output from permanent cache. Permanent cache is now filling in the getBestRouteAndOutput method.


Source: Curve-js router.ts

This update in the most recent version of Curve-js (v2.57.5), patched as of commit 32e9905, resolved this unlikely possibility that the router quote updates mid-execution.

Takeaway

Curve makes all possible efforts to maintain its open source services, although these repositories are available “as is”, and Curve cannot guarantee the performance of this software for use by third part integrators. Wherever possible, integrators should use the latest version of Curve software and consult with the Curve team on integrations.

However, this incident revealed a potential issue that could affect users by executing a swap at a different rate from the quote given by the router. This was considered a low probability but high impact risk. Therefore, Curve Grants intends to award f(x) with a bug bounty worth $250,000 in CRV for discovering the issue. Curve Grants intends to send the amount to the f(x) treasury.

f(x) Treasury Multisig: 0x26B2ec4E02ebe2F54583af25b647b1D619e67BbF

Address Confirmation: deployments/deployments.mainnet.md at main · AladdinDAO/deployments · GitHub

4 Likes