Duet Protocol Price Oracle Manipulation Bugfix Review

Narya.ai
6 min readApr 11, 2022

--

TL;DR

In late March, our whitehats i2huer (Purdue University) and 0xtarafans from Pwned No More (PNM) DAO submitted a critical vulnerability with a working PoC to Duet Protocol’s bug bounty program. The attack is enabled by price oracle manipulation and allows the attacker to borrow out high-value assets with few collaterals, rendering its synthetic assets market at risk. For this finding, we were rewarded $50,000. The Duet team is highly responsive with the bugfix and also with the payout of the bounty.

Background

You may hear many DeFi security researchers and developers blame the price oracle manipulation attack, one of the most notorious hacks in cryptocurrency history. You may also wonder how popular such a hack is nowadays in 2022. Unfortunately, despite its infamousness, price oracle manipulation is still prevalent in the cryptocurrency market and, no doubt, extremely painful. For example, on March 15, 2022, Deus Finance, a DeFi protocol on Fantom was hacked with a loss of $3M, exploited via a flashloan-enabled price oracle manipulation.

To better understand the price oracle manipulation attack, we are going to start with three related concepts, automated market makers (AMM), price oracles, and flashloan.

Automated Market Maker (AMM)

AMM protocols are a series of smart contracts that allow users to swap one token for another. Without any centralized exchange center or other intermediaries, AMM protocols decide the price of a token according to a mathematical formula. In the case of PancakeSwap (the one involved in Duet’s bug), the formula is a constant function:

x and y denote the amounts of token0 and token1 in the AMM, respectively. Note that, for PancakeSwap, each market, namely pool, consists of two tokens, usually referred to as token0 and token1.

Liquidity providers (LP) can deposit both token0 and token1 into a target pool, to increase the pool’s liquidity. A liquidity token, namely an LP token, represents the shares of the pooled liquidity. Thus, we can think of each LP token as a share unit in the pool.

Price Oracle

Price oracles are those smart contract functions used to estimate the value of given assets. There are two types of price oracles, including on-chain and off-chain oracles. On-chain oracles are usually implemented by querying the price from AMMs, while off-chain oracles are data feeds from an off-chain decentralized network. Chainlink is known to be one of the most reliable off-chain price providers.

Flashloan

Flashloan is a new lending model allowing users to borrow a considerable amount of assets without pre-collateralization. It is enabled by the atomicity of on-chain transactions and is commonly supported by mainstream AMMs, e.g., PancakeSwap.

Specifically, PancakeSwap’s flashloan function is organized as first lending the requested amount of loan, invoking user-provided callback functions, and last checking whether the loan is repaid in the callback functions. Arbitrage can be achieved with borrowed assets in the user-provided callback functions. Note that the transaction would be reverted if the repayment was not accomplished. Since the transaction has to be atomic, AMMs would not suffer from any loss.

Vulnerability Analysis

Duet protocol (Duet) is a multi-chain synthetic asset protocol that aims to bring various assets for use in the blockchain world.

  • It treats single assets such as BTC/USDT/DAI and receipt tokens such as WBNB-BUSD LP of PancakeSwap as collaterals, and credits users with synthetic stable coin dUSD and other synthetic assets.
  • By design, the value of a lended synthetic token should be lower than the one of user’s collaterals.

Price Oracle of LP Tokens

Duet contracts use the following price oracle to estimate the value of the given _amount of LP tokens.

Line-by-line code explanation is as follows.

The oracle first queries lpSupply, the total supply (i.e., total liquidity) of the given PancakeSwap pool pair.

By invoking IPair(pair).getReserves, the oracle gets reserve0 and reserve1, denoting the current reserves of tokne0 and token1 in the pool, respectively. amount is the amount of the collateralized LP tokens.

Recall that LP tokens represent the shares of the pooled liquidity. The code hence derives amount / lpSupply , amount0, and amount1 to denote the portion of pooled liquidity, the amount of token0, and the amount of token1, held by the collateralized LP tokens, respectively.

The prices of token0 and token1 are acquired from the Chainlink’s off-chain price oracles, where oracle0 (oracle1) is the corresponding Chainlink oracle and price0 (price1) the returned price.

The price of the given LP tokens is hence calculated as the values of the held token0 and token1 (taking the example of dp == false).

  • reverse0 = 10, reverse1 = 10
  • lpSupply = 10, amount = 3
  • amount0 = reverse0 * amount / lpSupply = 3
  • amount1 = reverse1 * amount / lpSupply = 3
  • price0 = $1, price1 = $1
  • value = amount0 * price0 + amount1 * price1 = $6

Note that we ignore decimals for discussion simplicity.

Vulnerability

The vulnerability originates from the malicious manipulation of getReserves function. Note that the correctness of the subject price oracle is guaranteed by the stable balance of pair pool.

An extremely imbalanced pool would lead to an overpriced estimation. Considering the aforementioned BUSD-USDT pool where k = 100 (i.e., reverse0 * reverse1) , if token0 was drained out from the pool, e.g., reverse0 = 0.01, to retain the constant formula, reverse1 would dramatically increase to 10000. In that case, the estimated price of 3 LP tokens would $3000.003, much greater than the original price of $6.

Once the collaterals are overpriced, the attackers can borrow more assets without repaying. Note the real price of collaterals is much lower than the borrowed ones, rendering a profitable attack.

To achieve the excessive imbalance, attackers can leverage a flashloan from an auxiliary pool supplied with token1. Note that, in normal cases, AMM pools are always balanced, forced by the incentive of arbitrage, e.g., for the aforementioned imbalanced pool, users can swap in $99.99 token0 to get $9900 token1 out, rebalancing the pool.

A step-by-step guide on how to launch this attack is as follows:

  • Provide a few liquidity in to pair pool and get corresponding LP tokens.
  • Deposit these LP tokens as collaterals.
  • Invoke a flashloan from the auxiliary pool to borrow out a great amount of token1.
  • Swap in all the borrowed token1 to imbalance the pair pool, and hence crazily increase the price of collaterals.
  • Borrow out a large amount of assets, given the overpricing of collaterals.
  • Swap out those borrowed token1 from the pair pool, restoring its balance
  • Repay token1 to the auxiliary pool and exit the flashloan.

Note that all these steps happen in a single transaction.

Vulnerability Fix

Duet team quickly responded to the bug report and suspended the borrowing functionality immediately (to prevent potential attacks). After a 3-day emergent discussion and bugfix, an in-time patch was proposed. All the deployed contracts (proxy) have been upgraded for remediation, after which the borrowing functionality is back online.

You can also read this blog post on notion. Follow us on twitter for more coming bug reviews and postmortems!

--

--

Narya.ai

Automated smart contract testing powered by AI. Previously Pwned No More Labs.