Skip to content

test(contracts): CharonLiquidator unit + reentrancy + skipped fork#38

Merged
obchain merged 5 commits intomainfrom
feat/13-foundry-fork-tests
Apr 24, 2026
Merged

test(contracts): CharonLiquidator unit + reentrancy + skipped fork#38
obchain merged 5 commits intomainfrom
feat/13-foundry-fork-tests

Conversation

@obchain
Copy link
Copy Markdown
Owner

@obchain obchain commented Apr 21, 2026

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.

  • 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
  • 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
  • Section E — fork happy-path (1 test, skipped): vm.skip(true) plus a TODO for a pinned-block BSC test against real Aave + Venus + PancakeSwap V3

forge build clean, forge fmt --check clean, forge test passes 16/0/1 in 6.73 ms.

Depends on #13 (feat/12-charon-liquidator-full).

…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
obchain added 2 commits April 23, 2026 13:35
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.
@obchain obchain changed the base branch from feat/12-charon-liquidator-full to main April 24, 2026 10:46
obchain added 2 commits April 24, 2026 16:26
…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 obchain merged commit ff8119d into main Apr 24, 2026
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[test] Foundry fork tests for CharonLiquidator (all Venus markets + slippage edge cases)

1 participant