# Audit report

> Audit the vault: src/CDPVault.sol, src/ParameterizedVault.sol and src/ImdUSD.sol, at the pinned commit, for a mainnet launch. Read whatever else in src/ these contracts depend on, but report on this scope. Seven audit rounds and their fixes are already in (docs/AUDIT-*.md; the newest is docs/AUDIT-RETRY2-PANEL-VAULT-2026-10-08.md, whose Resolution section says how each finding was answered). Two commits no panel has read: 58f73de (a repayment that leaves its backing behind stays in the supply backing is measured against) and d7fceab (the fee base, the banks, bite). They are what to break first. A finding of an earlier round counts only if its fix regressed or left a gap; the two accepted items (retry2 #6: a price fall re-prices a debt-bound term as cold; retry2 #3's trade-off: while most supply is new the fee base is small and redemptions pay more) are findings only if their stated reason is wrong.
>
> imdUSD is a dollar-denominated CDP stablecoin borrowed against sIMD (IdentityMD's staked IMD, an ERC-4626 share with 24 decimals, about 7.95 IMD each). Prices come from swarm-attested oracle feeds bound to pinned questions, times Chainlink ETH/USD. Everything about the deployment is in src/DeploymentConfig.sol and docs/MAINNET-RUNBOOK.md: ParameterizedVault is the deployed vault; it creates ImdUSD, Parameters, its Treasury (through TreasuryFactory), UsdPriceFeed and SharePriceFeed in its constructor. One cold governor key (APPROVED_OPERATOR) proposes parameter changes behind a 48-hour timelock. Collateral pricing is per 1e18 raw units throughout. IMD's only market is a full-range Uniswap v4 pool, about $2.3M a side with a 1% fee; docs/PARAMETERS-2026-10-05.md has the numbers every economic parameter was chosen from.
>
> Answer each numbered question, including the ones where nothing is wrong:
> 1. THE FEE BASE (CDPVault._feeBase, Position.feeExcess, _feeExcess, _moveExcess with fee = true, _reduceDebt's repayment flag, the credit release in _lag, WORK_MINTED_THIS_TX_SLOT). The base is the live supply, less principal still cold and work minted in the transaction, plus warm principal repaid by wipe, bite or cover in the last hours, kept per position and released only as that position borrows back from its bank. Prove or break: no sequence, by one position or several, in one transaction or across many, moves the base below the honest warm supply (pinning the fee at the cap cheaply) or above it (diluting it, resetting the base rate) without seasoned capital held for hours; a redemption against a position adds nothing; the release cannot be triggered by capital that was never repaid; the cost of the accepted trade-off (a small base while most supply is new) in fee and in peg floor at launch.
> 2. THE SUPPLY KEPT FOR BACKING (58f73de: Position.excess, _excess, _excessNow, _moveExcess with fee = false, in wipe, free and draw; _backingPerUnit's supply = live + excess). Prove or break: a repayment that leaves its backing behind can no longer raise what a redemption is paid, in one transaction or across several; an honest repay-and-withdraw is not underpaid; the excess cannot be released by another position, inflated, or left stranded (bite, cash and cover add nothing to it; free releases it by the term's fall at a price that may be unreadable).
> 3. BANKS THAT COOL (_lag: a bank cools at BACKING_HALF_LIFE while it waits and is gone after a quiet BACKING_WARMUP; credit only to the position that lost the warmth; the bank re-dated at every touch of its side). Prove or break: no visit pattern keeps warmth alive past what honest cooling gives; credit cannot exceed what left warm; the secured and debt sides cannot feed each other.
> 4. COLD CAPITAL PER POSITION after d7fceab (the quiet-day cutoff now applies to the vault totals and banks only; a position's own figures cool without it; _cool, _pow, _coldNow, laggedNow; the 128-bit saturation). Confirm the previous panel's answers still hold and that the totals, now at least the sum of the positions only up to rounding and except after a quiet day, never let laggedNow exceed honest warm-up.
> 5. LIQUIDATION AND COVER ON DRAINED POSITIONS (bite always requires a mark, grace and an open window; cover takes a re-lock worth less than the recorded bad debt at its value, CoverBelowCollateralValue; _coverDust). Can a drained borrower now hold cover off, can a rebuilding borrower be harmed, can cover or bite be griefed, and do the bad-debt record and totalBadDebt stay consistent?
> 6. Contract size (ParameterizedVault initcode 46,993 of 49,152, runtime 22,780 of 24,576), the fresh-debt record, earn's wage gate, positions, redemption, the stability fee, price gating and arithmetic: for regressions.
>
> Not findings: addresses in DeploymentConfig that are placeholders until deployment (INTAKE, ORACLE_ASKER, TREASURY_FACTORY, WORK_ORACLE_FACTORY); the mocks (MockIMD, MockWorkOracle, LaunchToken); script/checks/ (a separate, partly stale tree); web/ and points/; anything docs/COMPUTE-BACKING-DESIGN.md describes as future work; and findings of the earlier audits in docs/AUDIT-*.md and docs/INTERNAL-AUDIT-2026-10-04.md, unless the fix regressed. A constant set to a deliberate economic value is not a finding; an arithmetic or ordering error in how it is used is.
>
> For every finding: severity; file and function; the call sequence from an external caller; a concrete failing input or state with expected against actual; whether it is reachable with the constants as committed; and the smallest fix. Also report every place a comment or NatSpec claims a property the code does not have, and say which contracts you read in full and which you could not reach.

| | |
|---|---|
| Repository | https://github.com/fa11up/infer-protocol |
| Commit | `d7fceab63a0310979dd911929f00e9469aa68258` |
| Job | `45bf3777-cc84-44eb-a7ad-bbb2c0833d07` |
| Judged | 2026-10-08 08:28 UTC |
| Findings | 1 high · 4 medium · 5 low · 3 info |

Four agents audited the code as it is at `d7fceab`, each in one area (math, permissions, economics, control flow),
and a judge reproduced, merged and ranked what they found, then read the code once more itself. Nothing in the repository was changed or deployed.

## Findings

### 1. High: CDPVault.wipe: Position.excess is never released when the secured term falls by another path (a second wipe, bite, cash, cover), so a repay-a-slice, repay-the-rest, withdraw sequence strands the slice

`src/CDPVault.sol:572`

```
            _moveExcess(position, false, repaid - Math.min(repaid, Math.mulDiv(fell, _priceOrZero(), 1e18)), 0);
```

Q2 (58f73de). `wipe` adds to `Position.excess` the part of THIS repayment that its secured term did not follow down (line 571-572: `repaid - min(repaid, fell x price)`, netted only against this call's `repaid`). The only releases are `draw` (by the amount borrowed, line 503) and `free` (by the term's fall, line 480-482). Nothing releases it when the term falls for any other reason: a later `wipe` whose fall exceeds its own repayment (the second repayment of a loan clears the debt and drops the term to zero, and `repaid - min(repaid, fellValue)` is zero, not negative), `bite`, `cash` against the position, or `cover`. A borrower whose term is collateral-bound (CR in the 170-200% band, i.e. any position that just drew at mat) repays a slice (term unchanged, excess += slice), repays the rest (term to zero, nothing released), frees everything: the slice stays in `_excess` with no debt and no collateral behind it, and `_backingPerUnit` (line 784, `supply = totalSupply + _excessNow()`) adds it to the denominator of BOTH the live and the lagged figure for every later redemption, halving every six hours. Honest two-step unwinds do this once per exit. Deliberately, a fresh helper contract per cycle (a position's own redraw would release its own excess, another position's never does) runs lock C, draw D at 170%, wipe 0.15 D, wipe the rest, free C in one transaction and repeats with the SAME collateral: each cycle strands 0.15 D for gas, zero seconds of stability fee and no seasoning (the slice is cold; `wipe` does not look at `coldOut`). It works above par too: a fully backed vault (OTHER 2,000 IMD against 1,000 imdUSD at $1, backing 1.0) is taken to 0.173 by ten cycles of 10,000 IMD. `cash` pays `_backingPerUnit x (1 - fee)`, so the channel the design says must never halt is paid down toward nothing on demand (gemOut rounds to 0 and `cash` reverts ZeroAmount at a large enough excess), and the peg floor min(1 - fee, backing) collapses with it. Who loses: every redeemer and the peg; who gains: a candidate borrower deterring redemptions against itself, or anyone short the peg. Reachable with the constants as committed (LINE $1M bounds D per cycle, repaid inside the cycle; wage 0; no governance). The NatSpec at 775-782 ('Only that part of a repayment is kept in the supply ... lagging the whole supply instead underpaid every redeemer') does not hold: what is kept outlives the backing it was kept for. Call sequence from an external caller: Pump.run -> (new Cycler).run -> vault.lock(10_000e18); vault.draw(2_352e18); vault.wipe(352e18); vault.wipe(debtOf); vault.free(10_000e18), ten times in one transaction; next block HOLDER vault.cash(100e18, 0, OTHER). Smallest fix (no new storage): clamp `position.excess` to the value its term still stands behind after every priced `_resecure` (in `_resecureBounded`, when `price != 0`: `uint256 cap = mulDiv(current, price, 1e18); if (position.excess > cap) _moveExcess(position, false, 0, position.excess - cap)`), which releases it in the second wipe, bite, cash and cover in one place; or net the fall both ways in `wipe` (`remove = fellValue > repaid ? fellValue - repaid : 0`) and release by the term's fall value in `bite`, `_redeemPosition` and `cover`. The warm-only half (medium finding, line 569) bounds the gas-only pump to seasoned capital but does not close the honest stranding; both halves are needed. Fits the margin (runtime 22,780 of 24,576, initcode 46,993 of 49,152). Merged from audit_economics (high) with the same mechanism's appearance in the other specialists' NatSpec notes.

**Reproduction**

test/scratch/Proof_StrandedExcess.t.sol (attached; both tests fail on this code). ParameterizedVault over an 18-decimal MockIMD at $1 (IMD/ETH 1/2000 times a Chainlink ETH/USD of 2000e8 etched at CHAINLINK_ETH_USD), NHI 0.85 (mat 170, gap 50), TreasuryFactory etched at TREASURY_FACTORY, Treasury empty. OTHER locks 2,000 and draws 1,000, hands HOLDER the 1,000; two quiet days; IMD to $0.40: backingPerUnit() == 0.8e18. Test 1: a Pump contract holding 10,000 IMD runs ten fresh Cycler contracts in ONE transaction (lock 10_000e18, draw 2_352e18 = 170.07%, wipe 352e18 [term stays 10,000 = min(10,000, 2 x 2,000 / 0.4)], wipe debtOf [term to 0, nothing released], free 10_000e18, collateral handed back). Next block: totalDebt == 1,000e18, vault IMD balance == 2,000e18, totalSupply == 1,000e18, exactly as before. EXPECTED: backingPerUnit() == 0.8e18 and HOLDER's cash(100e18, 0, OTHER) paid about 199e18 raw IMD. ACTUAL: backingPerUnit() == 177044233429577747 (800 / (1,000 + 3,518.6 of excess)) and the redemption paid 42048005439524714750 raw IMD. Test 2 (honest, one EOA, three transactions): lock 10_000e18 + draw 2_352e18; wipe 352e18; wipe debtOf + free 10_000e18; positions(NEW) == (0, 0). EXPECTED backingPerUnit() == 0.8e18. ACTUAL 591715976331360946 (352 stranded). Above par (test/scratch/Leads.t.sol test_strandedExcessAbovePar): the same book at $1, backing 1e18, ten cycles of draw 5_882 / wipe 882: ACTUAL backingPerUnit() == 173175974064472438 with 8,816 of excess against a 1,000 supply.

**Proof**: a Foundry test that fails on this code and passes once it is fixed.

```solidity
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;

// CDPVault.wipe keeps in Position.excess the principal repaid that the secured term did not follow down, and
// only `draw` (by the amount borrowed) and `free` (by the term's fall) ever release it. A second `wipe` whose
// term fall exceeds its own repayment releases nothing, so a borrower who repays a slice while its term is
// collateral-bound, then repays the rest and withdraws, leaves the slice in `_excess` with no debt and no
// collateral behind it. `_backingPerUnit` adds it to the supply for every later redemption, for hours.
// A fresh helper contract per cycle makes it a gas-only, unbounded pump on the redemption payout.

import {Test} from "forge-std/Test.sol";
import {ParameterizedVault} from "src/ParameterizedVault.sol";
import {ImdUSD} from "src/ImdUSD.sol";
import {MockIMD} from "src/MockIMD.sol";
import {TreasuryFactory} from "src/TreasuryFactory.sol";
import {ISwarmFeed} from "src/interfaces/ISwarmFeed.sol";
import {APPROVED_OPERATOR, CHAINLINK_ETH_USD, TREASURY_FACTORY} from "src/DeploymentConfig.sol";

contract SeFeed is ISwarmFeed {
    uint256 public constant maxAge = 1 days;
    uint256 private value;
    uint64 private updatedAt;

    constructor(uint256 v) {
        value = v;
        updatedAt = uint64(block.timestamp);
    }

    function set(uint256 v) external {
        value = v;
        updatedAt = uint64(block.timestamp);
    }

    function latestValue() external view returns (uint256, uint64) {
        return (value, updatedAt);
    }

    function isStale() external pure returns (bool) {
        return false;
    }
}

contract SeMirror is ISwarmFeed {
    ISwarmFeed private immutable primary;

    constructor(ISwarmFeed p) {
        primary = p;
    }

    function latestValue() external view returns (uint256, uint64) {
        return primary.latestValue();
    }

    function isStale() external view returns (bool) {
        return primary.isStale();
    }

    function maxAge() external view returns (uint256) {
        return primary.maxAge();
    }
}

contract SeAggregator {
    function decimals() external pure returns (uint8) {
        return 8;
    }

    function latestRoundData() external view returns (uint80, int256, uint256, uint256, uint80) {
        return (1, 2000e8, block.timestamp, block.timestamp, 1);
    }
}

/// @dev One cycle: lock C, draw D at 170%, repay a slice (term unchanged: kept as excess), repay the rest
/// (term to zero: nothing released), withdraw everything. Position closed, excess stranded.
contract SeCycler {
    function run(ParameterizedVault vault, MockIMD imd, uint256 c, uint256 d, uint256 slice) external {
        imd.approve(address(vault), c);
        vault.lock(c);
        vault.draw(d);
        vault.wipe(slice);
        vault.wipe(vault.debtOf(address(this)));
        vault.free(c);
        imd.transfer(msg.sender, c);
    }
}

contract SePump {
    function run(ParameterizedVault vault, MockIMD imd, uint256 c, uint256 d, uint256 slice, uint256 n) external {
        for (uint256 i; i < n; i++) {
            SeCycler cy = new SeCycler();
            imd.transfer(address(cy), c);
            cy.run(vault, imd, c, d, slice);
        }
    }
}

contract StrandedExcessTest is Test {
    address private constant OTHER = address(0x07E);
    address private constant HOLDER = address(0x401);
    address private constant NEW = address(0x0E3);

    MockIMD private imd;
    SeFeed private primary;
    ParameterizedVault private vault;
    ImdUSD private stable;

    function usd(uint256 dollars) private pure returns (uint256) {
        return dollars / 2000; // IMD/ETH such that times Chainlink's 2000 USD/ETH it reads `dollars`
    }

    function setUp() public {
        if (TREASURY_FACTORY.code.length == 0) vm.etch(TREASURY_FACTORY, address(new TreasuryFactory()).code);
        vm.etch(CHAINLINK_ETH_USD, address(new SeAggregator()).code);
        vm.warp(1_000_000);
        imd = new MockIMD();
        primary = new SeFeed(usd(1 ether)); // IMD = $1
        SeFeed health = new SeFeed(0.85 ether); // mat 170, gap 50
        vault = new ParameterizedVault(
            address(imd), address(0), address(0), address(primary), address(health), address(new SeMirror(primary))
        );
        stable = vault.stablecoin();
        vm.startPrank(APPROVED_OPERATOR);
        imd.mint(OTHER, 100_000 ether);
        imd.mint(NEW, 100_000 ether);
        vm.stopPrank();
        vm.prank(OTHER);
        imd.approve(address(vault), type(uint256).max);
        vm.prank(NEW);
        imd.approve(address(vault), type(uint256).max);

        // OTHER: 2,000 IMD against 1,000 imdUSD, warm; IMD falls to $0.40: backing 800 / 1,000 = 0.8.
        vm.startPrank(OTHER);
        vault.lock(2_000 ether);
        vault.draw(1_000 ether);
        stable.transfer(HOLDER, 1_000 ether);
        vm.stopPrank();
        vm.warp(block.timestamp + 2 days);
        primary.set(usd(0.4 ether));
        assertEq(vault.backingPerUnit(), 0.8e18, "the book: 800 of collateral value against 1,000 imdUSD");
    }

    /// Ten fresh helpers in ONE transaction, the same 10,000 IMD reused. Afterwards the vault holds exactly
    /// OTHER's 2,000 IMD against the same 1,000 imdUSD. EXPECTED: backing 0.8 and a 100 imdUSD redemption paid
    /// about 199 IMD. ACTUAL: 3,520 of stranded excess in the supply, backing 0.177, the redemption paid 42 IMD.
    function test_aGasOnlyPumpStrandsExcessAndCollapsesTheRedemptionPayout() public {
        SePump pump = new SePump();
        vm.prank(APPROVED_OPERATOR);
        imd.mint(address(pump), 10_000 ether);
        pump.run(vault, imd, 10_000 ether, 2_352 ether, 352 ether, 10);
        vm.warp(block.timestamp + 12);
        assertEq(vault.totalDebt(), 1_000 ether, "only OTHER's debt is open");
        assertEq(imd.balanceOf(address(vault)), 2_000 ether, "only OTHER's collateral is in the vault");
        assertEq(stable.totalSupply(), 1_000 ether, "only OTHER's imdUSD exists");
        uint256 backing = vault.backingPerUnit();
        emit log_named_uint("backingPerUnit after the pump", backing);
        vm.prank(HOLDER);
        uint256 paid = vault.cash(100 ether, 0, OTHER);
        emit log_named_uint("cash(100) paid (raw IMD)", paid);
        assertGe(backing + 1e15, 0.8e18, "closed positions must leave nothing in the supply backing is measured against");
    }

    /// One honest borrower, three transactions: open at 170%, repay a slice, repay the rest and withdraw.
    /// EXPECTED: backing back at 0.8. ACTUAL: 0.59, the slice stranded in `_excess` for hours.
    function test_anHonestTwoStepUnwindStrandsItsFirstSlice() public {
        vm.startPrank(NEW);
        vault.lock(10_000 ether);
        vault.draw(2_352 ether);
        vm.stopPrank();
        vm.prank(NEW);
        vault.wipe(352 ether);
        vm.startPrank(NEW);
        vault.wipe(vault.debtOf(NEW));
        vault.free(10_000 ether);
        vm.stopPrank();
        (uint256 collateral, uint256 debt) = vault.positions(NEW);
        assertEq(collateral + debt, 0, "NEW left nothing behind");
        uint256 backing = vault.backingPerUnit();
        emit log_named_uint("backingPerUnit after the unwind", backing);
        assertGe(backing + 1e15, 0.8e18, "an honest full unwind must not lower what every redeemer is paid");
    }
}
```

### 2. Medium: CDPVault.wipe measures the term's fall against the term as last written, so a lock one transaction earlier (a cold rise toward the debt bound) absorbs the repayment's fall and nothing is kept in the s

`src/CDPVault.sol:571`

```
            uint256 fell = termBefore > position.secured ? termBefore - position.secured : 0;
```

Q2 (58f73de regressed by ordering). The term is min(collateral, 2 x principal / price). A borrower whose term is its whole collateral (170-200% band) first locks E so the term rises toward the debt bound (the rise is cold in `_lag`; `lock` needs no feed). Its `wipe` of W then re-secures to min(C + E, 2 (P - W) / price): the term falls by at least W in value once E is large enough, so line 572 adds `repaid - min(repaid, fellValue)` == 0 to the excess. `_lag` takes that fall out of the position's own cold first (the lock's rise), so the LAGGED secured figure does not move either, while the lagged debt falls by W (warm principal). The redemption in between is paid V / (S - W) instead of V / S, live and lagged alike; the borrower then frees E (the term is debt-bound, so the free moves nothing and releases nothing) and redraws W. The position ends exactly where a direct wipe of W leaves it, but with Position.excess == 0 where the direct wipe records W. The committed fix works for the plain wipe / cash / draw order (verified: a direct wipe one transaction before the same cash pays the honest figure) and not for this one. Reachable with the constants as committed, below par only (the figure is capped at 1e18), as five separate transactions in ONE block (cooling at elapsed 0 is identity), for gas and E held for two transactions; also through `lockIMD`. Bound: W up to 15% of the churner's principal at mat 170 (25% at 150%, since `wipe` has no health check), premium (S - fresh) / (S - fresh - W), paid out of the Treasury's reserve (reserve-funded cash) or the other holders' backing (position-funded). Comments at 564-568 and 776-782 ('a repayment, a redemption and a redraw in three transactions pay the redemption no premium', 'nor one a TRANSACTION earlier') do not hold. Call sequence: BORROWER lock(540e18); wipe(140e18); cash(500e18, 0, BORROWER); free(540e18); draw(140e18). Smallest fix: measure the fall against the WARM term only: have `_resecureBounded` keep the cold part of the secured decrease that `_lag` already returns (`coldFall = _lag(position, true, before, current)`, in a private storage word or a return value threaded through `_reduceDebt`), and in `wipe` use `fell -= min(fell, coldFall)` before valuing it. A lock-then-wipe then keeps W exactly as a direct wipe does; the committed test/retry-panel/AdjacentTxBurn.t.sol tests are unaffected. Distinct from the free / lock finding (line 481): that one restores the term WARM from the secured bank after the excess was released; this one never records the excess. From audit_flow (medium).

**Reproduction**

test/scratch/Proof_LockThenWipe.t.sol (attached; fails on this code). ParameterizedVault over MockIMD at $1 (Chainlink 2000e8 etched), NHI 0.85 (mat 170, gap 50), launch constants, Treasury empty. BORROWER lock(5_790e18) draw(1_000e18); OTHER lock(5_100e18) draw(3_000e18) and hands BORROWER the 3,000 imdUSD; IMD to $0.294 (BORROWER 170.2%, OTHER 50%); both lock(1); three quiet days; backingPerUnit() == 0.8004e18. Honest reference (snapshot, reverted): BORROWER cash(500e18, 0, BORROWER) pays 1293187500000000000000 raw IMD; a direct wipe(140e18) one transaction before the same cash pays the same 1293187500000000000000 (the committed fix holds for that order). Churn, five transactions, no time passing: lock(540e18); wipe(140e18) [excessNow() == 0 afterwards]; cash(500e18, 0, BORROWER); free(540e18); draw(140e18). EXPECTED: payout <= 1294480687500000000000 (honest + 0.1%). ACTUAL: 1339963990912350394557 (+3.6%).

**Proof**: a Foundry test that fails on this code and passes once it is fixed.

```solidity
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;

// CDPVault.wipe measures the backing a repayment left behind as the repayment less the secured term's fall
// INSIDE the wipe call, against the term as last written. A lock one transaction earlier raised the term (cold)
// toward the debt bound; the wipe's re-secure then nets that rise against the repayment's fall, so the fall
// reads as at least the repayment and nothing is kept in the supply. `_lag` takes the fall out of the position's
// own cold (the lock's rise), so the lagged numerator does not move either. The redemption in between is paid
// the repay-then-redeem premium 58f73de set out to close, in five transactions of one block, for gas.

import {Test} from "forge-std/Test.sol";
import {ParameterizedVault} from "src/ParameterizedVault.sol";
import {ImdUSD} from "src/ImdUSD.sol";
import {MockIMD} from "src/MockIMD.sol";
import {TreasuryFactory} from "src/TreasuryFactory.sol";
import {ISwarmFeed} from "src/interfaces/ISwarmFeed.sol";
import {APPROVED_OPERATOR, CHAINLINK_ETH_USD, TREASURY_FACTORY} from "src/DeploymentConfig.sol";

contract LwFeed is ISwarmFeed {
    uint256 public constant maxAge = 1 days;
    uint256 private value;
    uint64 private updatedAt;

    constructor(uint256 v) {
        value = v;
        updatedAt = uint64(block.timestamp);
    }

    function set(uint256 v) external {
        value = v;
        updatedAt = uint64(block.timestamp);
    }

    function latestValue() external view returns (uint256, uint64) {
        return (value, updatedAt);
    }

    function isStale() external pure returns (bool) {
        return false;
    }
}

contract LwMirror is ISwarmFeed {
    ISwarmFeed private immutable primary;

    constructor(ISwarmFeed p) {
        primary = p;
    }

    function latestValue() external view returns (uint256, uint64) {
        return primary.latestValue();
    }

    function isStale() external view returns (bool) {
        return primary.isStale();
    }

    function maxAge() external view returns (uint256) {
        return primary.maxAge();
    }
}

contract LwAggregator {
    function decimals() external pure returns (uint8) {
        return 8;
    }

    function latestRoundData() external view returns (uint80, int256, uint256, uint256, uint80) {
        return (1, 2000e8, block.timestamp, block.timestamp, 1);
    }
}

contract LockThenWipeTest is Test {
    address private constant BORROWER = address(0xB0B);
    address private constant OTHER = address(0x07E);

    MockIMD private imd;
    LwFeed private primary;
    ParameterizedVault private vault;
    ImdUSD private stable;

    function usd(uint256 dollars) private pure returns (uint256) {
        return dollars / 2000;
    }

    function setUp() public {
        if (TREASURY_FACTORY.code.length == 0) vm.etch(TREASURY_FACTORY, address(new TreasuryFactory()).code);
        vm.etch(CHAINLINK_ETH_USD, address(new LwAggregator()).code);
        vm.warp(1_000_000);
        imd = new MockIMD();
        primary = new LwFeed(usd(1 ether)); // IMD = $1
        LwFeed health = new LwFeed(0.85 ether); // mat 170, gap 50
        vault = new ParameterizedVault(
            address(imd), address(0), address(0), address(primary), address(health), address(new LwMirror(primary))
        );
        stable = vault.stablecoin();
        vm.startPrank(APPROVED_OPERATOR);
        imd.mint(BORROWER, 10_000 ether);
        imd.mint(OTHER, 10_000 ether);
        vm.stopPrank();
        vm.prank(BORROWER);
        imd.approve(address(vault), type(uint256).max);
        vm.prank(OTHER);
        imd.approve(address(vault), type(uint256).max);

        // The retry panel's below-par book: the borrower in the 170-200% band, OTHER underwater, all warm.
        vm.startPrank(BORROWER);
        vault.lock(5_790 ether);
        vault.draw(1_000 ether);
        vm.stopPrank();
        vm.startPrank(OTHER);
        vault.lock(5_100 ether);
        vault.draw(3_000 ether);
        stable.transfer(BORROWER, 3_000 ether);
        vm.stopPrank();
        primary.set(usd(0.294 ether)); // borrower 170.2%, OTHER 50%
        vm.prank(BORROWER);
        vault.lock(1);
        vm.prank(OTHER);
        vault.lock(1);
        vm.warp(block.timestamp + 3 days);
        assertApproxEqRel(vault.backingPerUnit(), 0.8004e18, 1e15, "below par");
    }

    /// Five transactions in one block: lock 540, wipe 140, cash 500 against self, free 540, draw 140.
    /// The position ends where a direct wipe of 140 leaves it, but the direct wipe keeps 140 in the supply
    /// (Position.excess) and this order keeps nothing. EXPECTED: the honest payout. ACTUAL: +3.6%.
    function test_aLockBeforeTheWipeMasksTheFallAndPaysThePremium() public {
        uint256 snap = vm.snapshotState();
        vm.prank(BORROWER);
        uint256 honest = vault.cash(500 ether, 0, BORROWER);
        vm.revertToState(snap);

        vm.prank(BORROWER);
        vault.lock(540 ether);
        vm.prank(BORROWER);
        vault.wipe(140 ether);
        vm.prank(BORROWER);
        uint256 churned = vault.cash(500 ether, 0, BORROWER);
        vm.prank(BORROWER);
        vault.free(540 ether);
        vm.prank(BORROWER);
        vault.draw(140 ether);
        emit log_named_uint("honest payout (raw IMD)", honest);
        emit log_named_uint("churned payout (raw IMD)", churned);
        assertLe(churned, honest + honest / 1_000, "a lock before the repayment must not raise what a redemption is paid");
    }
}
```

### 3. Medium: CDPVault.free releases Position.excess by the term's fall while _lag banks that same fall warm, so a lock one transaction later restores the term warm from bankSecured with no excess behind it: wipe,

`src/CDPVault.sol:481`

```
            _moveExcess(position, false, 0, Math.mulDiv(termBefore - position.secured, price, 1e18));
```

Q2 (58f73de) and Q3 (the secured bank). `wipe` keeps in `Position.excess` the principal repaid that the term did not follow (a position in the 170-200% band). `free` then releases the excess by the term's fall at the current price (line 481, 'The backing a repayment left behind has now left too'). But that term decrease was just banked WARM by `_lag` inside `_resecure` (`position.bankSecured`, `bank += out - coldOut`), and `lock` / `lockIMD` credit a term increase from that bank with no `_moveExcess` of their own. So, in separate transactions: wipe W (term unchanged, excess W); free collateral worth at least W (excess released to zero, the fall banked); lock the same collateral back (the term is back and reads warm in `laggedNow`, excess still zero); cash against any candidate or the reserve (paid against supply - W with the backing unchanged, live and lagged); draw W. The vault ends where it began and the redemption was paid the premium (S - fresh) / (S - fresh - W). Cost: gas and five transactions (or four: wipe + free in one call), no seasoned capital beyond what the churner holds; below par only; W bounded by the churner's band slack (up to 15% of its principal at 170%). Who loses: the Treasury's sIMD reserve or the candidate's collateral, and every other holder's backing, per redemption, repeatable. Reachable with the constants as committed, wage 0, no governance. Comments that do not hold: 479, 565-567, 776-782. Call sequence: CHURNER wipe(W); free(W / price); lock(W / price); anyone cash(amount, 0, candidate); CHURNER draw(W). Smallest fix, two options: (a) in `_lag`'s secured increase branch, when `credit != 0` re-add to the position's excess min(credit x price, what `free` released from it), which needs the released amount kept per position (a uint128 cooled like the others); or (b) no new storage: do not release the excess in `free` at all, letting only `draw` (the supply returning) and time (the six-hour half-life) release it, accepting that an honest repay-and-withdraw leaves its excess to decay for a few hours (redeemers are then paid the pre-repayment figure, the direction 58f73de already chose for the repayment itself); (b) removes lines 476-482's `termBefore` / `price` locals and shrinks the contract. Note (b) must be combined with the release-on-fall fix of the high finding (line 572) in a form that does not reopen this path: clamping the excess to the term's value releases it in `free` again, so the clamp should skip (or the bank should forget) the part of the fall a `free` caused, e.g. reduce `position.bankSecured` by the IMD whose excess release it credited. Merged from audit_permissions (medium) and audit_math (medium), both reproduced.

**Reproduction**

test/scratch/Proof_cf806f207acd.t.sol (attached, audit_math's proof; fails on this code) and test/scratch/Proof_e07a65886ac8.t.sol (audit_permissions', reserve-funded variant; also fails). Position-funded: ParameterizedVault over MockIMD at $1 (Chainlink 2000e8 etched), NHI 0.85, wage 0. BORROWER locks 5,790 and draws 1,000; OTHER locks 5,100, draws 3,000 and hands BORROWER the 3,000 imdUSD; IMD to $0.294 (BORROWER 170.2%, OTHER 50%), both re-priced by lock(1); three quiet days: backingPerUnit() == 0.8004e18. Honest (snapshot): BORROWER cash(500e18, 0, BORROWER) pays 1293187500000000000000 raw IMD. Then wipe(140e18) [excess 140]; free(500e18) [worth 147: excess released, bankSecured 500]; lock(500e18) [securedCollateral back to 5,790 + 5,100 + 2, laggedNow().secured within 0.1% of it]; cash(500e18, 0, BORROWER); draw(140e18). EXPECTED: at most 1294480687500000000000. ACTUAL: 1339790057161054981292 (+3.6%). Reserve-funded (Proof_e07a65886ac8): UNDERWATER 5,100 / 3,000, CHURNER 18,000 / 1,000, Treasury 1,000 IMD, IMD to $0.10, three quiet days, backingPerUnit() 0.6025e18; honest cash(100e18, 0, address(0)) by a holder pays 591956250000000000000; after CHURNER wipe(100e18), free(995.13e18), lock(995.13e18): ACTUAL 606897935995404173000 (+2.5%, backingPerUnit() 0.6178e18 = 0.6025 x 4000 / 3900.5).

**Proof**: a Foundry test that fails on this code and passes once it is fixed.

```solidity
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;

// The supply kept for a repayment that left its backing behind (CDPVault.Position.excess, 58f73de) is released
// by `free` by the term's fall, and the term's fall is banked warm (`_lag`), so a re-lock of the same collateral
// one transaction later is credited warm and restores the term with no excess behind it. wipe / free / lock /
// cash / draw, five transactions for gas, pays the redemption the premium the retry panel's medium #4 closed.

import {Test} from "forge-std/Test.sol";
import {ParameterizedVault} from "src/ParameterizedVault.sol";
import {ImdUSD} from "src/ImdUSD.sol";
import {MockIMD} from "src/MockIMD.sol";
import {TreasuryFactory} from "src/TreasuryFactory.sol";
import {ISwarmFeed} from "src/interfaces/ISwarmFeed.sol";
import {APPROVED_OPERATOR, CHAINLINK_ETH_USD, TREASURY_FACTORY} from "src/DeploymentConfig.sol";

contract FrFeed is ISwarmFeed {
    uint256 public constant maxAge = 1 days;
    uint256 private value;
    uint64 private updatedAt;

    constructor(uint256 v) {
        value = v;
        updatedAt = uint64(block.timestamp);
    }

    function set(uint256 v) external {
        value = v;
        updatedAt = uint64(block.timestamp);
    }

    function latestValue() external view returns (uint256, uint64) {
        return (value, updatedAt);
    }

    function isStale() external pure returns (bool) {
        return false;
    }
}

contract FrMirror is ISwarmFeed {
    ISwarmFeed private immutable primary;

    constructor(ISwarmFeed p) {
        primary = p;
    }

    function latestValue() external view returns (uint256, uint64) {
        return primary.latestValue();
    }

    function isStale() external view returns (bool) {
        return primary.isStale();
    }

    function maxAge() external view returns (uint256) {
        return primary.maxAge();
    }
}

contract FrAggregator {
    function decimals() external pure returns (uint8) {
        return 8;
    }

    function latestRoundData() external view returns (uint80, int256, uint256, uint256, uint80) {
        return (1, 2000e8, block.timestamp, block.timestamp, 1);
    }
}

contract FreeRelockPremiumTest is Test {
    address private constant BORROWER = address(0xB0B);
    address private constant OTHER = address(0x07E);

    MockIMD private imd;
    ParameterizedVault private vault;
    ImdUSD private stable;
    FrFeed private primary;

    function setUp() public {
        if (TREASURY_FACTORY.code.length == 0) vm.etch(TREASURY_FACTORY, address(new TreasuryFactory()).code);
        vm.etch(CHAINLINK_ETH_USD, address(new FrAggregator()).code);
        vm.warp(1_000_000);
        imd = new MockIMD();
        primary = new FrFeed(uint256(1 ether) * 1e18 / 2000 ether); // IMD = $1
        FrFeed health = new FrFeed(0.85 ether); // mat 170, gap 50
        vault = new ParameterizedVault(
            address(imd), address(0), address(0), address(primary), address(health), address(new FrMirror(primary))
        );
        stable = vault.stablecoin();
        vm.startPrank(APPROVED_OPERATOR);
        imd.mint(BORROWER, 10_000 ether);
        imd.mint(OTHER, 10_000 ether);
        vm.stopPrank();
        vm.prank(BORROWER);
        imd.approve(address(vault), type(uint256).max);
        vm.prank(OTHER);
        imd.approve(address(vault), type(uint256).max);

        // The retry panel's below-par book: the borrower in the 170-200% band, OTHER underwater, all warm.
        vm.startPrank(BORROWER);
        vault.lock(5_790 ether);
        vault.draw(1_000 ether);
        vm.stopPrank();
        vm.startPrank(OTHER);
        vault.lock(5_100 ether);
        vault.draw(3_000 ether);
        stable.transfer(BORROWER, 3_000 ether);
        vm.stopPrank();
        primary.set(uint256(0.294 ether) * 1e18 / 2000 ether); // borrower 170.2%, OTHER 50%
        vm.prank(BORROWER);
        vault.lock(1);
        vm.prank(OTHER);
        vault.lock(1);
        vm.warp(block.timestamp + 3 days);
        assertApproxEqRel(vault.backingPerUnit(), 0.8004e18, 1e15, "below par");
    }

    /// Five transactions: wipe 140 (term unchanged, excess 140), free 500 IMD (worth 147: releases the whole
    /// excess, the term's fall banked warm), lock 500 (the term is back, credited warm from the bank, no excess),
    /// cash 500 against the borrower, draw 140. EXPECTED: the payout of a world with no churn. ACTUAL: the payout
    /// is measured against a supply 140 smaller with the same backing.
    function test_freeAndRelockRestoresTheRepayThenRedeemPremium() public {
        uint256 snap = vm.snapshotState();
        vm.prank(BORROWER);
        uint256 honest = vault.cash(500 ether, 0, BORROWER);
        vm.revertToState(snap);

        vm.prank(BORROWER);
        vault.wipe(140 ether);
        vm.prank(BORROWER);
        vault.free(500 ether);
        vm.prank(BORROWER);
        vault.lock(500 ether);
        assertEq(vault.securedCollateral(), 5_790 ether + 5_100 ether + 2, "the numerator is back where it stood");
        (, uint256 lagSecured) = vault.laggedNow();
        assertApproxEqRel(lagSecured, vault.securedCollateral(), 1e15, "and it reads warm (credited from the bank)");
        vm.prank(BORROWER);
        uint256 churned = vault.cash(500 ether, 0, BORROWER);
        vm.prank(BORROWER);
        vault.draw(140 ether);
        assertLe(churned, honest + honest / 1_000, "a repayment, a withdrawal and a re-lock must not raise the payout");
    }
}
```

### 4. Medium: CDPVault._cool / _lag: after a quiet day the vault's cold totals (and _excess, _feeExcess) read zero while each position's own cold keeps cooling, so the next position's cold is put into a total an ol

`src/CDPVault.sol:1038`

```
        if (elapsed >= (up ? BACKING_WARMUP : 256 * BACKING_HALF_LIFE)) return 0;
```

Q4 (d7fceab, the fix for retry2 low #7), Q1 and Q3. A position's own figures cool without the quiet-day cutoff (`_cool(..., up = false)` runs to 256 half-lives) while the vault totals `_coldDebt` / `_coldSecured` / `_excess` / `_feeExcess` still read zero once `block.timestamp - _coldAt >= BACKING_WARMUP` (line 1038, up = true). The NatSpec at 1033-1035 says the total is then below the sum 'and every subtraction from it saturates'; that holds only until another position adds cold. OLD draws D; nobody calls `_lag` for a day (lock, free with a term change, draw, wipe, bite, cover, cash all do: a quiet night at launch); NEW draws E (`_lag` cools the totals to zero, then adds E: the totals hold exactly NEW's cold); OLD repays D: its own cold is D / 16, `coldOut = min(D / 16, D)`, and `total = total - coldOut` (line 1002) takes D / 16 out of NEW's E. With D >= 16 E, NEW's one-second-old principal and term read fully warm in `laggedNow`: the lagged `_backingPerUnit` counts them (overpaying redeemers below par: D1's borrow / redeem / repay / withdraw round trip in miniature, OLD and NEW may be the same actor), `_feeBase` no longer subtracts E (the retry2 medium #3 dilution by a new path), and once a wage is set `ParameterizedVault.backedDebt` counts E for the work ceiling. The same for `_excess` (an old position's draw releases its orphaned excess from the total that holds only newer repayments: supply understated, backing overstated) and `_feeExcess`. Bounded by 1 / 16 of the old position's capital per quiet day, which is why this is medium and not the high the retry panel gave the unbounded version. Reachable with the constants as committed. Comments that claim the property the code does not have: CDPVault 322-323 and 1033-1035, ParameterizedVault 239-240. Call sequence: OLD lock, draw(D); warp 1 day + 1; NEW lock, draw(E); OLD wipe(D). Smallest fix: cool the vault totals `_coldDebt`, `_coldSecured`, `_excess` and `_feeExcess` without the BACKING_WARMUP cutoff, exactly as the positions are cooled (keep rounding up; keep the cutoff for the per-position banks, where it is harmless): the totals are then at least the sum of the positions always, up to `_pow`'s rounding, and a decrease never removes more than the position put there. `_pow` over a long gap is at most about 27 squarings. 'A quiet day credits in full' (line 318-319) becomes 'a quiet day credits 15 / 16', the honest figure. From audit_math (medium), reproduced.

**Reproduction**

test/scratch/Proof_9f43449e679b.t.sol (attached, audit_math's proof; fails on this code). ParameterizedVault over MockIMD at $1 (Chainlink 2000e8 etched), NHI 0.85, wage 0. OLD locks 32,000 and draws 16,000. Warp 1 day + 1 second with no other call. NEW locks 2,000 and draws 1,000, hands OLD 100 imdUSD. laggedNow() == (16,000e18, 32,000e18): OLD warm, NEW cold, as designed. OLD wipe(16,000e18). EXPECTED: laggedNow().debt <= 3e18 (OLD's ~2 of fee-turned-principal, warm) and .secured <= 6e18, NEW's one-second-old 1,000 and 2,000 still cold. ACTUAL: laggedNow() == (1001914234264134354012, 2003828468528268708025): NEW's principal and term read warm in full. Control (the specialist's, same sequence with one draw(1e18) by a third position at the 12-hour mark so the day is not quiet): laggedNow().debt == 2696331918907323218.

**Proof**: a Foundry test that fails on this code and passes once it is fixed.

```solidity
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;

// After d7fceab a position's own cold cools without the quiet-day cutoff while the vault totals read zero after a
// quiet BACKING_WARMUP. The next position to draw puts its cold into a total that no longer holds the old
// position's share; the old position's repayment then takes its own (continuously cooled) cold out of that
// total, which is the new position's. What one position removes warms what another added.

import {Test} from "forge-std/Test.sol";
import {ParameterizedVault} from "src/ParameterizedVault.sol";
import {ImdUSD} from "src/ImdUSD.sol";
import {MockIMD} from "src/MockIMD.sol";
import {TreasuryFactory} from "src/TreasuryFactory.sol";
import {ISwarmFeed} from "src/interfaces/ISwarmFeed.sol";
import {APPROVED_OPERATOR, CHAINLINK_ETH_USD, TREASURY_FACTORY} from "src/DeploymentConfig.sol";

contract QdFeed is ISwarmFeed {
    uint256 public constant maxAge = 1 days;
    uint256 private value;
    uint64 private updatedAt;

    constructor(uint256 v) {
        value = v;
        updatedAt = uint64(block.timestamp);
    }

    function latestValue() external view returns (uint256, uint64) {
        return (value, updatedAt);
    }

    function isStale() external pure returns (bool) {
        return false;
    }
}

contract QdMirror is ISwarmFeed {
    ISwarmFeed private immutable primary;

    constructor(ISwarmFeed p) {
        primary = p;
    }

    function latestValue() external view returns (uint256, uint64) {
        return primary.latestValue();
    }

    function isStale() external view returns (bool) {
        return primary.isStale();
    }

    function maxAge() external view returns (uint256) {
        return primary.maxAge();
    }
}

contract QdAggregator {
    function decimals() external pure returns (uint8) {
        return 8;
    }

    function latestRoundData() external view returns (uint80, int256, uint256, uint256, uint80) {
        return (1, 2000e8, block.timestamp, block.timestamp, 1);
    }
}

contract QuietDayOrphanTest is Test {
    address private constant OLD = address(0x01D);
    address private constant NEW = address(0x0E3);

    MockIMD private imd;
    ParameterizedVault private vault;
    ImdUSD private stable;

    function setUp() public {
        if (TREASURY_FACTORY.code.length == 0) vm.etch(TREASURY_FACTORY, address(new TreasuryFactory()).code);
        vm.etch(CHAINLINK_ETH_USD, address(new QdAggregator()).code);
        vm.warp(1_000_000);
        imd = new MockIMD();
        QdFeed primary = new QdFeed(uint256(1 ether) * 1e18 / 2000 ether); // IMD = $1
        QdFeed health = new QdFeed(0.85 ether); // mat 170, gap 50
        vault = new ParameterizedVault(
            address(imd), address(0), address(0), address(primary), address(health), address(new QdMirror(primary))
        );
        stable = vault.stablecoin();
        vm.startPrank(APPROVED_OPERATOR);
        imd.mint(OLD, 100_000 ether);
        imd.mint(NEW, 100_000 ether);
        vm.stopPrank();
        vm.prank(OLD);
        imd.approve(address(vault), type(uint256).max);
        vm.prank(NEW);
        imd.approve(address(vault), type(uint256).max);
    }

    /// OLD draws 16,000; the vault is quiet for a day and a second (the totals read zero, OLD's own cold is
    /// 1,000). NEW draws 1,000 (cold, the totals hold exactly it). OLD repays: its 1,000 of cold comes out of the
    /// totals, which held only NEW's. EXPECTED: NEW's second-old 1,000 of principal and 2,000 of term stay cold.
    /// ACTUAL: laggedNow reads them warm in full.
    function test_anOldPositionsRepaymentAfterAQuietDayWarmsANewPositionsCapital() public {
        vm.startPrank(OLD);
        vault.lock(32_000 ether);
        vault.draw(16_000 ether);
        vm.stopPrank();
        vm.warp(block.timestamp + 1 days + 1);
        vm.startPrank(NEW);
        vault.lock(2_000 ether);
        vault.draw(1_000 ether);
        stable.transfer(OLD, 100 ether); // OLD's day of stability fee
        vm.stopPrank();
        (uint256 lagDebt, uint256 lagSecured) = vault.laggedNow();
        assertEq(lagDebt, 16_000 ether, "OLD is warm after a quiet day, NEW is cold");
        assertEq(lagSecured, 32_000 ether, "OLD's term is warm, NEW's is cold");
        vm.prank(OLD);
        vault.wipe(16_000 ether);
        (lagDebt, lagSecured) = vault.laggedNow();
        // What is left: OLD's day of fee (about 2 imdUSD of principal, warm) and NEW's 1,000, one second old.
        assertLe(lagDebt, 3 ether, "NEW's one-second-old principal must stay cold after OLD's repayment");
        assertLe(lagSecured, 6 ether, "NEW's one-second-old term must stay cold after OLD's repayment");
    }
}
```

### 5. Medium: CDPVault.wipe adds the whole repaid principal to Position.excess, cold or warm, so a cold draw-and-repay by a position in the 170-200% band leaves phantom supply in the backing denominator for hours:

`src/CDPVault.sol:569`

```
        uint256 repaid = principalBefore - position.debt;
```

Q2 (58f73de: 'inflated', 'an honest repay-and-withdraw is not underpaid'). `_reduceDebt` adds only the WARM part of a repayment to `feeExcess` (`principalPaid - coldOut`, line 1402), because principal that was never warm was never in the lagged supply. `wipe` has no such distinction: line 569-572 adds `repaid - min(repaid, fell x price)` to `excess` where `repaid` is the whole principal retired. A position whose term is its collateral draws D (cold: `_lag` adds D to its cold and to `_coldDebt`; the term does not move while collateral-bound) and repays D in the next transaction or the same one: `_lag` retires the D of cold (`coldOut = D`), the term still does not move, and `excess` gains D. The vault is exactly where it was (same debt, collateral, live supply, cold) but `_backingPerUnit` measures backing against live + D for the next hours (half after six) in BOTH figures: the live one (B / (S + D)) and the lagged one (B_w / (S + D - fresh), whose denominator the cold principal had already left, so the burn should have been neutral). Every redemption below par is paid D / S less; a candidate gives up D / S less collateral per imdUSD cancelled against it (2.8% in the proof at 191%). D is bounded by the position's draw room above mat (up to 17.6% of its principal at 200%), re-armed every block for gas (the draw releases the excess, the wipe re-adds it, so it does not stack but never ages), no seasoned capital beyond the position; a dominant borrower ($1M line) can hold a discount of ~15% of its principal over the supply through a crash, when redemptions defend the peg. Honest newcomers cause it too (a 170% loan repaid in part the same day). Reachable with the constants as committed, wage 0, below par only. The NatSpec at 773-774 ('honest redemptions are not underpaid') does not hold here. Call sequence: BORROWER draw(D); wipe(D); REDEEMER cash(amount, 0, BORROWER). Smallest fix: base the excess on the warm part of the repayment only, as `_reduceDebt` already does for `feeExcess`: have `_reduceDebt` return `coldOut` (it already computes it) and in `wipe` use `warm = principalPaid - coldOut; add = warm > fellValue ? warm - fellValue : 0`. The committed test/retry-panel/AdjacentTxBurn.t.sol (a warm 140 in the band) is unchanged by it. Merged from audit_permissions (low), audit_math (medium) and audit_economics (medium), all three reproducing the same mechanism; not given a proof only because of the four-proof cap, the specialist's proof is in test/scratch/Proof_4ad9a6b9f3e8.t.sol and fails as stated.

**Reproduction**

test/scratch/Proof_4ad9a6b9f3e8.t.sol (audit_math's proof, run here: fails with 'a cold draw and repayment must not lower the payout: 1338716019417475726237 < 1377500000000000000000'). ParameterizedVault over MockIMD at $1 (Chainlink 2000e8 etched), NHI 0.85 (mat 170), wage 0. BORROWER locks 6,500 and draws 1,000; OTHER locks 5,100, draws 3,000, hands REDEEMER 2,000 and BORROWER 500 imdUSD; IMD to $0.294 (BORROWER 191%, OTHER 50%), both re-priced by lock(1); three quiet days; backingPerUnit() 0.8525e18. Honest payout (snapshot): REDEEMER cash(500e18, 0, BORROWER) pays 1377500000000000000000 raw IMD. Then BORROWER draw(120e18) and wipe(120e18) in two transactions: debtOf(BORROWER) and securedCollateral exactly as before. REDEEMER cash(500e18, 0, BORROWER). EXPECTED: the honest payout within 0.1%. ACTUAL: 1338716019417475726237, 2.8% less: the supply read 4,120 (120 of phantom excess) against the same backing. The newcomer variant (audit_permissions, test described as ColdRepayExcess): NEWCOMER opens 51,000 IMD / 3,000 at 170% at $0.10, cold; wipe(450e18) raises the lagged denominator from 3,001 to 3,451 and a holder's cash(100e18) falls from 199.48 to 173.47 IMD (13% less).

### 6. Low: The cost of the accepted fee-base trade-off is understated: a dust (same block) or 5 imdUSD (12 s later) reserve-funded redemption after a large draw STORES the 4.5% cap as redemptionBaseRate, which d

`src/CDPVault.sol:908`

```
        uint256 increase = amount == 0 ? 0 : prior == 0 ? cap : Math.mulDiv(amount, 1e18, prior) / redemptionDivisor();
```

Q1, the cost of the accepted trade-off (retry2 #3). The accepted reason is right: new supply must not dilute the fee, so `_feeBase` is the warm supply and, while most supply is cold, a redemption pays up to the cap. The stated cost ('early redemptions, and those right after a large draw, pay more') describes the QUOTE and not the STORED rate. `_redemptionRate` turns a zero base into `cap` for any nonzero amount (line 908), and a tiny base into the cap for a few imdUSD; `cash` stores `base` unchanged when the burn has no fresh part (line 747), which every reserve-funded burn lacks (principalCancelled = 0, so freshCancelled = 0). The stored rate decays at REDEMPTION_SECOND_DECAY (twelve-hour half-life) while the cold principal that made the base small warms at BACKING_HALF_LIFE (six hours): when 7 / 8 of the supply is warm (18 h) the honest increase for a 1 imdUSD burn is under a basis point and the quote is 210; 163 at 24 h, 79 at 48 h. The peg floor min(1 - fee, backing) is 0.955 at launch and stays about 0.979-0.984 through the second day because of the pin, where without it the fee would already be at the floor. The candidate route does not pin (a fresh candidate's principal is `freshCancelled`, so the stored rate is `_redemptionRate(amount - freshCancelled)`, about zero), the reserve route does, and anyone can open the reserve route by transferring a few IMD to the Treasury (`redemptionReserve` is `gem.balanceOf(treasury)`, listed or not; the runbook also seeds it and the first liquidation's bonus share lands there). Cost to the pinner: 1e-12 imdUSD in the draw's block, or 5 imdUSD at 5% twelve seconds later, plus a 6 IMD donation it redeems back, and gas. Not an attack on funds; a quantified launch cost the resolution did not state, repeatable whenever the warm supply is small (launch, or after the warm supply turns over). Reachable with the constants as committed. Call sequence: P lock, draw(100_000e18); R transfers 6 IMD to vault.treasury(); R cash(5e18, 0, address(0)). Smallest fix: store the increase measured against max(prior, live supply / 2) (or against the live supply less this transaction's mints) while still CHARGING the redeemer against `prior`; or, when `prior == 0`, keep the decayed rate instead of storing the cap; or bound the stored increase by min(cap, amount / live / divisor) so dust can never store the cap. Or document the number in docs/MAINNET-RUNBOOK.md and open redemptions a day after the first draws. Merged from audit_permissions (low) and audit_math (low).

**Reproduction**

test/scratch/Leads.t.sol test_launchPinViaReserve (fails on this code at the stated figures; logs). ParameterizedVault over MockIMD at $1 (Chainlink 2000e8 etched), NHI 0.85, launch constants (divisor 2). P lock(200_000e18), draw(100_000e18), hands R 2,000 imdUSD; the Treasury is given 6 IMD. (a) Same block as the draw: feeBase() == 0; R cash(1e6, 0, address(0)) [1e-12 imdUSD]: redemptionBaseRate == 45000000000000000 (the cap). (b) Twelve seconds later: feeBase() == 38500763251036729981 (38.5 of 100,000 warm), redemptionFeeBps(5e18) == 500; R cash(5e18, 0, address(0)): redemptionBaseRate == 45000000000000000. Then redemptionFeeBps(1e18) reads 369 at +6 h, 210 at +18 h (feeBase 87,499: the honest increase for 1 imdUSD is 1 / 87,499 / 2, under a basis point, so EXPECTED 51), 163 at +24 h, 79 at +48 h. EXPECTED (the accepted cost, the quote only): the stored rate about 1.9e13 after a position-funded burn against P, and 51 bps at every later reading.

### 7. Low: CDPVault.wipe: a full repayment of an underwater position adds its shortfall (debt minus collateral value) to Position.excess although the position then owes nothing and its term is zero, so retired u

`src/CDPVault.sol:570`

```
        if (repaid != 0) {
```

Q2 ('inflated'). The excess is `repaid - min(repaid, fell x price)`: the principal whose backing did not follow it out. For a position below 100% that repays everything, the term (its whole collateral) falls to zero, `fell x price` is the collateral's value, and the difference `debt - collateral value` is credited as 'backing left behind' although no backing is left. The supply backing is measured against is overstated by the shortfall for the next hours (half after six) in both the live and the lagged figure, so every redemption pays less than the honest pro-rata figure. The same happens in the outage case the comment at 565-568 accepts, where a full repayment keeps the whole repaid amount (low finding, line 568). Safe for the protocol, costly for redeemers in exactly the crash in which an underwater borrower repaying in full is the behaviour the protocol wants; it needs a borrower who repays more than its collateral is worth, which bounds the severity. Reachable with the constants as committed. Call sequence: P wipe(debtOf(P)) with P below 100%; R cash(amount, 0, Q). Smallest fix: cap the excess at the backing that remains, `min(repaid - fellValue, position.secured x price / 1e18)`, which is zero when the position owes nothing and leaves the 170-200% case (term unchanged, excess = repaid) exactly as it is; the clamp proposed for the high finding (line 572) does the same in one place. From audit_math (low), reproduced.

**Reproduction**

test/scratch/Leads.t.sol test_underwaterFullWipe (fails on this code). ParameterizedVault over MockIMD at $1 (Chainlink 2000e8 etched), NHI 0.85, wage 0. P locks 2,000 and draws 1,000; Q locks 2,000, draws 1,000, hands P 100 and R 500 imdUSD; three quiet days; IMD to $0.40 (both at 80%): backingPerUnit() == 0.8e18. P wipe(debtOf(P)) (about 1,000.37). EXPECTED: supply 1,000, backing 800 / 1,000 = 0.8e18 (Q's 2,000 IMD at $0.40 over Q's 1,000 of supply). ACTUAL: backingPerUnit() == 666666666666666666 with excessNow() == 200e18 (supply read 1,200 for a position that owes nothing and holds nothing).

### 8. Low: A wipe while the collateral price is unreadable scales the secured term down in proportion AND keeps the whole repayment as excess, so backing per imdUSD read after the outage is double-discounted for

`src/CDPVault.sol:568`

```
        // values the term's fall at nothing, the safe direction.
```

Q2 ('free releases it by the term's fall at a price that may be unreadable': `free` with debt is feed-gated and with no debt the term is zero before and after, so that release is never at price zero; `wipe` is the ungated path). When `_priceOrZero()` is 0 (UsdPriceFeed returns (0, 0) for a missing, non-positive, oversized or malformed Chainlink answer; SharePriceFeed for a share vault that stops answering) `_reduceDebt` already scales the term by debtAfter / debtBefore through `unpricedCap` (line 1409-1410, the sweep panel fix), so the backing followed the repayment down. `wipe` then values that fall at price 0 and adds the WHOLE repayment to `Position.excess` (line 572). Both halves of the same repayment are discounted: the numerator lost term x W / P and the denominator kept W. The comment at 567-568 calls this 'the safe direction'; it is a 30% underpayment in the reproduction, lasting until the position is touched again or the excess cools. `cash` is halted during the outage (stale feeds), so the harm lands on the first redemptions after it ends, reserve- and position-funded alike; a candidate benefits (its debt is cancelled for less collateral) and can cause it deliberately. Needs an ETH/USD outage (a stale-but-positive answer does NOT read as zero, so ETH_USD_MAX_AGE alone does not trigger it): reachable but infrequent with the constants as committed. Call sequence: (aggregator answers 0) BORROWER wipe(300e18); (aggregator recovers) anyone cash(...). Smallest fix: when the price is unreadable add nothing to the excess, since the proportional scaling already took the backing with the repayment: `_moveExcess(position, false, price == 0 ? 0 : repaid - min(repaid, fellValue), 0)`; or keep the term unscaled and the excess whole, but not both. Merged from audit_flow (low) and audit_economics (low), both reproduced.

**Reproduction**

test/scratch/Leads.t.sol test_outageDoubleDiscount (fails on this code). ParameterizedVault over MockIMD, launch constants, NHI 0.85, a Chainlink-shaped aggregator etched at CHAINLINK_ETH_USD whose answer can be set. BORROWER lock(1_800e18) draw(900e18); OTHER lock(200e18) draw(100e18); IMD to $0.50 (both at 100%); both lock(1); two quiet days: backingPerUnit() == 1e18 (securedCollateral 2,000 + 2). Control (snapshot): BORROWER wipe(300e18) with a readable price: term unchanged (1,801 < 2 x 600 / 0.5), excess += 300, backingPerUnit() == 1e18, securedCollateral 2000000000000000000002. Outage: aggregator answer set to 0 (UsdPriceFeed.latestValue reads (0, 0)); BORROWER wipe(300e18): securedCollateral 1400437917808219176801 (the term scaled to 1,801 x 600 / 900) and excessNow() == 299781041095890411600; aggregator restored. EXPECTED backingPerUnit() == 1e18 (the same book as the control). ACTUAL 700218958904109588 == (1,200.4 + 201) x 0.5 / (700 + 300).

### 9. Low: CDPVault._lag releases feeExcess by any bank credit, and the debt bank is also filled by a redemption's cancellation, so a redraw after a redemption against the position releases warm repaid principal

`src/CDPVault.sol:1011`

```
                if (!secured && position.feeExcess != 0) _moveExcess(position, true, 0, credit);
```

Q1 ('the release cannot be triggered by capital that was never repaid'). `feeExcess` is filled only by repayments (`_reduceDebt` with `repayment == true`: wipe, bite, cover) and released by the bank credit on a debt increase (line 1011). The debt bank (`bankDebt`) is filled by EVERY warm decrease (line 1003), including a redemption's cancellation (`_redeemPosition` calls `_reduceDebt(..., false)`, which adds nothing to feeExcess but still banks the warm principal). A position that wiped W warm (feeExcess W, bank W), is then redeemed against for R (bank W + R, feeExcess W, base down by R: correct, the burn counts at once) and draws R back (credit R from the bank) has its feeExcess cut to W - R although the R that came back is new live supply and the W repaid has not returned: the base reads supply + W - R where the design's figure is supply + W. Direction: the fee is HIGHER than designed for the next hours, by R / base; each R costs the redemption fee on R, the same cost as the honest burn whose effect it duplicates, so this is an accuracy defect in the fee base, not a cheaper pin. The NatSpec at 64-66 and 1009-1011 ('released as this position borrows back from its bank', 'the supply its repayment took away, returning') is true of the mechanism but the bank is not only repaid principal. Reachable with the constants as committed. Call sequence: P cash(R, 0, P); P wipe(W); P draw(R). Smallest fix: keep the part of the bank that came from repayments separately (a second uint128 per position) and release feeExcess by min(credit, that part); or, cheaper in bytes, do not bank a redemption's cancellation at all (pass `repayment` into `_lag` and skip `bank += out - coldOut` when false), which also removes the redeemed-then-redrawn warmth the backing lag credits today. Merged from audit_math (low) and audit_flow (low), both reproduced.

**Reproduction**

test/scratch/Leads.t.sol test_feeExcessReleasedByRedemptionCredit (fails on this code; a Probe subclass of ParameterizedVault exposes `_feeBase()`). ParameterizedVault over MockIMD at $1 (Chainlink 2000e8 etched), NHI 0.85, launch constants, Treasury empty. P lock(1_800e18) draw(900e18); OTHER lock(200e18) draw(100e18) and hands P 100 imdUSD; two quiet days (supply 1,000, all warm). Control (snapshot): P wipe(500e18) then draw(20e18): feeBase() == 996484375000000000001 (the wipe's warm 496.5 kept, the 20 redrawn from the bank releases 20 and adds 20 of supply). Churn: P cash(20e18, 0, P) [feeBase 980e18: the burn counts at once, correct]; P wipe(500e18) [980e18]; P draw(20e18) [credited 20 from the bank the redemption filled]. EXPECTED feeBase() == 996484375000000000001. ACTUAL 980000000000000000000: the 20 released from feeExcess although it was redeemed, never repaid.

### 10. Low: CDPVault.wipe: the term's fall is measured against position.secured as last priced, so after a price fall the upward re-pricing of a debt-bound term masks the fall the repayment causes and the whole r

`src/CDPVault.sol:562`

```
        (uint256 principalBefore, uint256 termBefore) = (position.debt, position.secured);
```

Q2 (58f73de) and the accepted item retry2 #6. `termBefore` is the term as of the position's LAST touch, at that touch's price. For a position above 200% the term is debt-bound (2 x principal / price), so a price fall makes the honest term larger in IMD. `wipe` compares the new term, priced today with the smaller principal, against the stale smaller one: `fell` reads 0 whenever the re-pricing rise outweighs the repayment's fall, and line 572 keeps the WHOLE repayment as excess although 2 x repaid of backing value left the term with it. This is the ordinary flow of a drawdown (the price falls, borrowers above 200% repay to stay clear of mat), and each such repayment inflates the supply `_backingPerUnit` measures against, so redemptions are paid less for the next hours. It is a second consequence of reading the re-priced rise as cold (accepted #6): in the lag's own view the warm term did not fall, so keeping the repayment is consistent with it, and the control that re-prices first lets the fall come out of the cold instead. The accepted item's stated reason (the safe direction, it can only underpay a redemption) therefore still holds; what the resolution did not state is that the underpayment also enters through the excess and lasts hours rather than one touch. Severity low for that reason (audit_economics rated it medium). Reachable with the constants as committed, below par. Call sequence after a price fall, as the position's first touch: NEW wipe(200e18); anyone cash(...). Smallest fix, if the cost is not accepted: re-price before measuring, `_resecure(position, price)` at the top of `wipe` when `price != 0`, so `termBefore` is today's term (the rise is then a separate cold `_lag` event and the subsequent fall takes that cold first). Note that the fix proposed for the lock-then-wipe finding (line 571: net the fall against its cold part) would leave this case where it is, since the fall would then be all cold; the two must be decided together.

**Reproduction**

test/scratch/Leads.t.sol test_repriceMasksFall2 (logs). ParameterizedVault over MockIMD at $1, NHI 0.85, wage 0. OTHER locks 2,000 and draws 1,000 (hands HOLDER the 1,000); NEW locks 6,000 and draws 1,000 (600%: term 2,000 IMD, debt-bound); two quiet days; IMD to $0.40 (NEW 240%, honest term 5,000 IMD): backingPerUnit() == 0.8e18. Control (snapshot): NEW lock(1) re-prices the term to 5,000; +12 s; NEW wipe(200e18): term 5,000 -> 4,000, nothing kept; backingPerUnit() == 889025391789840721. Revert; +12 s; NEW wipe(200e18) as the first touch after the fall. EXPECTED: the control's figure. ACTUAL: backingPerUnit() == 800000000000000000 with excessNow() == 199756695433789955000: the 200 repaid kept as excess (term 2,000 -> 4,000 read as no fall), 10% under the control for the next hours.

### 11. Info: CDPVault.cash: the stored base rate for a partly fresh burn is computed from _feeBase() after the candidate's cold principal was retired but before the burn, so the non-fresh part moves the stored rat

`src/CDPVault.sol:747`

```
        redemptionBaseRate = freshCancelled == 0 ? base : _redemptionRate(amount - freshCancelled);
```

Q6 (redemption, the fresh-debt record) and Q1. `base` is quoted on the pre-state at line 699. When the burn cancels fresh principal, the rate stored for everyone is a second `_redemptionRate(amount - freshCancelled)` evaluated at line 747, after `_redeemPosition` ran `_reduceDebt`, whose `_lag` removed the cancelled principal's cold share from `_coldDebt`, and before `stablecoin.burn` at line 749 lowers the supply. `_feeBase()` at that moment is supply_pre + feeExcess - (cold_pre - coldOut): the imdUSD about to be burned is counted as warm supply although it is the fresh principal's own, so the base is larger by coldOut (up to the whole burn) and the stored increase smaller by the same proportion. Nobody profits: a redemption can never lower the rate below its decayed value, and a redeemer who controls the candidate pays its fee into its own collateral whatever the stored figure. An accuracy defect in the throttle the self-redemption pump (b952037a) is slowed by, not an extraction. Reachable with the constants as committed. Smallest fix: evaluate `_feeBase()` once before the position is touched and pass it into a `_redemptionRate(amount, prior)` overload used for both the quote and the stored rate. From audit_permissions (info), reproduced.

**Reproduction**

test/scratch/Leads.t.sol test_midStateRate (logs). ParameterizedVault at $1 (Chainlink 2000e8 etched), NHI 0.85, divisor 2, Treasury empty so the burn is position-funded. OTHER locks 4,000 and draws 2,000; SELF locks 4,300 and draws 1,000; two days. SELF draws 1,000 more (fresh, cold; 2,000 debt at 215%, eligible). Twelve seconds later feeBase() == 3000385007632510367299. SELF cash(1_100e18, 0, SELF): 1,000 of the 1,099.76 principal cancelled is fresh, so the non-fresh part is about 100. EXPECTED (the pre-state base): redemptionBaseRate about 100 / 3,000.385 / 2 = 16664528009841342. ACTUAL: 12500000000000000 = 100 / 4,000 / 2, the second `_feeBase()` having read the 4,000 pre-burn supply with SELF's 999.6 of cold already retired: 25% less.

### 12. Info: CDPVault.bite: the `mark.marked ? mark.marker : msg.sender` fallback and its comment ('An unmarked drained position has no marker') are dead since d7fceab, because bite reverts PositionNotMarked at li

`src/CDPVault.sol:1214`

```
        address marker = mark.marked ? mark.marker : msg.sender;
```

Q5 and Q6. d7fceab made `bite` require a mark, grace and an open window for every position (lines 1188-1191), removing the no-mark shortcut for a drained position's re-lock. Line 1213-1214 still describes and implements the shortcut's marker fallback: `mark.marked` is always true when line 1214 runs, so the false branch cannot execute for any input. About 20 bytes of dead code in a contract 2,159 bytes under the initcode limit, and a comment that describes a path that no longer exists. Smallest fix: `address marker = mark.marker;` and drop the comment. From audit_flow (info), confirmed by reading.

**Reproduction**

Read `bite`: line 1189 `if (!mark.marked) revert PositionNotMarked();` precedes line 1214 unconditionally, so the ternary's second arm is unreachable. test/Cover.t.sol test_aReLockAfterTheDrainsMarkLapsedNeedsANewMarkAndGrace pins the new behaviour (a re-lock needs a mark).

### 13. Info: Comments and NatSpec that claim properties the code does not have after 58f73de and d7fceab; the answers to Q1-Q6 where nothing is wrong; coverage

`src/CDPVault.sol:1015`

```
        // units (3.4e14 sIMD) a position's excess over that counts as warm.
```

CLAIMS WITHOUT THE PROPERTY. (1) CDPVault 1014-1015, 'past 128 bits of raw units ... a position's excess over that counts as warm': `total += after_ - before - credit` (line 1008) is uncapped while only the position's `cold` is capped (line 1016), so the surplus is COLD in `laggedNow` and, since the position's capped `coldOut` can never retire it, stays orphaned in the total until a quiet day: the opposite of the comment, the safe direction, unreachable at sIMD's supply (2^128 raw units is 3.4e14 sIMD). The retry2 panel listed this (its #10, item 6) and the resolution says every listed comment was rewritten; this one was not. (2) CDPVault 322-323 and 1033-1035, ParameterizedVault 239-240, 'what one position removes can never warm what another adds, in either order' / 'every subtraction from it saturates': false after a quiet day (medium, line 1038). (3) CDPVault 479, 564-568 and 776-782: 'The backing a repayment left behind has now left too', 'a repayment, a redemption and a redraw in three transactions pay the redemption no premium', 'nor one a TRANSACTION earlier': false through lock / wipe / cash (medium, line 571) and wipe / free / lock / cash (medium, line 481); and 'a repayment earlier in the same call does not shrink it' is literally no longer true since d7fceab deleted the start-of-transaction supply floor (58f73de's `if (start > supply) supply = start`): a same-call repayment of a debt-bound position shrinks the supply by the part its term followed down, harmlessly, since the backing counted falls by twice that. (4) CDPVault 773-774, 'honest redemptions are not underpaid': a cold draw-and-repay (medium, line 569), an honest two-step unwind (high, line 572), an underwater full repayment (low, line 570) and an outage repayment (low, line 568) all underpay them for hours; 780-782 'Only that part of a repayment is kept in the supply': what is kept outlives the backing it was kept for. (5) CDPVault 61-62, Position.excess is 'principal this position repaid that its secured term did not follow down': added for a COLD repayment too, and for a repayment the re-pricing masked (low, line 562). (6) CDPVault 64-66 and 1009-1011, feeExcess 'released as this position borrows back from its bank': the bank also holds a redemption's cancellation (low, line 1011). (7) CDPVault 53-55, the bank 'may come back warm within BACKING_WARMUP of the bank's own date, the moment it last went from empty to full': describes 24337a2; since d7fceab the bank cools continuously and is re-dated at every touch of its side (1019 / 1023). (8) CDPVault 1053-1062 and the retry2 #3 resolution, 'early redemptions ... pay more': the quote; a reserve-funded burn also STORES the cap (low, line 908). (9) CDPVault 746, 'The fresh part of the burn ... does not move the rate everyone else pays': the non-fresh part moves it less than the pre-state base says (info, line 747). (10) CDPVault 1213-1214: dead branch (info). (11) CDPVault 584-586 and 672-679, 'holding cover off costs the griefer the whole re-lock every time': only while the Treasury holds imdUSD worth the re-lock; with less, `cover` reverts (CoverBelowCollateralValue, or the burn fails) and the re-lock waits for bark, grace and bite, at a 20% loss to the griefer per cycle (verified in test/scratch/Cover.t.sol: a drained position with 291.7 of recorded bad debt re-locks collateral worth 175 while the Treasury holds 0.03 imdUSD; cover(B, 1e18) reverts CoverBelowCollateralValue, cover(B, value + 1) reverts in the burn; bark, six hours, bite(B, 1e18) proceeds and totalBadDebt moves 291.697 -> 290.706 with the record). (12) ParameterizedVault 241-243, 'the cost of it is real capital at risk in an open position, not gas': true of the work ceiling's slow round trip; for the redemption half the churns above cost gas. ANSWERS WHERE NOTHING IS WRONG. Q1: no sequence without seasoned capital moves the fee base BELOW the honest warm supply more cheaply than the honest burn: a cold draw and repayment nets to zero (coldOu

**Reproduction**

(1) read `_lag` lines 1004-1016: `total += after_ - before - credit` is uncapped, `if (cold > type(uint128).max) cold = type(uint128).max` caps only the position; `laggedNow` (1092-1096) subtracts the total. (2)-(10): the reproductions of the findings they document (lines 1038, 571, 481, 569, 572, 570, 568, 562, 1011, 908, 747, 1214). (11) test/scratch/Cover.t.sol test_relockBelowBadDebtWithEmptyTreasury (passes; logs). Sizes: `forge build --sizes`.

---

Judge's submission `d3cd8b5602eef82ef427160013f7681e58440fbf586e5aafc0d5e8af0927b507`, accepted on the IdentityMD network. Acceptance means the report met the job's checks;
it is not a guarantee that the code has no other defects.
