UpgradesSeptember 14, 2026by
EmpoorioChain Core
EmpoorioChain Core

Postmortem: A Spending Cap of 10 % Every Twelve Hours

Summary

The treasury pallet enforces TreasuryMaxSpendPerPeriod = 10 % of the treasury balance per TreasuryVentanaDeTope. The tokenomics document described the window as one year. In the runtime, until spec 217, it was 12 hours. Ten percent every twelve hours is not a spending cap; it is a schedule for draining a treasury in about a week. No funds were lost — the testnet treasury holds test DMS and governance never proposed spending — but the control described in the tokenomics did not exist.

How it happened

The constant was expressed in blocks. With 6-second blocks, a year is 5,256,000 blocks; twelve hours is 7,200. The value in the runtime was 7,200 — the same number the upgrade policy uses for a normal upgrade's minimum timelock. The most likely story is a copy from one constant to another during runtime assembly, and a review that saw a plausible number of blocks without converting it to time.

Why it was not caught

No test asserted the window's duration in time units. Tests of the spending cap used a mock where the period constant was set directly, so they passed regardless of the production value. Nothing compared the runtime constant against the tokenomics document; the two lived in different files and different languages.

Detection

The "wire it or retire it" sweep for runtime 217 walked every economic constant in the runtime against TOKENOMICS.json. The window did not match. The tokenomics document was then updated to record the history explicitly: until 216 the window was 12 h and the "10 %" meant 10 % every 12 hours.

Fix

Runtime 217 set TreasuryVentanaDeTope to 365 days in blocks. An economic-class change under the upgrade policy: 48-hour timelock, economic simulation, dry run.

Lessons

  1. Constants with units need tests in those units. assert_eq!(WINDOW_BLOCKS * 6, 365 * 86_400) would have failed on day one.
  2. The canonical economics file must be machine-checked against the runtime. scripts/tokenomics/verificar_cifras.py now ties TOKENOMICS.json to the chain spec; the treasury window is one of the checks.
  3. Record the history in the document. A reader who sees "10 % per year" today can also see it was not always so, and how much to trust the next constant.
  4. A cap that cannot bind is decoration — the same question the validator-audit postmortem asked, applied to economics.

Based on TOKENOMICS_CANONICO.md §2 (treasury row) and the runtime 217 sweep.

Share this article