test(contracts): CharonLiquidator unit + reentrancy + skipped fork#38
Merged
test(contracts): CharonLiquidator unit + reentrancy + skipped fork#38
Conversation
…loses #22) 17-test Foundry suite. 16 run pure (no fork, deterministic, ~7ms); the BSC happy-path is scaffolded as a skipped test so the file exposes the right shape and a follow-up can fill in the pinned borrower / pinned block. - Section A — access control (4 tests): non-owner rejected on both state-changing entrypoints, executeOperation rejects external callers and wrong initiator - Section B — input validation (7 tests): one per guarded field on LiquidationParams; reverts fire inside executeLiquidation before flashLoanSimple is reached so no pool mock needed - Section C — rescue (4 tests): ERC-20 sweep via in-file MockERC20, native BNB sweep via vm.deal, zero-recipient + zero-amount guards - Section D — reentrancy (1 test): malicious ReentrantPool deployed as both pool AND owner so the reentry trips nonReentrant ("reentrant") rather than onlyOwner ("!owner") — only design that exercises the actual guard - Section E — fork happy-path (1 test, skipped): vm.skip(true) plus a TODO referencing #22.x for a pinned-block test against real Aave + Venus + PancakeSwap V3 - Section F — invariants: deferred (existing tests cover the zero-allowance invariant implicitly) forge build clean, forge fmt --check clean, forge test passes 16/0/1 in 6.73 ms. Run with `forge test --root contracts`.
This was referenced Apr 22, 2026
Closed
Closed
Closed
Closed
pin every src pragma from `^0.8.24` to `=0.8.24` so builds are reproducible and no upstream bugfix minor can silently change bytecode. add `evm_version = "paris"` to contracts/foundry.toml — BSC does not yet support the PUSH0 opcode introduced by the shanghai upgrade, so the default target would emit bytecode that reverts at deploy time on BNB Chain. self-contained fix for this branch; ancestor branch feat/12 carries the same change. rebase will deduplicate.
add in-file Venus vToken, pcs v3 swap router, and ERC-20 mocks so executeOperation can be driven end-to-end without a mainnet fork. new coverage: - full happy-path callback: liquidateBorrow + redeem + swap + profit sweep, with vm.expectEmit on LiquidationExecuted asserting exact profit value and indexed topics. - asset/debt and amount/repay mismatch guards both revert. - two slippage paths: router-side `amountOutMinimum` enforcement with a nonzero minSwapOut, plus the defensive `swap output below repayment` check when minSwapOut is zero. - constructor zero-address guards for aavePool and swapRouter. - rescue() to a contract recipient whose receive() reverts — pins the 2300-gas `transfer` behaviour (issue #135) and documents the loss-of-funds safety of the current path. extend MockERC20 with allowance-aware `approve` and `transferFrom` (needed by the mock router's real `transferFrom` pull). retarget the skipped fork test to issue #53 (feat/25) — the placeholder pointed at a non-existent #22.x.
…tests # Conflicts: # contracts/foundry.toml # contracts/src/CharonLiquidator.sol # contracts/src/interfaces/IAaveV3Pool.sol # contracts/src/interfaces/IERC20.sol # contracts/src/interfaces/IFlashLoanSimpleReceiver.sol # contracts/src/interfaces/ISwapRouter.sol # contracts/src/interfaces/IVToken.sol # contracts/test/CharonLiquidator.t.sol
obchain
added a commit
that referenced
this pull request
Apr 24, 2026
- Add `swapPoolFee: 3000` to `_params` so the on-fork swap tier matches the tier quoted via Quoter V2 in `_minOutFromQuoter`. Without it, the post-#38 `LiquidationParams` struct literal is missing a field and `_validate`'s `swapPoolFee > 0` check rejects the call. - Expect the post-#38 `LiquidationExecuted` event shape (3 indexed topics including `recipient`, 2 data fields). Asserting the `recipient` topic is load-bearing: it pins the CLAUDE.md cold-wallet invariant at the log level, mirroring the runtime balance assertions. - Gate `setUp` on `BNB_HTTP_URL` before calling `vm.createSelectFork`. The `bnb` RPC alias resolves the env var at fork-create time and raises a hard failure when missing; the gate mirrors the skip-on-env pattern used by the unit suite so CI without the env var skips cleanly.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #23
17-test Foundry suite. 16 run pure (no fork, deterministic, ~7 ms). BSC happy-path scaffolded as a skipped test so the file exposes the right shape and a follow-up can fill in pinned borrower / pinned block.
executeOperationrejects external callers and wrong initiatorLiquidationParams; reverts fire insideexecuteLiquidationbeforeflashLoanSimpleis reachedMockERC20, native BNB sweep viavm.deal, zero-recipient + zero-amount guardsReentrantPooldeployed as both pool AND owner so the reentry tripsnonReentrant("reentrant") rather thanonlyOwnervm.skip(true)plus a TODO for a pinned-block BSC test against real Aave + Venus + PancakeSwap V3forge buildclean,forge fmt --checkclean,forge testpasses 16/0/1 in 6.73 ms.Depends on #13 (
feat/12-charon-liquidator-full).