Bonq Protocol Incident Post-Mortem

Omniscia
5 min readFeb 2, 2023

--

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 the TellorFlex oracle
  • Reported a false price of exactly 5.000.000 USD per WALBT 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 the TellorFlex oracle using an alternative address (presumably to avoid being prohibited from a second submission)
  • Reported a false price of exactly 0.0000001 USD per WALBT token, significantly undervaluing the token
  • Queried all Troves of the WALBT token in sequence using the firstTrove, lastTrove, and nextTrove mechanisms of the TroveFactory proxied via BonqProxy
  • For each trove:
    – Evaluated whether the WALBT asset is associated with the Trove via containsTrove
    – Evaluated whether the Trove has a non-zero debt via Trove::debt(), invoking Trove::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 of uint256⁷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 the Trove 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

  1. Polygon Address of WALBT / BEUR Conversion Oracle: https://polygonscan.com/address/0x7D4c36c79b89E1f3eA63A38C1DdB16EF8c394bc8
  2. Polygon Address of Bonq’s WALBT Tellor Price Feed: https://polygonscan.com/address/0xa1620af6138d2754f7250299dc9024563bd1a5d6
  3. Polygon Address of Bonq’s BEUR Chainlink Feed: https://polygonscan.com/address/0x96923e13b9e7C4eB853D9c37ee32E2293eE872B8
  4. Polygon Address of WALBT / USD Tellor Price Feed: https://polygonscan.com/address/0x8f55d884cad66b79e1a131f6bcb0e66f4fd84d5b
  5. Polygon Address of WALBT Trove #1: https://polygonscan.com/address/0x4248FD3E2c055a02117eB13De4276170003ca295
  6. Polygon Address of WALBT Trove #2: https://polygonscan.com/address/0x5343c5d0af82b89DF164A9e829A7102c4edB5402
  7. Maximum of uint256 in Solidity (usually expressed as type(uint256).max): 115792089237316195423570985008687907853269984665640564039457584007913129639935
  8. Omniscia Audit of Bonq Protocol (August 2022): https://omniscia.io/reports/bonq-borrowing-protocol/

Follow us on twitter — https://twitter.com/Omniscia_sec

--

--

Omniscia

Team of experienced smart contract auditors & developers with deep expertise building & securing complex #decentralized networks & applications …