feat(contracts): full Aave + Venus + PancakeSwap liquidation flow#37
Merged
feat(contracts): full Aave + Venus + PancakeSwap liquidation flow#37
Conversation
…oses #12) Fills the two stub bodies in `CharonLiquidator.sol` so the contract performs a complete atomic liquidation. The skeleton's signatures, storage layout, and event surface are unchanged — Rust callers and deploy scripts written against #11 keep working. - `executeLiquidation` body: - `nonReentrant` modifier added (1/2 storage flag, gas-cheap) - ABI-encodes `LiquidationParams` and calls `IAaveV3Pool.flashLoanSimple(self, debtToken, repayAmount, encoded, 0)`. The actual liquidation runs inside Aave's callback into `executeOperation`. - `executeOperation` body, in order: 1. Decode params, cross-check `asset == debtToken` and `amount == repayAmount` 2. Approve `debtVToken` for `repayAmount` 3. `IVToken.liquidateBorrow(borrower, repayAmount, collateralVToken)` — error code 0 required 4. `IVToken(collateralVToken).redeem(vBal)` — pulls underlying collateral; non-zero error → revert 5. Approve PancakeSwap V3 router for the seized underlying 6. `ISwapRouter.exactInputSingle(...)` swap collateral → debtToken with `amountOutMinimum = minSwapOut` 7. Final balance check: `finalBal >= amount + premium` — backstop in case the router doesn't catch it 8. Sweep `profit = finalBal - totalOwed` to `owner` BEFORE returning so Aave only pulls exactly what it's owed 9. Approve Aave for `totalOwed`, emit `LiquidationExecuted`, return `true` - Approvals are zeroed after consumption for `debtVToken` and the swap router; the Aave allowance is fully consumed by the same-tx `transferFrom` so it can't leak. - New `interfaces/ISwapRouter.sol` mirrors the Uniswap V3 surface PancakeSwap V3 implements — minimal `ExactInputSingleParams` struct + `exactInputSingle`. `interfaces/IVToken.sol` extended with the Compound-V2 `redeem(uint256)` variant required by step 4. - Reentrancy guard sits only on `executeLiquidation`, not `executeOperation` — the callback runs inside the same call frame so adding it there would deadlock; the `msg.sender == AAVE_POOL` gate already blocks external entry. - No external libs (still no OZ / aave-core). All imports stay inline. `forge build` + `forge fmt --check` both clean. Tests + fork tests + deploy script land in #22 and a follow-up.
This was referenced Apr 22, 2026
Closed
Closed
BNB Chain has not adopted the Shanghai hard fork and rejects the PUSH0 opcode. solc >= 0.8.20 emits PUSH0 by default, so contracts compiled with the prior config would revert on first touch when deployed to BSC. pinning the target to paris keeps our bytecode compatible with the live chain until BSC enables Shanghai. closes #118
drop the floating `^0.8.24` caret across CharonLiquidator.sol and the five inline interfaces. pins deployed-bytecode producing solc to a single known version and removes risk of a future 0.8.x patch release subtly changing codegen, optimizer behaviour or metadata hash. closes #119
hot wallet (owner) holds gas only — profit must exit the bot's operational key perimeter inside the same atomic flash-loan frame. the prior sweep targeted `owner`, violating the CLAUDE.md safety invariant and exposing accumulated profit to any compromise of the scanner/executor hot key. add an immutable `COLD_WALLET` address set at construction, validate it non-zero, and transfer `profit` to it inside executeOperation before the Aave repayment approval. extend LiquidationExecuted with an indexed `recipient` so off-chain monitors can filter by cold wallet. constructor signature gains a third arg `_coldWallet`. closes #120
vBNB is the only Venus vToken whose underlying is native BNB. its `redeem()` transfers native BNB to msg.sender via a low-level call rather than emitting an IERC20.transfer, so the existing `IERC20(collateralToken).balanceOf(address(this))` read returned zero after redemption and the PancakeSwap V3 swap leg reverted with zero amountIn, stranding every seized vBNB position. add an IWETH interface and hard-code the BSC mainnet vBNB and WBNB addresses as internal constants. after redeem(), if the seized vToken is vBNB, wrap address(this).balance into WBNB via IWETH.deposit before the balance-of read. require callers that seize vBNB to declare WBNB as collateralToken so the swap routes through a real pool. closes #121
…able the swap leg hard-coded `fee: 3000` (0.30 %). that tier does not exist or has near-zero liquidity for several Venus-collateral pairs on PCS V3: BTCB/USDT sits in the 0.05 % (500) pool, ETH/USDT in the 0.01 % (100) pool, XVS/WBNB in the 1 % (10000) pool. routing those through the 0.30 % pool would revert on `SPL` or eat unbounded slippage. add `uint24 swapPoolFee` to LiquidationParams, validate non-zero in executeLiquidation, and pass it to ExactInputSingleParams.fee. the off-chain opportunity router now selects the deepest pool per pair. abi layout note: this extends LiquidationParams with a new tail field. the companion Rust `LiquidationParams` builder in the charon-executor crate (not yet present on this branch — lands with PR #41) must mirror the added field. closes #122
…ator-full # Conflicts: # contracts/src/CharonLiquidator.sol
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 #13
Fills the two stub bodies in
CharonLiquidator.solso the contract performs a complete atomic liquidation. Skeleton signatures, storage layout, and event surface are unchanged.executeLiquidationbody:nonReentrantguard; ABI-encodesLiquidationParamsand callsIAaveV3Pool.flashLoanSimple(self, debtToken, repayAmount, encoded, 0)executeOperationbody (runs inside Aave callback):asset == debtTokenandamount == repayAmountdebtVTokenforrepayAmountIVToken.liquidateBorrow(borrower, repayAmount, collateralVToken)— error code 0 requiredIVToken(collateralVToken).redeem(vBal)— pulls underlying collateralexactInputSingleswap collateral → debtToken withamountOutMinimum = minSwapOutfinalBal >= amount + premium(backstop)profit = finalBal - totalOwedtoownerbefore returningtotalOwed, emitLiquidationExecuted, returntruedebtVTokenand the swap routerinterfaces/ISwapRouter.sol(PancakeSwap V3 = Uniswap V3 ABI);IVTokenextended withredeem(uint256)executeLiquidationonly — the callback can't deadlockNo external libs (no OZ / aave-core).
forge build+forge fmt --checkboth clean.Depends on #12 (
feat/11-foundry-skeleton).