Qubit QBridge xETH Unbacked Mint ACT Opportunity
Exploit Transactions
0x7f537463d055f32b49050a7e2179461666a9494deb7045d7825d72c2101682980xac7292e7d0ec8ebe1c94203d190874b2aab30592327b6cc875d00f18de6f31330x8c5877d1b618f29f6a3622cb610ace08ca96e04d8218f587072a3f91e8545bdcVictim Addresses
0x20e5e35ba29dc3b540a1aee781d0814d5c77bce6Ethereum0x80d1486ef600cc56d4df9ed33baf53c60d5a629bEthereum0x4d8ae68fcae98bf93299548545933c0d273ba23aBSC0xfd7a5506f434f5334c100efb765025243c39137cBSCLoss Breakdown
Similar Incidents
BUILD Governance Takeover and Unlimited Mint ACT Exploit
37%RewardsHypervisor reentrant deposit mints unbacked vVISR and drains VISR
33%Unlimited-Mint Collateral Used to Over-Mint Debt Token
32%CVG staking supply drain from reward-mint inflation bug
30%WETH Drain via Unprotected 0xfa461e33 Callback on 0x03f9-62c0
29%BSC ORT Staking 1-to-6000 Reward Mint ACT Opportunity
29%Root Cause Analysis
Qubit QBridge xETH Unbacked Mint ACT Opportunity
1. Incident Overview TL;DR
In January 2022, Qubit Finance’s cross-chain QBridge allowed an attacker to mint a large quantity of Qubit xETH (qXETH) on BSC without locking or burning any backing xETH representation on Ethereum. Using only an unprivileged externally owned account (EOA), the adversary called the Ethereum QBridge proxy with depositETH and then deposit for a burnList‑configured xETH resourceID, while the corresponding handler on Ethereum performed only value and whitelist checks and never burned or locked the backing QBridgeToken. On BSC, honest relayers later processed the resulting proposals via voteProposal, causing the BSC QBridge handler to execute the burnList path and mint qXETH to the attacker from the zero address. This broke the bridge’s conservation‑of‑value invariant and created 216960.19999999998 unbacked qXETH, constituting an Anyone‑Can‑Take (ACT) opportunity for any similarly situated unprivileged actor.
2. Key Background
QBridge is Qubit Finance’s cross‑chain bridge between Ethereum and BSC. On each chain, a QBridge contract maintains deposit nonces and proposal state, while a QBridgeHandler contract performs the asset‑level logic for deposits and proposal execution. For tokens bridged as wrapped representations (such as xETH/qXETH), the system uses a QBridgeToken contract that can be minted and burned by designated minters.
On Ethereum, the relevant contracts and addresses are:
- QBridge implementation:
0x99309d2e7265528dc7c3067004cc4a90d37b7cc3 - QBridge TransparentUpgradeableProxy:
0x20e5e35ba29dc3b540a1aee781d0814d5c77bce6 - QBridgeHandler:
0x80d1486ef600cc56d4df9ed33baf53c60d5a629b - QBridgeToken (Ethereum xETH representation):
0x17b7163cf1dbd286e262ddc68b553d899b93f526
On BSC, the primary addresses are:
- QBridge proxy:
0x4d8ae68fcae98bf93299548545933c0d273ba23a - Qubit xETH (qXETH) proxy:
0xfd7a5506f434f5334c100efb765025243c39137c - qXETH implementation (from proxy metadata):
0x2dfab0eebec0d3deb37c72f3c2ec62116178040e
The bridge uses a resourceID to tie together handler logic and token contracts across chains. On Ethereum, QBridgeHandler.setResource sets resourceIDToTokenContractAddress[resourceID] and contractWhitelist[token]. The handler also maintains a burnList mapping: if a token is burnable, executing a proposal on the destination chain mints fresh wrapped tokens and deposits on the source chain must burn or lock the backing.
The relevant parts of QBridgeHandler (Ethereum) are:
function deposit(bytes32 resourceID, address depositer, bytes calldata data) external override onlyBridge {
uint option;
uint amount;
(option, amount) = abi.decode(data, (uint, uint));
address tokenAddress = resourceIDToTokenContractAddress[resourceID];
require(contractWhitelist[tokenAddress], "provided tokenAddress is not whitelisted");
if (burnList[tokenAddress]) {
require(amount >= withdrawalFees[resourceID], "less than withdrawal fee");
QBridgeToken(tokenAddress).burnFrom(depositer, amount);
} else {
require(amount >= minAmounts[resourceID][option], "less than minimum amount");
tokenAddress.safeTransferFrom(depositer, address(this), amount);
}
}
function depositETH(bytes32 resourceID, address depositer, bytes calldata data) external payable override onlyBridge {
uint option;
uint amount;
(option, amount) = abi.decode(data, (uint, uint));
require(amount == msg.value);
address tokenAddress = resourceIDToTokenContractAddress[resourceID];
require(contractWhitelist[tokenAddress], "provided tokenAddress is not whitelisted");
require(amount >= minAmounts[resourceID][option], "less than minimum amount");
}
For burnList tokens, deposit burns QBridgeToken from the depositor. By contrast, depositETH only checks msg.value, whitelist, and minAmounts and never burns or locks the backing ERC‑20. On the destination chain, executeProposal mints:
function executeProposal(bytes32 resourceID, bytes calldata data) external override onlyBridge {
uint option;
uint amount;
address recipientAddress;
(option, amount, recipientAddress) = abi.decode(data, (uint, uint, address));
address tokenAddress = resourceIDToTokenContractAddress[resourceID];
require(contractWhitelist[tokenAddress], "provided tokenAddress is not whitelisted");
if (burnList[tokenAddress]) {
address delegatorAddress = delegators[option];
if (delegatorAddress == address(0)) {
QBridgeToken(tokenAddress).mint(recipientAddress, amount);
} else {
QBridgeToken(tokenAddress).mint(delegatorAddress, amount);
IQBridgeDelegator(delegatorAddress).delegate(tokenAddress, recipientAddress, option, amount);
}
} else if (tokenAddress == ETH) {
SafeToken.safeTransferETH(recipientAddress, amount.sub(withdrawalFees[resourceID]));
} else {
tokenAddress.safeTransfer(recipientAddress, amount.sub(withdrawalFees[resourceID]));
}
}
The expected invariant is that for any burnList resourceID, destination‑chain minted supply must be backed by source‑chain burns/locks. In the incident configuration, the exploited xETH resourceID is in the burnList on BSC, but the attacker’s deposits on Ethereum use the depositETH path that does not burn QBridgeToken.
3. Vulnerability Analysis & Root Cause Summary
The vulnerability is a cross‑chain invariant violation caused by inconsistent use of burn semantics for a burnList‑configured resourceID. On the destination chain (BSC), the qXETH handler treats the xETH resourceID as burnable and mints qXETH when proposals are executed, assuming that backing has already been burned or locked on the source chain. On the source chain (Ethereum), the same resourceID is wired so that the attacker’s deposits go through QBridge.depositETH → QBridgeHandler.depositETH, which performs only ETH value and minimum‑amount checks and never calls QBridgeToken.burnFrom or transfers any backing tokens.
As a result, each cross‑chain deposit for this resourceID increases the attacker’s qXETH balance on BSC without decreasing any xETH representation on Ethereum. Honest BSC relayers, acting under standard voteProposal/executeProposal logic, unknowingly mint unbacked qXETH to the attacker. This misconfiguration and handler/API mismatch form an Anyone‑Can‑Take ACT opportunity: any unprivileged EOA that can call QBridge on Ethereum and receive qXETH on BSC can reproduce the exploit.
4. Detailed Root Cause Analysis
4.1 Pre‑state and configuration
At Ethereum block 14090105 and BSC block 14741733, the following conditions hold (see seed/index.json, storage layout snapshots, and tokentx logs):
- QBridge/QBridgeHandler on Ethereum are deployed and configured with a resourceID that maps to a token contract
0x2f422fe9ea622049d6f73f81a906b9b8cff03b7f, andburnList[tokenAddress]is enabled in the relevant configuration on BSC. - On BSC, the qXETH proxy
0xfd7a5506f434f5334c100efb765025243c39137cis deployed and wired to aTransparentUpgradeableProxyimplementation (0x2dfab0...), which in turn delegates to a token implementation that exposesmintandTransfersemantics. - Attacker EOA
0xd01ae1a708614948b2b5e0b7ab5be6afa01325c7is funded and has no prior qXETH mint events in the observed window. - Ethereum QBridgeToken
0x17b7163cf1dbd286e262ddc68b553d899b93f526shows no ERC‑20 transfers or burns in the block range 14090000–14090300, confirming that the bridge does not manipulate this token during the attack window:
{
"status": "0",
"message": "No transactions found",
"result": []
}
4.2 Source‑chain deposit path without burn
The attacker begins by calling QBridge.depositETH on Ethereum via the proxy 0x20e5e35b...:
- Tx
0x7f537463d055f32b49050a7e2179461666a9494deb7045d7825d72c210168298(block 14090105)
Function:depositETH(uint8 destinationChainID, bytes32 resourceID, bytes data)
Value:0.1 ETH(msg.value = 0.1 ETH)
DestinationChainID:56(BSC)
ResourceID: embeds token0x2f422fe9ea622049d6f73f81a906b9b8cff03b7f
Data: ABI‑encoded(option, amount)withamount = 0.1 ETH.
From attacker_to_qbridge_summary.json, this is the first of the attacker’s deposits. Within QBridge.sol, the call path is:
function depositETH(uint8 destinationDomainID, bytes32 resourceID, bytes calldata data) external payable notPaused {
uint option;
uint amount;
(option, amount) = abi.decode(data, (uint, uint));
require(msg.value == amount.add(fee), "QBridge: invalid fee");
address handler = resourceIDToHandlerAddress[resourceID];
require(handler != address(0), "QBridge: invalid resourceID");
uint64 depositNonce = ++_depositCounts[destinationDomainID];
IQBridgeHandler(handler).depositETH{value:amount}(resourceID, msg.sender, data);
emit Deposit(destinationDomainID, resourceID, depositNonce, msg.sender, data);
}
The handler then executes depositETH as shown in Section 2: it only validates msg.value, whitelist, and minAmounts for the resourceID/option pair. It does not:
- Burn any QBridgeToken from the depositor.
- Transfer any ERC‑20 representing xETH into escrow.
The attack later repeats this pattern with QBridge.deposit:
- Seed tx
0xac7292e7d0ec8ebe1c94203d190874b2aab30592327b6cc875d00f18de6f3133(block 14090170) and 15 similardepositcalls, all from the attacker and all using the same resourceID and destinationDomainID with varying amounts. These are enumerated inattacker_to_qbridge_summary.json.
For these deposit calls, the code path would normally enforce a burn for burnList tokens, but the misconfiguration and cross‑chain resourceID wiring cause the exploited xETH resourceID to be handled inconsistently: the BSC side treats it as burnList while the attacker’s Ethereum path does not actually burn or lock backing for the minted qXETH.
4.3 Destination‑chain proposal execution with mint
On BSC, the honest relayer 0xeb645b4c35cf160e47f0a49c03db087c421ab545 monitors QBridge Deposit events and submits voteProposal calls on the QBridge proxy 0x4d8ae68f.... A representative transaction is:
- Tx
0x8c5877d1b618f29f6a3622cb610ace08ca96e04d8218f587072a3f91e8545bdc(block 14741733)
Function:voteProposal(uint8 chainID, uint64 depositNonce, bytes32 resourceID, bytes data)
chainID = 1(Ethereum),resourceIDmatches the xETH resourceID,depositNonceties back to the attacker’s Ethereum deposit.
The BSC callTracer trace for this transaction (bsc_tx_trace/.../tx_trace_callTracer.json) shows:
- A call into the qXETH proxy
0xfd7a5506...that forwards via delegatecall to implementation0x2dfab0.... - Within the implementation, a call with selector
0x40c10f19(standard ERC‑20mint(address,uint256)) to a token contract0xbf8169c5...(underlying implementation), with arguments:
{
"from": "0x2f422fe9ea622049d6f73f81a906b9b8cff03b7f",
"to": "0xbf8169c537eb6861c62beaacb9403ac37b4c8c7f",
"type": "DELEGATECALL",
"input": "0x40c10f19 ... 0000000000000000016345785d8a0000"
}
This results in a qXETH Transfer event from the zero address to the attacker. The Etherscan‑style token log summary (attacker_tokentx_xeth_summary.json) confirms that for this and four other sampled mints:
{
"contractAddress": "0xfd7a5506f434f5334c100efb765025243c39137c",
"from": "0x0000000000000000000000000000000000000000",
"to": "0xd01ae1a708614948b2b5e0b7ab5be6afa01325c7",
"tokenName": "Qubit xETH",
"tokenSymbol": "qXETH",
"methodId": "0xc0331b3e",
"functionName": "voteProposal(uint8 chainID, uint64 depositNonce, bytes32 resourceID, bytes data)"
}
Across the 18 qXETH mint events tied to voteProposal calls, the total amount minted to the attacker is 216960.19999999998 qXETH, as computed in root_cause.json from attacker_tokentx_raw.json.
4.4 Invariant and breakpoint
The core invariant for burnList‑style bridge tokens is:
- For any resourceID configured so that the destination‑chain handler executes the burnList branch and mints a wrapped token (qXETH) to the recipient, the total minted supply on the destination chain must not exceed the total amount of backing tokens that have been irreversibly burned or locked on the source chain by
QBridgeHandlerin response to corresponding deposits.
The concrete breakpoint is:
- On Ethereum, for the exploited xETH resourceID, the attacker’s deposits are processed by
QBridgeHandler.depositETH, which:- Checks that
amount == msg.value. - Checks that the token is whitelisted and
amount >= minAmounts[resourceID][option]. - Does not burn or lock any QBridgeToken backing for the xETH representation.
- Checks that
- On BSC, the same resourceID is configured as a burnList token, and
executeProposalmints qXETH to the attacker under the burnList branch.
Because there is no burn or lock on Ethereum, each cross‑chain deposit yields net positive qXETH supply on BSC, directly violating the invariant. On‑chain evidence shows:
- 18 qXETH
Transferevents from the zero address to the attacker on BSC. - No QBridgeToken transfers or burns for the same window on Ethereum.
This behavior is entirely driven by public contract logic and configuration and does not rely on private keys beyond the attacker’s unprivileged EOA.
5. Adversary Flow Analysis
5.1 Adversary‑related cluster
The adversary‑related cluster consists of a single EOA:
- Attacker EOA:
0xd01ae1a708614948b2b5e0b7ab5be6afa01325c7- Sends all QBridge.depositETH and QBridge.deposit calls in the exploit window on Ethereum (see
attacker_to_qbridge_summary.json). - Receives all 18 qXETH mint events on BSC from the zero address (see
attacker_tokentx_raw.jsonandattacker_tokentx_xeth_summary.json).
- Sends all QBridge.depositETH and QBridge.deposit calls in the exploit window on Ethereum (see
Victim contracts are:
- Ethereum QBridge proxy/implementation and QBridgeHandler (bridge logic and handler).
- Ethereum QBridgeToken (xETH representation).
- BSC QBridge proxy.
- BSC qXETH proxy/implementation (wrapped token representation).
5.2 Lifecycle stages
-
Ethereum QBridge deposits for xETH resourceID
- Tx
0x7f537463d055f32b49050a7e2179461666a9494deb7045d7825d72c210168298(block 14090105,depositETHwith 0.1 ETH). - Tx
0xff6fa990d4f6cf85592f7fb32840539e6dad20fda4e547acb1a4bbb969d99ba9(block 14090126,depositETHwith 0.1 ETH). - Tx
0xac7292e7d0ec8ebe1c94203d190874b2aab30592327b6cc875d00f18de6f3133and 15 otherdepositcalls (blocks 14090170–14090234) with larger amounts. - Effect: increments depositNonce for the xETH resourceID for destinationChainID 56, emitting
Depositevents while not burning or locking any QBridgeToken on Ethereum.
- Tx
-
BSC voteProposal executions minting qXETH
- Representative tx
0x8c5877d1b618f29f6a3622cb610ace08ca96e04d8218f587072a3f91e8545bdc(block 14741733). - Additional txs
0x881a68c9...,0x61ca8bc2...,0xf6008ab4...,0xd8bba155...and others in the same series. - Effect: for each proposal that reaches relayer threshold,
executeProposalmints qXETH (via ERC‑20mint) and results in aTransferevent from0x000...000to the attacker’s EOA. Total minted: 216960.19999999998 qXETH.
- Representative tx
-
Downstream collateralization and borrowing (out of scope for invariant)
- External incident reports and BSC token transfer logs indicate that the attacker later used qXETH as collateral in Qubit’s BSC lending protocol to borrow other assets. The present root cause analysis focuses on the bridge‑level invariant break (creation of unbacked qXETH) rather than reconstructing the full lending‑pool drain.
5.3 ACT criteria
The exploit satisfies the ACT (Anyone‑Can‑Take) criteria:
- Unprivileged adversary: The attacker uses only an EOA with no special privileges on either chain.
- Permissionless access:
QBridge.depositETHandQBridge.depositare externally callable by any EOA; BSC relayer transactions are public and do not depend on private agreements with the attacker. - Public information: All required parameters (resourceID, destinationChainID, depositNonce) are visible from public logs and traces; the misconfiguration can be discovered by inspecting verified contract code and storage.
- Repeatability: Any other unprivileged actor observing the same configuration could independently execute the same sequence of deposits and rely on the same relayers to mint unbacked qXETH to their own address.
6. Impact & Losses
From the bridge’s perspective:
- Unbacked minted supply: A total of 216960.19999999998 qXETH were minted on BSC to the attacker from the zero address without any corresponding burn or lock of QBridgeToken on Ethereum. This directly measures the magnitude of the invariant violation in the qXETH reference asset.
- Backing deficit: After the exploit, the outstanding qXETH liabilities on BSC exceed the Ethereum‑side xETH representation by 216960.19999999998 units. Any honest user holding qXETH is exposed to under‑collateralization risk if they attempt to redeem across chains.
External reports (see qbridge_known_incidents.json) and additional BSC traces show that the attacker subsequently supplied qXETH as collateral to Qubit’s lending pools and borrowed other tokens, amplifying losses beyond the bridge itself. However, even if no downstream lending actions were taken, the bridge alone would remain critically insolvent with respect to xETH/qXETH.
7. References
-
Ethereum contracts and code
- QBridge implementation (
QBridge.sol):0x99309d2e7265528dc7c3067004cc4a90d37b7cc3- Source:
artifacts/root_cause/data_collector/iter_1/contract/1/0x99309d2e7265528dc7c3067004cc4a90d37b7cc3/source/src/bridge/QBridge.sol
- Source:
- QBridgeHandler (
QBridgeHandler.sol):0x80d1486ef600cc56d4df9ed33baf53c60d5a629b- Source:
artifacts/root_cause/data_collector/iter_1/contract/1/0x80d1486ef600cc56d4df9ed33baf53c60d5a629b/source/src/bridge/QBridgeHandler.sol
- Source:
- QBridgeToken (
QBridgeToken.sol):0x17b7163cf1dbd286e262ddc68b553d899b93f526- Source:
artifacts/root_cause/data_collector/iter_1/contract/1/0x80d1486ef600cc56d4df9ed33baf53c60d5a629b/source/src/bridge/QBridgeToken.sol
- Source:
- QBridge implementation (
-
BSC contracts and code
- QBridge proxy:
0x4d8ae68fcae98bf93299548545933c0d273ba23a - qXETH proxy:
0xfd7a5506f434f5334c100efb765025243c39137c- Proxy verification and implementation:
artifacts/root_cause/data_collector/iter_4/bsc/bsc_qxeth_getsourcecode.json
- Proxy verification and implementation:
- QBridge proxy:
-
Key transactions
- Ethereum deposits (attacker → QBridge proxy): summarized in
artifacts/root_cause/data_collector/iter_3/tx/1/attacker_to_qbridge_summary.json, including seed tx0xac7292e7d0ec8ebe1c94203d190874b2aab30592327b6cc875d00f18de6f3133and initialdepositETHtx0x7f537463d055f32b49050a7e2179461666a9494deb7045d7825d72c210168298. - BSC qXETH mints via voteProposal: summarized in
artifacts/root_cause/data_collector/iter_4/bsc/attacker_tokentx_xeth_summary.jsonandattacker_tokentx_raw.json, with representative tx0x8c5877d1b618f29f6a3622cb610ace08ca96e04d8218f587072a3f91e8545bdc.
- Ethereum deposits (attacker → QBridge proxy): summarized in
-
Traces and storage
- Seed Ethereum tx call trace (
cast run -vvvvv) for0xac7292e7d0ec8ebe1c94203d190874b2aab30592327b6cc875d00f18de6f3133:artifacts/root_cause/seed/1/0xac7292e7d0ec8ebe1c94203d190874b2aab30592327b6cc875d00f18de6f3133/trace.cast.log. - BSC callTracer trace for qXETH‑minting voteProposal tx
0x8c5877d1b618f29f6a3622cb610ace08ca96e04d8218f587072a3f91e8545bdc:artifacts/root_cause/data_collector/iter_5/bsc_tx_trace/0x8c5877d1b618f29f6a3622cb610ace08ca96e04d8218f587072a3f91e8545bdc/tx_trace_callTracer.json. - Ethereum QBridge/QBridgeHandler storage layout and snapshot at
0xd6ffba:artifacts/root_cause/data_collector/iter_2/QBridge_storage_layout.json,artifacts/root_cause/data_collector/iter_2/QBridgeHandler_storage_layout.json, andartifacts/root_cause/data_collector/iter_2/storage/1/qbridge_and_handler_storage_0xd6ffba.json.
- Seed Ethereum tx call trace (
-
External reports
- Aggregated public incident reports describing the broader QBridge/Qubit exploit and downstream lending‑pool losses:
artifacts/root_cause/data_collector/iter_2/other/qbridge_known_incidents.json.
- Aggregated public incident reports describing the broader QBridge/Qubit exploit and downstream lending‑pool losses: