11/06/2021: Post-mortem of nUSD Metapool Exploit
On 11/06/2021, an attacker manipulated the nUSD Metapool virtual price, lowering it by approximately 12.5%. Ultimately, while no funds were lost, there was a contract bug in the Saddle implementation of Metapools which the Synapse AMM uses. This was not related to any core Synapse bridging contracts, and was confined to an AMM issue using contracts forked from Saddle.
What Metapools are
In the stableswap implementation developed by Curve, there are two kinds of pools:
- Standard stableswap pools — which contain multiple base assets(Asset A, B, C), priced against each other in a tight range, as described here
- Metapools — which implements the same stableswap invariant, but which prices Asset D against the LP token of Asset A, B, C. This results in the ideal balance weight of the pool being 50% Asset D, 16.66% of A, B, C. This both isolates risk of Asset D while allowing the base pool to be used for additional metapools. While this has a number of upsides, it’s also a much more complex implementation.
What was the implementation issue?
While most users directly swapped from Asset D to A, B, or C, when swapping to the LP token of A,B C, the Saddle Metaswap contract left out a check to calculate the base virtual price of the LP token. See line 424 of the Metaswap contract. Compare this to line 277, which correctly implements it, as adding liquidity & removing one token from the pool is effectively the same as a swap. This correctly scales the liquidity removal by the base virtual price. This is also correctly implemented in swapUnderlying().
The omission of the check described above allowed address 0x3AB92d06F5f2A33D8F45F836607F8Da68CAb81e8 to loop through transactions continuously, lowering the Metapool virtual price and draining funds from LPs. A secondary address 0xb9...2415 linked to 0x38…81e8 performed transaction loops at a smaller scale.
Timeline of events
- 11/06 18:09 UTC: an Avalanche LP reported a partial loss of assets upon removing liquidity to the Discord support channel
- 18:14 UTC: A community member, who has been working on Synapse data infrastructure, notices the Avalanche nUSD virtual price at 0.887, and tags Aurelius.
- 18:18 UTC: Aurelius responds that he is looking into this user report.
- 18:22 UTC: Avalanche nUSD Metapool is paused immediately upon noticing abnormal activity. Investigation by core contributors continues.
- 18:58 UTC: Metapools on all 6 chains are paused and the AMM + bridge pause is announced on Discord. Validators coordinate to go offline to protect the network against further malicious activity.
- 18:40–19:00 UTC: Synapse core contributors reach out to Saddle, who developed the Metaswap contract, to set up a working group to identify the bug and evaluate the severity of the issue. Synapse contributors recommend Saddle initiate a pause on their deployed Metapool.
- 19:00 UTC — 20:40 UTC: Synapse contributors identify exploiter addresses, and begin to look through each transaction and identify a pattern flow of the exploiter:
Transaction 1: nUSD -> USDC through Firebird Router, calls swap(), removeLiquidityOneToken() on Metaswap.sol
Transaction 2: USDC -> nUSD-LP through MetaswapDeposit -> addLiquidity() Transaction 3: nUSD-LP -> nUSD through MetaswapDeposit removeLiquidityOneToken()
- 21:34 UTC: Contract bug is identified (line 424 of MetaSwapUtils.sol, as described above). Preliminary findings are shared with Saddle. Saddle confirms the findings are likely correct.
- 22:20 UTC: Synapse core contributors decide to move forward with deploying new Swap.sol pools containing Assets A, B, C, and D, all in one pool, and migrate liquidity to the new pools once deployed.
- 11/06 22:20–11/07 03:00 UTC: New pools are re-deployed across all chains
- 03:00 UTC — 07:00 UTC: Plan of action for liquidity migration is created, UI work is done to support new pools and simple liquidity migration.
- 07:00 UTC — 12:00 UTC: Contract reviews are completed, UI is being tested on each chain. Upon new contracts being finalized, validators coordinate to come back online, and bridge returns to fully functioning. All pending user transactions are processed.
- 12:50 UTC: A brief summary of the past 16 hours, with explanation of the contract bug, and effects on LPs is announced on Discord and Telegram. Bridge is fully functioning, liquidity migration plan is announced.
Distribution of funds to Avalanche LPs
While funds were drained from the metapool itself, the funds were not lost. The address which drained funds from LPs attempted to move funds through the bridge while the validators were offline, and as such the transaction was not yet processed. However, the validators, by consensus, had elected to not process this transaction as it was malicious against LPs and the network as a whole: ~$8.2m nUSD was therefore not been minted to the attacker’s address on the destination chain. This nUSD will instead be returned to affected Avalanche LPs.
All Avalanche nUSD LPs will be made whole, with no funds lost. Details for this distribution will be shared soon. A snapshot of all Avalanche LPs addresses & balances has been taken both prior to the incident and at the time of the pause.
What comes next
- Protocol development and growth remains unobstructed.
- Liquidity migration to new pools is ongoing: Over $200mm TVL has migrated
- Avalanche nUSD Metapool LPs will have funds returned to them over the coming days.
- Audits of all Synapse contracts will be conducted by multiple audit firms on a continuous basis.
- A formal bug bounty program will be created.
- Security remains the #1 priority of all core Synapse contributors.