We do not have a reliable USD price for the recorded assets yet.
0x31957ecc43774d19f54d9968e95c69c882468b46860f921668f2c55fadd51b190x3bB7fFD08f46620beA3a9Ae7F096cF2b213768B3Polygon0x20D50159aff262f953C8913Ec859Cac13A010b8aPolygonBonqDAO accepted a permissionless TellorFlex SpotPrice(albt, usd) report inside the live ALBT collateral price path. In Polygon transaction 0x31957ecc43774d19f54d9968e95c69c882468b46860f921668f2c55fadd51b19, the adversary cluster staked 10 TRB, submitted an ALBT/USD value of 5e27, opened a fresh ALBT trove with only 0.1 ALBT, and minted 100,000,000 BEUR. In Polygon transaction 0xa02d0c3d16d6ee0e0b6a42c3cc91997c2b40c87d777136dedebe8ee0f47f32b1, the same cluster submitted 1e11 for the same Tellor query and then liquidated Bonq troves that became unhealthy under the manipulated price.
The root cause is a direct oracle-trust failure. Bonq's solvency logic treated raw Tellor reporter output as authoritative ALBT pricing without bounds checks, freshness validation, dispute delay, or trusted-source aggregation. That made both borrow() and liquidate() depend on an attacker-controlled oracle value.
Bonq's ALBT collateral flow matters because the protocol uses the current token price to decide whether a trove may borrow more BEUR or must be liquidated. On-chain validation in the current validator rerun at block 38792977 confirms:
BonqProxy / OriginalTroveFactory at 0x3bb7ffd08f46620bea3a9ae7f096cf2b213768b3 points to TokenToPriceFeed at .0x20D50159aff262f953C8913Ec859Cac13A010b8aTokenToPriceFeed.tokenPriceFeed(ALBT) returns 0x7D4c36c79b89E1f3eA63A38C1DdB16EF8c394bc8.TokenToPriceFeed.mcr(ALBT) returns 2000000000000000000, so ALBT troves must remain at or above 200% collateralization.TokenToPriceFeed.tokenPrice(ALBT) returns 97690490106405379 before the exploit transaction.The configured ALBT feed is not a local Bonq calculation. It is a converted feed that multiplies one price source by another conversion leg:
contract ConvertedPriceFeed is IPriceFeed, Constants {
IPriceFeed public immutable priceFeed;
IPriceFeed public immutable conversionPriceFeed;
function price() public view override returns (uint256) {
return (priceFeed.price() * DECIMAL_PRECISION) / conversionPriceFeed.price();
}
}
The ALBT-specific priceFeed leg resolves to Tellor-backed pricing. TellorFlex itself is permissionless: any address that stakes the required TRB can submit a report for any query ID as long as it respects the reporting lock. The relevant TellorFlex logic is:
function depositStake(uint256 _amount) external {
...
require(token.transferFrom(msg.sender, address(this), _amount));
_updateStakeAndPayRewards(msg.sender, _stakedBalance + _amount);
}
function submitValue(
bytes32 _queryId,
bytes calldata _value,
uint256 _nonce,
bytes calldata _queryData
) external {
...
require(_staker.stakedBalance >= stakeAmount, "balance must be greater than stake amount");
require(_queryId == keccak256(_queryData), "query id must be hash of query data");
...
_report.valueByTimestamp[block.timestamp] = _value;
}
Validator-side RPC checks confirm that TellorFlex.getStakeAmount() returned 10000000000000000000, so the exploit prerequisite was the public 10 TRB stake and nothing more.
This incident is an ATTACK-class oracle-manipulation failure in BonqDAO's collateral solvency path. Bonq's TokenToPriceFeed contract simply forwards token pricing to an external feed, and the ALBT feed chain eventually reaches TellorPriceFeed. TellorPriceFeed.price() converts the current Tellor report directly into a uint256 and returns it without validating reporter identity, value range, report age, or dispute status. Trove accounting then multiplies ALBT collateral by that oracle output and compares the resulting collateralization ratio against mcr(). Because the oracle value is attacker-controlled after a public Tellor stake and report submission, Bonq accepts an otherwise undercollateralized borrow and later also accepts liquidations created by the opposite price move. The protocol therefore violated a core solvency invariant: borrowing and liquidation should depend on manipulation-resistant collateral prices rather than on a single permissionless reporter's current output.
Bonq's first critical step is the price lookup itself:
function tokenPrice(address _token) public view override returns (uint256) {
return IPriceFeed(tokens[_token].priceFeed).price();
}
The Tellor-backed feed then exposes the raw value:
function price() public view virtual override returns (uint256) {
return uint256(bytes32(oracle.getCurrentValue(queryId)));
}
That raw price is consumed by Bonq's trove solvency logic:
function collateralValue() public view override returns (uint256) {
return (normalisedDecimals(collateral()) * factory.tokenToPriceFeed().tokenPrice(address(token))) / DECIMAL_PRECISION;
}
function _collateralization() private view returns (uint256) {
if (_debt > 0) {
return (normalisedDecimals(recordedCollateral) * factory.tokenToPriceFeed().tokenPrice(address(token))) / _debt;
} else {
return MAX_INT;
}
}
function insertTrove(address _newNextTrove) private {
require(_collateralization() >= mcr(), "41670 TCR must be > MCR");
}
function liquidate() public {
_updateCollateral();
require(_collateralization() < mcr(), "454f4 CR must lt MCR");
}
This is the concrete breakpoint. Once the Tellor value moves, Bonq's borrow and liquidation predicates move with it.
The collector trace for tx 0x31957ecc43774d19f54d9968e95c69c882468b46860f921668f2c55fadd51b19 shows the exploit sequence directly:
TellorFlex::depositStake(10000000000000000000)
TellorFlex::submitValue(
0x12906c5e9178631dba86f1f750f7ab7451c61e6357160eb890029b9eac1fb235,
0x00000000000000000000000000000000000000001027e72f1f12813088000000,
0,
SpotPrice("albt","usd")
)
OriginalTroveFactory::createTrove(WrappedToken: [0x35b2ECE5B1eD6a7a99b83508F8ceEAB8661E0632])
Trove::increaseCollateral(0, 0x0000000000000000000000000000000000000000)
Trove::borrow(0xED596991ac5F1Aa1858Da66c67f7CFA76e54B5f1, 100000000000000000000000000, 0x0000000000000000000000000000000000000000)
The query ID in that trace is the Tellor hash for SpotPrice("albt","usd"). The submitted value bytes decode to 5000000000000000000000000000 (5e27). Validator-side RPC checks show the effect immediately:
38792978, TokenToPriceFeed.tokenPrice(ALBT) = 97690490106405379.0x31957ecc..., TokenToPriceFeed.tokenPrice(ALBT) = 4579375226679073720614149170.Bonq therefore saw ALBT as orders of magnitude more valuable than it was in the pre-state. The root cause's ACT success predicate is satisfied because the same transaction minted BEUR from a trove that would be below the required mcr() under the baseline price.
The balance diff makes the economic realization deterministic:
{
"token": "0x338eb4d394a4327e5db80d08628fa56ea2fd4b81",
"holder": "0xed596991ac5f1aa1858da66c67f7cfa76e54b5f1",
"before": "0",
"after": "100000000000000000000000000",
"delta": "100000000000000000000000000"
}
That same balance diff also shows the Tellor stake movement into TellorFlex and the 0.1 ALBT movement into the newly created trove.
The second seed transaction uses the same primitive in the opposite direction. The collector trace for tx 0xa02d0c3d16d6ee0e0b6a42c3cc91997c2b40c87d777136dedebe8ee0f47f32b1 shows:
TellorFlex::depositStake(10000000000000000000)
TellorFlex::submitValue(
0x12906c5e9178631dba86f1f750f7ab7451c61e6357160eb890029b9eac1fb235,
0x000000000000000000000000000000000000000000000000000000174876e800,
0,
SpotPrice("albt","usd")
)
0x454C40FC7C61B2153728871D11bEA6C56F99FE77::liquidate()
0x6e237d5fB96D9C7aEdfD37e679017849dc845502::liquidate()
0x605778C9B0938fd60634FcE0f73b908500ACb8AA::liquidate()
...
Validator-side RPC checks show the ALBT price collapsing to 91594383432 after this transaction. At that point Bonq's require(_collateralization() < mcr()) liquidation branch becomes true for many ALBT troves.
The second balance diff shows the liquidation harvest landing in the attacker path, starting with:
{
"token": "0x35b2ece5b1ed6a7a99b83508f8ceeab8661e0632",
"holder": "0xeec2266a0ea9d1970fe692273839dbbb6a9ae598",
"before": "0",
"after": "858342997708983721666464",
"delta": "858342997708983721666464"
}
and continuing across many liquidated troves. The root cause artifact summarizes the total ALBT realization from this phase as 113813998.369826208354681311 ALBT.
This is an ACT opportunity because every dependency is public:
No private key, governance privilege, or non-public order flow is required.
The adversary cluster identified in the root cause is:
0xcacf2d28b2a5309e099f0c6e8c60ec3ddf656642: EOA that sent both seed exploit transactions.0xed596991ac5f1aa1858da66c67f7cfa76e54b5f1: attacker entry contract that receives the BEUR and ALBT gains.0xbaf48429b4d30bdfad488508d3b528033331fe8a: first Tellor reporter helper used in tx 0x31957ecc....0xb5c0ba8ed0f4fb9a31fccf84b9fb3da639a1ede5: second Tellor reporter helper used in tx 0xa02d0c3d....Their execution flow is straightforward:
SpotPrice("albt","usd") with a manipulated value.0.1 ALBT, and mint 100,000,000 BEUR.Bonq victim-facing addresses relevant to this path are:
0x3bb7ffd08f46620bea3a9ae7f096cf2b213768b3 (BonqProxy / OriginalTroveFactory)0x20d50159aff262f953c8913ec859cac13a010b8a (TokenToPriceFeed)0x35b2ece5b1ed6a7a99b83508f8ceeab8661e0632 (ALBT WrappedToken)The incident created two deterministic impacts:
100,000,000 BEUR was minted into the attacker path in tx 0x31957ecc43774d19f54d9968e95c69c882468b46860f921668f2c55fadd51b19.113,813,998.369826208354681311 ALBT was accumulated during the liquidation phase summarized by tx 0xa02d0c3d16d6ee0e0b6a42c3cc91997c2b40c87d777136dedebe8ee0f47f32b1.The non-monetary exploit predicate is equally important: Bonq accepted a borrow from a newly created ALBT trove that would be undercollateralized under the pre-manipulation price. That is the core solvency break. The profit fields in root_cause.json now quantify the first BEUR realization deterministically: attacker entry contract 0xed596991ac5f1aa1858da66c67f7cfa76e54b5f1 moved from 0 to 100000000000000000000000000 BEUR, while BEUR-denominated fees remained 0 because gas was paid in POL by the attacker EOA.
0x31957ecc43774d19f54d9968e95c69c882468b46860f921668f2c55fadd51b190xa02d0c3d16d6ee0e0b6a42c3cc91997c2b40c87d777136dedebe8ee0f47f32b10x3bb7ffd08f46620bea3a9ae7f096cf2b213768b30x20D50159aff262f953C8913Ec859Cac13A010b8a0x7D4c36c79b89E1f3eA63A38C1DdB16EF8c394bc80xa1620Af6138D2754F7250299DC9024563bd1a5D60x8f55D884CAD66B79e1a131f6bCB0e66f4fD84d5B0x35b2ECE5B1eD6a7a99b83508F8ceEAB8661E06320x338Eb4d394a4327E5dB80d08628fa56EA2FD4B810x12906c5e9178631dba86f1f750f7ab7451c61e6357160eb890029b9eac1fb2350x31957ecc43774d19f54d9968e95c69c882468b46860f921668f2c55fadd51b190xa02d0c3d16d6ee0e0b6a42c3cc91997c2b40c87d777136dedebe8ee0f47f32b1