Monolith: function-level review of the top-1 misses

September 19, 2026 ยท View on GitHub

The strict metric in results.md (Locating, category resolution) counts an issue as identified only when the function ranked #1 on its scored (file, question) pair is one the judges named. This review takes the four misses and asks a looser question: is the #1 function on the finding's exploit chain? A function is on the chain when it calls the buggy function, is called by it, produces the value the bug corrupts, or is where the corrupted value is consumed. A function that only touches the same state through a different path, or runs next to the exploit without affecting it, does not count.

Source: a cached re-run of bench/run_bench.py monolith (same rankings as results.md), code at contest commit b36e9ef (Monolith/src). The ranking is the one locating() scores: the issue's true pair with the highest file-level p.

IssueScored pairJudged functions#1 (p)#2, #3 (p)Ruling
H-1 Rounding lets a user borrow unbacked tokensLender.sol, arithmetic_precisionincreaseDebt, redeemwriteOff (0.83)redeem (0.81), updateBorrower (0.78)Not on the chain. The exploit runs redeem (inflates shares/debt) -> adjust repay -> decreaseDebt (clamps totalFreeDebt to 0) -> increaseDebt (mints shares 1:1). writeOff also writes totalFreeDebt through decreaseDebt(max), but it is not a step of this exploit and calls neither judged function.
M-2 Inconsistent health checks allow liquidating a healthy positionLender.sol, business_logic_state_machineadjust, getLiquidatableDebt, liquidatewriteOff (0.87)redeem (0.87), adjust (0.83)Not on the chain. liquidate calls writeOff after the wrongful liquidation, but writeOff applies its own 100x-debt threshold and is a no-op on a position at debt == borrowingPower. It neither produces nor consumes the <= vs > boundary.
M-3 Vault.totalAssets() can revert (EIP-4626 violation)Vault.sol, accounting_share_mathtotalAssets, getPendingInterestdeposit (0.81)previewDeposit (0.67), mint (0.62)On the chain. deposit calls convertToShares (Solmate ERC4626), which calls totalAssets(), which calls Lender.getPendingInterest(), whose catch block reverts on short gas.
M-6 wadExp() underflow to 0 leaves interest accrual stuckLender.sol, arithmetic_precisioncalculateInterest, accrueInterest, setHalfLifewriteOff (0.83)redeem (0.81), updateBorrower (0.78)On the chain. writeOff calls accrueInterest() on its first line, which calls calculateInterest (division by a zero growthDecay) and silently skips accrual in the catch block. This is a weak lead: most Lender entry points (redeem, adjust, liquidate) open with the same call.

The three hits (M-1 writeOff, M-4 redeem, M-5 calculateInterest) are unchanged. writeOff is #1 on the Lender.sol arithmetic_precision and business_logic_state_machine rankings, so it stands for three issues. It is the judged function for M-1 and on the chain for M-6 only.

Only the category-resolution ranking is reviewed, because it produces the 3 of 7. Detector resolution scores a different ranking per issue (results.md).

Strict: 3 of 7. Counting on-chain functions: 5 of 7.