The Index That Started at Zero — JavaScript Bug Hunt
Modelled on Compound's Proposal 062 (September 2021): an upgrade to the contract that distributes COMP rewards contained a bug in how reward indexes were…
- Language: JavaScript
- Layer: Backend
- Difficulty: Hard
- Concepts: Money, State
- Modelled on: Compound · 2021
- Visible tests: a settled account earns the index growth; joining a freshly initialised market earns nothing
- Reward: 50 XP for a complete fix
Briefing
Modelled on Compound's Proposal 062 (September 2021): an upgrade to the contract that distributes COMP rewards contained a bug in how reward indexes were initialised. A single comparison treated a market's freshly initialised index wrongly, so accounts that had never been settled in that market were credited as if they had been accruing since the very beginning — and could claim far more COMP than they had earned. Because changes to the protocol had to pass governance, the fix could not ship immediately.
rewards.js is a reconstruction of that accrual step: each market carries a reward index that only grows, each account remembers the index it was last settled at, and an account that has never been settled has index 0.
Fix accrue so a never-settled account starts from the initial index in every initialised market.
Bug report
BUG-COMP062 · Priority: Critical (over-distribution) · Reported by: protocol team
constants.js: SCALE = 1e6, INITIAL_INDEX = 1e6 — every rewards market starts at INITIAL_INDEX; a market with index 0 has never been initialised.
accrue(market, account) (market = { index }, account = { balance, index, accrued })
- start = account.index
- an account with index 0 has never been settled: in an initialised market (market.index >= INITIAL_INDEX) it starts from INITIAL_INDEX instead
- earned = floor(balance * (market.index - start) / SCALE)
- then account.index = market.index, account.accrued += earned; return earned
So an account that joins a market the moment its rewards begin earns 0.
Observed: accounts in a market whose index had just been initialised were credited balance * 1 COMP each on their first claim.
Logs
[comptroller] market cXYZ rewards initialised index=1000000
[comptroller] claim 0x9a..: supplierIndex=0 delta=1000000 accrued=4200
[comptroller] claim 0x3c..: supplierIndex=0 delta=1000000 accrued=91000The code as shipped
src/rewards/rewards.js (editable)
var C = require("./constants");
// Settles an account's reward accrual against the market's current index.
exports.accrue = function (market, account) {
var start = account.index;
if (start === 0 && market.index > C.INITIAL_INDEX) {
start = C.INITIAL_INDEX;
}
var earned = Math.floor(account.balance * (market.index - start) / C.SCALE);
account.index = market.index;
account.accrued += earned;
return earned;
};
Read-only context: src/rewards/constants.js.
Open the hunt to edit the files, run the visible tests and submit against the hidden ones. More JavaScript bug hunts.