Summary
CharonLiquidator.t.sol Section A tests the two entry gates of executeOperation (!pool, !initiator). No test exercises any code beyond those two require statements. The 12 steps documented in the executeOperation NatSpec — Venus liquidation, collateral redemption, PancakeSwap swap, profit sweep, balance verification, and Aave repayment approval — are covered only by the skipped fork test.
This means the following code paths have zero test coverage:
- Step a: abi.decode(data, (LiquidationParams))
- Step b:
require(asset == p.debtToken, "asset/debt mismatch") and require(amount == p.repayAmount, "amount/repay mismatch")
- Step c: IERC20.approve + IVToken.liquidateBorrow + liqErr check
- Step d: approval zero-out
- Step e: IVToken.balanceOf + redeem + vBal > 0 check + redeemErr check
- Step f: IERC20(collateral).balanceOf + ISwapRouter.exactInputSingle + minSwapOut enforcement
- Step g: router approval zero-out
- Step h:
require(finalBal >= totalOwed, "swap output below repayment")
- Step i: profit transfer to owner
- Step j: LiquidationExecuted event emission
- Step k: Aave approval for totalOwed
- Step l: return true
Location
contracts/test/CharonLiquidator.t.sol — Sections A, B, C, D all bypass executeOperation's liquidation body. Section E is skipped.
PRD / invariant violated
This is the core fund-moving logic of the entire protocol. The CLAUDE.md safety invariants — profit swept to cold wallet, Aave repaid correctly — apply to code that is completely untested by this PR.
Risk
The three p0/p1 bugs already filed (#120 profit-to-hot-wallet, #121 vBNB path broken, #122 fee tier hardcoded) all live inside the untested body. A test suite that passes green while these bugs exist provides false assurance. A mocked walkthrough of executeOperation — using stub vToken and stub router contracts that return controlled values — would have surfaced all three bugs without requiring a fork.
Fix
Add at least one deterministic unit test that exercises the happy path of executeOperation end-to-end using stub/mock external contracts (stub Aave pool that calls executeOperation directly, stub vToken that returns 0 for liqErr and redeemErr, stub router that transfers a fixed debtToken amount). Assert:
LiquidationExecuted emitted with correct args
- profit amount transferred to expected address
- Aave pool approved for totalOwed
Refs #38
Summary
CharonLiquidator.t.sol Section A tests the two entry gates of
executeOperation(!pool,!initiator). No test exercises any code beyond those two require statements. The 12 steps documented in theexecuteOperationNatSpec — Venus liquidation, collateral redemption, PancakeSwap swap, profit sweep, balance verification, and Aave repayment approval — are covered only by the skipped fork test.This means the following code paths have zero test coverage:
require(asset == p.debtToken, "asset/debt mismatch")andrequire(amount == p.repayAmount, "amount/repay mismatch")require(finalBal >= totalOwed, "swap output below repayment")Location
contracts/test/CharonLiquidator.t.sol— Sections A, B, C, D all bypassexecuteOperation's liquidation body. Section E is skipped.PRD / invariant violated
This is the core fund-moving logic of the entire protocol. The CLAUDE.md safety invariants — profit swept to cold wallet, Aave repaid correctly — apply to code that is completely untested by this PR.
Risk
The three p0/p1 bugs already filed (#120 profit-to-hot-wallet, #121 vBNB path broken, #122 fee tier hardcoded) all live inside the untested body. A test suite that passes green while these bugs exist provides false assurance. A mocked walkthrough of
executeOperation— using stub vToken and stub router contracts that return controlled values — would have surfaced all three bugs without requiring a fork.Fix
Add at least one deterministic unit test that exercises the happy path of
executeOperationend-to-end using stub/mock external contracts (stub Aave pool that calls executeOperation directly, stub vToken that returns 0 for liqErr and redeemErr, stub router that transfers a fixed debtToken amount). Assert:LiquidationExecutedemitted with correct argsRefs #38