This article is meant to analyze the Bonq Protocol incident that occurred on the 1st of February at approximately 6.32pm UTC in an impartial way and identify the root cause.
Vulnerability Analysis
The vulnerability that was exploited stems from how the Bonq Protocol has integrated their WALBT / BEUR
trove with price oracles which are used to assess what the “true” price of the WALBT
asset is.
In detail, the troves that were exploited utilized a ConvertedPriceFeed
smart contract¹ to assess the price of the WALBT
token against the price of real-world euro (EUR), meant to be represented in a one-to-one manner by Bonq’s euro stablecoin.
To achieve this, the ConvertedPriceFeed
fetches the USD-denominated price of the WALBT
asset from a custom Tellor Price Feed² and divides it by the USD-denominated price of EUR from a custom Chainlink feed³, accommodating for decimals via a DECIMAL_PRECISION
constant used in a multiplication before the division.
The end result of the ConvertedPriceFeed::price()
function is the value of WALBT
denominated in euros and is used in multiple trove calculations of the Bonq Protocol to assess the health of a trove and whether it should be liquidated.
Core Problem
The TellorPriceFeed
built by Bonq to take advantage of the TellorFlex
oracle system⁴ has been integrated incorrectly as it immediately consumes the latest data point reported to the TellorFlex
oracle via the TellorFlex::getCurrentValue
function.
The Tellor trustless oracle system works by incentivizing users to discredit improper data measurements by invoking functions on the TellorFlex
contract that permit them to slash the TRB
assets that the malicious party had staked to submit their incorrect data point.
As time needs to pass between a false data point’s submission to the Tellor network and its detection and consequent discreditment by an honest party, data points from Tellor oracles must be consumed after a sufficient dispute window has passed.
Attack Scenario
This pre-condition was not satisfied in the TellorPriceFeed
implementation which made use of the TellorFlex::getCurrentValue
function which yields the latest reported value of the oracle. As a result, the hacker was able to exploit this by submitting a false price to the TellorFlex
oracle and forfeiting their 10 TRB
tokens in the process.
As the value of the exploit greatly outweighed the value of the 10 TRB
tokens that would be slashed, the end result of the attack was a significantly high profit for the attacker. The attack occurred in two separate transactions to maximize the profit of the attacker.
First, they submitted a transaction at approximately 2023–02–01 06:29:18 UTC in which they performed the following:
- Staked
10 TRB
units on theTellorFlex
oracle - Reported a false price of exactly
5.000.000
USD perWALBT
token, significantly overvaluing the token - Created a Trove of the
WALBT
asset - Transferred
0.1 WALBT
tokens to the Trove⁵ - Recorded the newly submitted collateral of the Trove
- Borrowed a total of
100.000.000
BEUR tokens - Created another Trove of the
WALBT
asset - Transferred
~13.25973256272339977 WALBT
tokens to the second Trove⁶ - Recorded the newly submitted collateral of the second Trove
Interestingly, the attacker did not borrow more BEUR tokens from the second Trove they created. The reason they created two different Troves under opposite circumstances was a requirement for their attack as this setup ensured that TroveFactory::firstTrove()
and TroveFactory::lastTrove()
would yield the attacker’s Troves.
The Troves are ordered by their collateralization ratio, permitting the attacker to “manipulate” the internal ordering of the Troves within the TroveFactory
as they see fit. This was a crucial step for the next transaction to succeed.
In their second transaction submitted at approximately 2023–02–01 06:31:06 UTC, the following sequence of events transpired:
- Staked
10 TRB
units on theTellorFlex
oracle using an alternative address (presumably to avoid being prohibited from a second submission) - Reported a false price of exactly
0.0000001
USD perWALBT
token, significantly undervaluing the token - Queried all Troves of the
WALBT
token in sequence using thefirstTrove
,lastTrove
, andnextTrove
mechanisms of theTroveFactory
proxied viaBonqProxy
- For each trove:
– Evaluated whether theWALBT
asset is associated with the Trove viacontainsTrove
– Evaluated whether the Trove has a non-zero debt viaTrove::debt()
, invokingTrove::liquidate()
in such a case - Once no more Troves remained, the hacker proceeded to invoke
Trove::repay()
on the second Trove they opened in the first transaction with the maximum ofuint256⁷
to ensure their debt repayment will succeed regardless of the underlying debt - The
Trove
ultimately extracted a total of~1.341.461 BEUR
tokens from the hacker to repay the debt, rendering theTrove
empty of any debt - As a final action, the hacker invoked
Trove::decreaseCollateral()
with an amount of roughly~113.813.998 WALBT
tokens
The hacker did not iterate through the 0x4248FD3E2c055a02117eB13De4276170003ca295
and 0x5343c5d0af82b89DF164A9e829A7102c4edB5402
Troves they opened in their first transaction as they did not wish to liquidate them.
As the debt of a particular CommunityLiquidationPool
is “shared” in that it carries over to the next trove that claims it via CommunityLiquidationPool::claimCollateralAndDebt
performed within a Trove’s liquidate() -> _updateCollateral() -> getLiquidationRewards()
call-chain, the attacker wished to accumulate all the debt of the liquidated Troves to their own Trove #2 created in the first transaction.
This permitted them to Trove.repay()
the remaining debt associated with the liquidated troves and extract the ~113.813.998 WALBT
tokens that were ultimately liquidated throughout the iterative process explained above.
Ultimately, the attacker was able to retain the following assets post-execution of both transactions:
+~113.813.998 WALBT
=~4,552,559.92 USD
+~98.658.539 BEUR
= Indeterminate Evaluation-20 TRB
=~358.2 USD
As the value of the BEUR
asset is no longer considered canonical, we can assess the financial impact of the WALBT
and TRB
assets and sum a total estimated profit of roughly ~4,552,201.92 USD
as of 2023-02-01 15:27:00 UTC.
Security Audit
Omniscia performed a security audit of the Bonq Protocol on August 15th, 2022 with a conclusion date of August 30th, 2022 and a final delivery date of October 12th, 2022. The publicly available audit⁸ highlighted multiple issues with the oracle system they wished to implement at the time.
The formal response by the Bonq Protocol team was that they need to re-visit their approach with regards to the pricing oracles and that they will not move forward with the implementations audited at the time, opting to integrate Chainlink oracles in the future.
The Bonq Protocol has introduced numerous updates since the time the audit was finalized, including all contracts involved in the vulnerability (ConvertedPriceFeed
, ChainlinkPriceFeed
, and TellorPriceFeed
). These contracts were never in scope of any audit conducted by the Omniscia team and thus are considered to be unaudited code.
Conclusion
The attack ultimately arised from improper integration of the Tellor oracle system and did not account for how the system dynamics of the Tellor system mandate a time delay before a data point is considered safe to use.
Sources
- Polygon Address of
WALBT / BEUR
Conversion Oracle: https://polygonscan.com/address/0x7D4c36c79b89E1f3eA63A38C1DdB16EF8c394bc8 - Polygon Address of Bonq’s
WALBT
Tellor Price Feed: https://polygonscan.com/address/0xa1620af6138d2754f7250299dc9024563bd1a5d6 - Polygon Address of Bonq’s
BEUR
Chainlink Feed: https://polygonscan.com/address/0x96923e13b9e7C4eB853D9c37ee32E2293eE872B8 - Polygon Address of
WALBT / USD
Tellor Price Feed: https://polygonscan.com/address/0x8f55d884cad66b79e1a131f6bcb0e66f4fd84d5b - Polygon Address of
WALBT
Trove #1: https://polygonscan.com/address/0x4248FD3E2c055a02117eB13De4276170003ca295 - Polygon Address of
WALBT
Trove #2: https://polygonscan.com/address/0x5343c5d0af82b89DF164A9e829A7102c4edB5402 - Maximum of
uint256
in Solidity (usually expressed astype(uint256).max
): 115792089237316195423570985008687907853269984665640564039457584007913129639935 - Omniscia Audit of Bonq Protocol (August 2022): https://omniscia.io/reports/bonq-borrowing-protocol/
Follow us on twitter — https://twitter.com/Omniscia_sec