Security and threat model
No owner, no pause, no upgrade
Neither QaxeBurnVault nor QaxeDepositFactory has an owner, a pause switch, or any upgrade
path. There is no proxy, no admin key, no emergency stop. Once deployed, the code that ran on
2026-09-23 is the code that will always run at these addresses.
The only privileged actions, in full
The treasury is the sole privileged role in the entire system, and it can do exactly two things:
withdrawFees()— pull its own accrued 0.5% fee (feesOwed). It cannot pull anything else: notprepaid(burn principal), nottotalPending(miner payouts awaiting pull), not another address'spendingbalance.proposeTreasury(successor)/acceptTreasury()— hand the treasury role to a new address, in two steps (so a typo'd successor address can't accidentally lock the fee stream — the old treasury stays treasury until the new address explicitly accepts).
That's the complete list. There is no function anywhere in QaxeBurnVault or
QaxeDepositFactory that lets the treasury redirect a burn, redirect a miner's payout, change any
constant, or touch anything but its own fee.
Reentrancy
Both contracts guard every state-changing entry point with a standard nonReentrant modifier
(_locked flag, require(_locked == 1)). QaxeBurnVault's test suite specifically drives hostile
curve and token contracts that attempt to call back into sweep, syncAnonymous, credit,
withdrawFees, withdrawPending, proposeTreasury, and acceptTreasury mid-execution — every one
is blocked. QaxeDepositFactory is reentrancy-guarded the same way, and additionally, only the
factory itself may call collectOne (require(msg.sender == address(this))), so a batch entry
can't be reentered from outside the factory's own loop.
The vault's receive() is deliberately an empty, unguarded hook — it has to accept refunds and
returned payments while a payment call is in flight and the lock is held, since _pay itself can
trigger a call that bounces value straight back. It performs no state change itself; any actual
accounting only happens when the guarded functions above re-check balances.
Hostile payout and refund contracts
Both a payout wallet (the miner-leg destination) and a refundTo address (the caller-chosen gas
refund destination) can be arbitrary contracts, including malicious ones. The vault assumes
neither can be trusted and handles three failure modes:
- Gas-limited push, pull fallback. Every outbound payment (
_pay) is capped atPAYOUT_GAS = 30000gas. A payout or refund contract that reverts, or that simply runs out of that budget, cannot block anything: the payment attempt fails silently from the vault's point of view, and for a payout, the amount is credited topending[payout]instead — the recipient pulls it later withwithdrawPending(), on their own gas budget, whenever they want. A refund that fails is simply not paid; the reserved amount returns toprepaidas ordinary principal. - Returned-value reconciliation, not blind trust.
_paynever assumes the full amount it sent actually stuck — it measures the vault's own balance delta and treats anything a recipient calls back as a partial or full return, crediting it back toprepaidwith no newrefundAllowanceminted against it (see burn-vault). A hostile contract that "accepts" a payment only to immediately return some or all of it cannot use that trick to inflate its own future refund budget. - Isolation per entry. A hostile payout or refund contract on one deposit cannot affect any
other deposit's collection, or the batch it's part of —
QaxeDepositFactory.collectManyisolates each entry's revert viatry/catcharound the factory's owncollectOne, and reports it asCollectFailedrather than aborting the batch.
A gas refund can never be profitable
Several independent bounds stack to make refund-farming a losing strategy, not merely an unprofitable edge case:
- Priced from the chain, not the caller.
refundPrice() = min(tx.gasprice, 2 * block.basefee)— a caller who inflates their owntx.gasprice(a "priority-fee farming" attempt) gains nothing past 2× the network's real basefee; the test suite specifically drives an absurd 50×-basefee gasprice and confirms the refund stays bounded regardless. - Bounded per-action, not per-caller. Every refund is capped at
REFUND_CAP_BPS = 3%of the gross behind the specific deposit or sweep being refunded — there is no way to claim more by batching, repeating, or restructuring calls, because the cap is recomputed fresh from that action's own principal every time. - A contract caller gets no base-transaction claim.
TX_BASE_GAS(21,000) and calldata cost are only added to a collection's claimed gas when the top-level caller (tx.origin) is the same address making the call — i.e. only a plain, unrelayed transaction can claim the cost it actually paid as the transaction. A contract calling through a relay cannot claim a base cost it never paid itself; the test suite confirms a relayed sweep's refund stays strictly undergasUsed * price. - Calldata cost is counted at the EVM's own real rate.
_calldataGas()sums exactly 4 gas per zero calldata byte and 16 gas per nonzero byte — the same rates the EVM itself charges as intrinsic transaction gas. Padding a call with extra calldata to inflate the claimed overhead costs the caller precisely as much in real transaction gas as it could ever add to the claimed refund, so it cannot manufacture profit; it can, at best, break even on a value the caller was already going to pay for regardless. - Every measured calibration keeps refund ≤ real cost. Across the Hardhat suite and the
Orchard rehearsal, every refund-over-cost ratio measured is at or below
1.0(typically 0.79–1.00×, see burn-vault) — the tail constants (SWEEP_TAIL_GAS,CREDIT_TAIL_GAS,COLLECT_TAIL_GAS) were deliberately tuned conservative rather than to break even exactly, because underpaying by a little is safe and overpaying by any amount would not be.
Sandwich exposure
QaxeBurnVault.sweep has no independent price oracle — the only floor against a bad execution
price is the caller-supplied minTokensOut, checked against the vault's actual post-buy token
balance. This is an accepted design limitation, not an oversight: caps and cooldown bound how
much and how often exposure exists, they do not by themselves guarantee a fair price if a caller
supplies a weak floor.
The bounds that do exist:
- Slice size. Each sweep buys at most
min(1000 QUAI, 0.5% of the market's reserve)(SLICE_BPS = 50,ABS_MAX_SLICE = 1000 ether) — a sandwich on any single sweep is capped at what it can extract from 0.5% of reserve, roughly putting the sandwich break-even around ~1.02% of reserve given the round-trip fees involved. - Cooldown.
COOLDOWN_BLOCKS = 120between sweeps (after an unconditional first sweep) — the size cap is meaningless if an attacker can stack ten slices into one block; the cooldown forces genuinely separate blocks, and separate opportunities to notice and use a fresh, honest quote. - A stale-price rollback is proven, not assumed. The test suite drives a real price move
between a caller's quote and their
sweepcall and confirms the transaction reverts ("Insufficient burn") and every piece of state —prepaid,refundAllowance,feesOwed, supply, the vault's balance — rolls back completely. What is not proven or claimed is protection when a caller supplies a weak floor in the first place; see integrate for how to build a floor from a freshcurve.quoteBuyimmediately before sending.
Front-running collect
collect/collectMany/collectAndSweep are permissionless by design — anyone can collect anyone
else's deposit address, and a caller can be front-run by a faster bot doing the exact same call.
This is intentionally low-stakes: the split between burn principal and payout wallet is fixed by
the deposit address's own salt (payout, burnBps) and is identical no matter who calls
collect — front-running only competes for the small, bounded gas refund attached to
refundTo, never for the miner's principal or payout. Losing that race costs a would-be collector
only the gas of a reverted or unnecessary call; it never puts a miner's funds at risk.
What Hardhat cannot prove about Quai, and what Orchard did
Hardhat's in-process EVM models Ethereum-style gas accounting. It cannot show:
- Quai's real account-creation cost (measured ~39,358 gas for a plain transfer to a fresh account, versus ~21,000 + 25,000 on an Ethereum-style chain) or its storage-growth pricing, which appears to run outside the interpreter's own gas accounting.
- Whether a
CREATE2-deployed, same-transactionselfdestructgenuinely clearsEXTCODESIZEback to zero on real Quai state, leaving the address reusable. - Whether Quai's real gas pricing pushes the vault's calibrated refund constants above or below their intended ratio in practice.
scripts/orchard-rehearse-qaxe-v2.cjs exists specifically to close that gap on a real (non-mainnet)
Quai chain — its last recorded run was PASS 12/12, proving (among the checks listed in
deposit-factory): the forwarder's code is genuinely
cleared and the address genuinely reusable after collection, a real graduated-curve sweep burns
exactly the reported amount, and every measured refund-over-cost ratio stayed at or below 1.0 on
real Quai gas — with the honest caveat that real Quai gas runs 15–20% above what Hardhat's
in-process EVM sees, which is exactly why the refund ratios measured on Orchard (0.79–0.85×) sit
below the Hardhat-measured ones (0.94–1.00×).
Review status
An independent adversarial/spec review of the accounting and sweep mechanics (see
hartii-labs/contracts/docs/QaxeBurnVault-C2-review.md) found zero Critical and zero High
findings. Two informational items were logged and are reflected in this document as accepted
design limitations, not defects: the lack of an independent execution-price oracle for sweep
(covered above, under Sandwich exposure), and — at the time of that review — that gas
reimbursement was still staged and not yet calibrated against real receipts; it has since been
calibrated (see burn-vault) and rehearsed
end-to-end on Orchard.