The Deposit From Address Zero — JavaScript Bug Hunt
Modelled on the Qubit Finance bridge exploit (January 2022): the attacker called the bridge's token deposit path with a resource that mapped to the zero…
- Language: JavaScript
- Layer: Backend
- Difficulty: Medium
- Concepts: Security, Validation, Money
- Modelled on: Qubit Finance · 2022
- Visible tests: a real token deposit mints the same amount; the zero-address resource is refused and mints nothing
- Reward: 50 XP for a complete fix
Briefing
Modelled on the Qubit Finance bridge exploit (January 2022): the attacker called the bridge's token deposit path with a resource that mapped to the zero address — the placeholder used for native ETH. Calling an address that holds no contract code "succeeds" on Ethereum without doing anything, so the transfer helper reported success, the bridge recorded a deposit that never happened, and bridged tokens were minted on the other chain without any funds behind them. Losses were widely reported at around $80 million.
This project is a reconstruction. chain.js behaves like a low-level EVM call, and bridge.js trusts safeTransferFrom not throwing as proof that the funds arrived.
Fix deposit so only real token contracts are accepted and a mint is recorded only for funds the bridge actually received.
Bug report
BUG-QBIT-0127 · Priority: Critical · Reported by: bridge monitoring
deposit(chain, registry, resourceId, from, amount, mints) must:
- throw for an unknown resourceId or a non-positive amount
- throw if the resource's token address is the zero address (chain.ZERO) or an address with no contract code (chain.hasCode(address) is false)
- throw if the token transfer fails
- throw unless the bridge's balance of the token (chain.balanceOf(token, BRIDGE)) rose by exactly
amount(fee-on-transfer tokens are unsupported) - on success, push exactly one { to: from, resourceId, amount } onto mints
Whenever it throws, nothing is pushed onto mints.
Observed: a deposit against the ETH resource (token = zero address) mints xETH on the other chain although no funds moved.
Logs
[bridge] Deposit resource=ETH token=0x0000000000000000000000000000000000000000 amount=190000000000000000000
[bridge] mint qXETH to 0xd01a... amount=190000000000000000000
[monitor] bridge ETH balance unchanged after depositThe code as shipped
src/bridge/bridge.js (editable)
var safe = require("./safe");
var BRIDGE = "0xb41d6e";
exports.BRIDGE = BRIDGE;
// Locks `amount` of the resource's token in the bridge and records a mint on
// the other chain.
exports.deposit = function (chain, registry, resourceId, from, amount, mints) {
var token = registry[resourceId];
if (token === undefined) throw new Error("unknown resource " + resourceId);
if (!(amount > 0)) throw new Error("amount must be positive");
safe.safeTransferFrom(chain, token, from, BRIDGE, amount);
mints.push({ to: from, resourceId: resourceId, amount: amount });
};
Read-only context: src/bridge/chain.js, src/bridge/safe.js.
Open the hunt to edit the files, run the visible tests and submit against the hidden ones. More JavaScript bug hunts.