Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
62c90b2
feat(bstock): add atomic backstop liquidator
Debugger022 Jun 22, 2026
fc0cbbd
feat(bstock): add offchain liquidation scripts and driver test
Debugger022 Jun 22, 2026
46d9558
feat(bstock): add Safe fallback liquidation batch generator
Debugger022 Jun 22, 2026
6846758
refactor(bstock): drop shortfall pre-check, rename validation helper
Debugger022 Jul 1, 2026
a1ffe53
refactor(bstock): always route liquidation through the gate
Debugger022 Jul 1, 2026
7df4df2
refactor(bstock): drop redundant flash guard, harden sweep
Debugger022 Jul 1, 2026
8db3946
refactor(bstock): rename usdt* vars to debt* for clarity
Debugger022 Jul 1, 2026
7d9d1e0
fix(bstock): deduct Venus Liquidator cut in seize precompute
Debugger022 Jul 1, 2026
81fa8df
fix(bstock): route safe-fallback batch through the Liquidator gate
Debugger022 Jul 1, 2026
14729b7
docs(bstock): generalize debt-asset wording, drop stale comments
Debugger022 Jul 1, 2026
08eff13
fix(bstock): quote the debt asset, assert it matches Native output
Debugger022 Jul 2, 2026
d17b5ba
refactor(bstock): resolve bStock token from orderbook, drop hardcoded…
Debugger022 Jul 2, 2026
896dde9
refactor(bstock): rename native-quote smoke test to native-smoke
Debugger022 Jul 2, 2026
1ae2c47
feat(bstock): support non-USDT debt via two-hop swap
Debugger022 Jul 2, 2026
17717bc
feat(bstock): index debt market in Liquidated event
Debugger022 Jul 2, 2026
c9afc42
feat(bstock): reject zero minOut in liquidator
Debugger022 Jul 2, 2026
087ab6c
refactor(bstock): clarify treasury-cut var and hop-2 amountIn constraint
Debugger022 Jul 2, 2026
2968bf3
feat(bstock): add per-call swap deadline
Debugger022 Jul 2, 2026
cb3ee37
fix(bstock): reset liquidation-gate approval to zero
Debugger022 Jul 2, 2026
37bbaea
docs(bstock): clarify inventory floor and native-debt limits
Debugger022 Jul 2, 2026
d843b27
feat(bstock): support native BNB (vBNB) debt
Debugger022 Jul 2, 2026
b7df959
docs(bstock): add operator runbook for liquidation scripts
Debugger022 Jul 2, 2026
6319593
test(bstock): add fork helper to list a real bStock collateral market
Debugger022 Jul 2, 2026
0a3f355
test(bstock): harden liquidator suite with reentrancy, deadline, and …
Debugger022 Jul 2, 2026
a8c58d0
test(bstock): richer fork sweep — market names, decoded skips, cash-s…
Debugger022 Jul 2, 2026
1784444
chore: fix lint
Debugger022 Jul 2, 2026
9f452db
test(bstock): render dynamic sweep results as an aligned table
Debugger022 Jul 2, 2026
159c85e
test(bstock): restore chain snapshot after suite to prevent signer st…
Debugger022 Jul 2, 2026
327a362
chore: consolidate router validation into a single function
Debugger022 Jul 3, 2026
08f8c32
docs: clarify comments regarding native BNB debt handling in atomicLi…
Debugger022 Jul 3, 2026
28ba594
fix(bstock):: enforce Venus Liquidator gate checks in atomicLiquidate…
Debugger022 Jul 3, 2026
3cef17c
feat(bstock): add SEIZE_BUFFER parameter to atomicLiquidate for impro…
Debugger022 Jul 3, 2026
812c600
feat(bstock): support native BNB debt in safe fallback batch
Debugger022 Jul 3, 2026
43409ff
chore(bstock): deploy BStockLiquidator to bscmainnet
Debugger022 Jul 3, 2026
2812b9d
feat: updating deployment files
Debugger022 Jul 3, 2026
2115a9b
test(bstock): run fork suite against deployed bscmainnet BStockLiquid…
Debugger022 Jul 3, 2026
0f94350
feat(bstock): support a separate router spender for split-settlement …
Debugger022 Jul 10, 2026
f5e3d09
feat(bstock): add Liquid Mesh as a hop-1 liquidation source
Debugger022 Jul 10, 2026
03bd5da
fix: [L02]
Debugger022 Jul 13, 2026
ce0b179
fix: [L03]
Debugger022 Jul 13, 2026
355b16b
fix: [L04]
Debugger022 Jul 13, 2026
a2d2043
fix: [L05]
Debugger022 Jul 13, 2026
4d176db
fix: [L06]
Debugger022 Jul 13, 2026
4617af5
fix: [I01]
Debugger022 Jul 13, 2026
d57fc0b
fix(bstock): harden setRouterSpender against misconfiguration
Debugger022 Jul 13, 2026
e53cd8a
fix(bstock): reconcile indicative LM quotes against firm quotes after…
Debugger022 Jul 13, 2026
9b9c7a5
fix(bstock): enforce a minimum TTL on built Liquid Mesh orders
Debugger022 Jul 13, 2026
5c9cab2
refactor(bstock): type LM API responses and validate the signing seed
Debugger022 Jul 13, 2026
18fce77
docs(bstock): document hop-1 reconcile and setRouterSpender ordering
Debugger022 Jul 13, 2026
510c9e8
docs(bstock): add script decision flowchart to runbook
Debugger022 Jul 13, 2026
15ac30d
docs(bstock): fix runbook env table and swap mermaid for SVG
Debugger022 Jul 13, 2026
aba7007
docs(bstock): clarify renounce and swap-underflow invariants
Debugger022 Jul 13, 2026
b36c17e
fix(bstock): consistent LM slippage bps and tolerant SOURCE parsing
Debugger022 Jul 13, 2026
ae6b50f
fix(bstock): time out hung hop-1 source API calls
Debugger022 Jul 14, 2026
d8d0e99
fix(bstock): bubble up router revert reason in _swap
Debugger022 Jul 14, 2026
2a26a80
Merge branch 'feat/VPD-1598' into feat/VPD-1589
Debugger022 Jul 15, 2026
929ec41
fix(bstock): restore router spender lookup in _swap
Debugger022 Jul 15, 2026
591f6c7
fix(bstock): size the hop-2 leg off the LM floor, not the quote
Debugger022 Jul 15, 2026
6a21bca
feat(bstock): support VAI debt in the backstop liquidator
Debugger022 Jul 15, 2026
90fed52
fix(bstock): stop safe-fallback misreading a VAI debt as native BNB
Debugger022 Jul 15, 2026
3ab7e4f
feat(bstock): build the VAI hop-2 PSM calldata in the script
Debugger022 Jul 16, 2026
1baf78f
test(bstock): fork-test the VAI PSM hop against the real PegStability…
Debugger022 Jul 16, 2026
111af24
test(bstock): script-driven VAI liquidation fork scenario
Debugger022 Jul 16, 2026
4214098
fix(bstock): harden liquidation off-chain seize, minOut, and pre-flig…
Debugger022 Jul 16, 2026
6d5c7e9
docs(bstock): document env knobs and correct the runbook and sizing c…
Debugger022 Jul 16, 2026
801f180
docs(bstock): correct VAI incentive-divergence comments in liquidatio…
Debugger022 Jul 16, 2026
17bc7fd
test(bstock): fork-prove effective-incentive divergence for a non-cor…
Debugger022 Jul 16, 2026
0b4a754
docs(bstock): expand liquidation flowchart and correct Native/LM/PSM …
Debugger022 Jul 16, 2026
c18c571
docs(bstock): stop the atomic arrow overlapping the DRY_RUN=1 header …
Debugger022 Jul 16, 2026
316bbb4
fix(bstock): name the actual seize function in VAI error messages
Debugger022 Jul 16, 2026
b1815d0
feat(bstock): add lm-smoke probe, align smoke output, clarify naming …
Debugger022 Jul 17, 2026
c1925f0
Merge pull request #694 from VenusProtocol/feat/VPD-1589
Debugger022 Jul 17, 2026
1507c56
fix: [L07]
Debugger022 Jul 17, 2026
d7b3adb
test(bstock): fork-prove the full mode × hop-1 source × debt scenario…
fred-venus Jul 20, 2026
2ca908e
fix(bstock): pin vBNB oracle under the native sentinel, not WBNB
Debugger022 Jul 20, 2026
9d94d4d
test(bstock): harden scenario matrix
fred-venus Jul 20, 2026
8441a7c
test(bstock): remove standalone fork workflow
fred-venus Jul 20, 2026
00ca710
Merge pull request #696 from VenusProtocol/feat/VPD-1598
Debugger022 Jul 20, 2026
2536b36
chore(bstock): redeploy BStockLiquidator to bscmainnet
Debugger022 Jul 20, 2026
4ba70aa
docs(audits): add HashDit BStockLiquidator audit report
Debugger022 Jul 20, 2026
831c8b2
Merge pull request #701 from VenusProtocol/test/bstock-scenario-matrix
fred-venus Jul 21, 2026
351501b
Merge pull request #703 from VenusProtocol/feat/VPD-1598
fred-venus Jul 21, 2026
e75d2b7
Merge pull request #702 from VenusProtocol/feat/VPD-1636
fred-venus Jul 21, 2026
c688006
feat: updating deployment files
fred-venus Jul 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,11 @@ DEPLOYER_PRIVATE_KEY=

ETHERSCAN_API_KEY=
REPORT_GAS=

## bStock liquidation (scripts/bstock) — required secrets, never commit real values
NATIVE_API_KEY= # Native RFQ Swap API key (native-smoke.ts, atomic-liquidate.ts)
LM_API_KEY= # Liquid Mesh API key (lm-smoke.ts, atomic-liquidate.ts, verify-lm-fork.ts)
LM_PRIVATE_KEY_SEED= # Liquid Mesh Ed25519 signing seed, base64url (decodes to 32 bytes)
#RPC_URL= # optional BSC RPC override for read scripts; lm-smoke.ts otherwise reuses ARCHIVE_NODE_bscmainnet
# BSTOCK / AMOUNT / FROM / BORROWER / VBSTOCK / VDEBT / REPAY_AMOUNT / MODE / SOURCE / SLIPPAGE / ... are
# per-run flags passed inline on the command line, not .env config — see scripts/bstock/README.md.
2 changes: 1 addition & 1 deletion .eslinttsconfigrc
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"extends": "./tsconfig.json",
"include": ["./typechain", "./deploy", "./tests", "./script", "./scenario", "saddle.config.js", "docgen-templates", "commitlint.config.js", "./hardhat.config.zksync.ts", "type-extensions.ts"]
"include": ["./typechain", "./deploy", "./tests", "./script", "./scripts", "./scenario", "saddle.config.js", "docgen-templates", "commitlint.config.js", "./hardhat.config.zksync.ts", "type-extensions.ts"]
}
Binary file added audits/160_BStockLiquidator_Hashdit_20260720.pdf
Binary file not shown.
520 changes: 520 additions & 0 deletions contracts/BStock/BStockLiquidator.sol

Large diffs are not rendered by default.

168 changes: 168 additions & 0 deletions contracts/BStock/IBStockLiquidator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity 0.8.25;

import { IVBep20 } from "../InterfacesV8.sol";

/// @title IBStockLiquidator
/// @author Venus
/// @notice External API, events, errors and parameter struct for {BStockLiquidator}.
/// @dev The flash-loan callback `executeOperation` is intentionally NOT part of this interface — it is
/// declared by {IFlashLoanReceiver} (owned by the Core flash-loan subsystem) and implemented directly
/// by {BStockLiquidator}, keeping this interface free of the concrete `VToken` dependency.
interface IBStockLiquidator {
/// @notice Parameters for a single liquidation.
/// @dev The swap can be one or two hops. When `router2 == address(0)` it is a single hop
/// (bStock -> debt) and `swapCalldata2` / `intermediateToken` are ignored — behavior is
/// identical to the single-hop version. When `router2` is set it is two hops
/// (bStock -> `intermediateToken` -> debt): hop 1 sells bStock via `router`, hop 2 converts
/// the intermediate to the debt asset via `router2`. `minOut` is always the FINAL debt-asset
/// floor across the whole chain. `deadline` is a unix-timestamp expiry: the call reverts once
/// `block.timestamp` passes it, so a stale tx cannot settle against an expired quote.
struct LiquidationParams {
address borrower; // account to liquidate
IVBep20 vDebt; // borrowed market to repay (e.g. vUSDT)
IVBep20 vBStock; // bStock collateral market to seize (e.g. vTSLAB)
Comment thread
fred-venus marked this conversation as resolved.
uint256 repayAmount; // debt underlying to repay (its own decimals)
address router; // hop-1 RFQ router (Native firm-quote target, Liquid Mesh router, …) — must be allowlisted
bytes swapCalldata; // hop-1 calldata (off-chain-signed RFQ order): bStock -> intermediate (or -> debt if single-hop)
uint256 minOut; // minimum FINAL debt-asset amount the swap chain must yield, else revert
address router2; // hop-2 router (AMM/aggregator): intermediate -> debt; address(0) = single-hop
bytes swapCalldata2; // hop-2 calldata; the swap recipient inside it MUST be this contract
address intermediateToken; // token hop 1 outputs and hop 2 consumes (e.g. USDT); required when router2 set
uint256 deadline; // unix timestamp after which the call reverts; guards a stale tx sitting in the mempool
}

/// @notice Emitted when an operator is allowlisted or removed.
event OperatorSet(address indexed operator, bool allowed);

/// @notice Emitted when a swap router is allowlisted or removed.
event RouterSet(address indexed router, bool allowed);

/// @notice Emitted when a router's token-approval target (spender) is set or cleared.
event RouterSpenderSet(address indexed router, address indexed spender);

/// @notice Emitted on a successful liquidation.
/// @param borrower The liquidated account.
/// @param vBStock The seized bStock collateral market.
/// @param vDebt The repaid debt market.
/// @param repayAmount Debt underlying repaid.
/// @param seizedBStock Raw bStock redeemed and sold.
/// @param debtOut Debt-asset proceeds of the swap.
/// @param flash True if funded by a flash loan, false if from inventory.
event Liquidated(
address indexed borrower,
address indexed vBStock,
Comment thread
fred-venus marked this conversation as resolved.
address indexed vDebt,
uint256 repayAmount,
uint256 seizedBStock,
uint256 debtOut,
bool flash
);

/// @notice Emitted when the owner withdraws a token.
event Swept(address indexed token, address indexed to, uint256 amount);

/// @notice Emitted when the owner withdraws stuck native BNB.
event SweptNative(address indexed to, uint256 amount);

/// @notice Emitted when a swap hop pulls less than the amount approved to the router, leaving a
/// residual of the input token in the contract (e.g. a partially-filled RFQ quote). The
/// residual is recoverable via `sweep`.
/// @param token The input token left over (bStock on hop 1, the intermediate on hop 2).
/// @param amount The residual amount not consumed by the swap.
event PartialSwapLeftover(address indexed token, uint256 amount);

/// @notice Thrown when the caller is neither the owner nor an allowlisted operator.
error NotOperator();

/// @notice Thrown when the supplied swap router is not allowlisted.
error RouterNotAllowed(address router);

/// @notice Thrown when a router spender being set is not a deployed contract. The spender receives
/// a live token approval during the swap, so an EOA spender is always a misconfiguration.
error SpenderNotContract(address spender);

/// @notice Thrown when `vBStock.redeem` returns a non-zero error code.
error RedeemFailed(uint256 errCode);

/// @notice Thrown when the low-level call to the router reverts.
error SwapFailed();

/// @notice Thrown when swap proceeds are below `minOut`.
error InsufficientOut(uint256 got, uint256 minOut);

/// @notice Thrown when `minOut` is zero: a liquidation must set a non-zero debt-asset floor,
/// else it would silently accept any proceeds (including zero).
error ZeroMinOut();

/// @notice Thrown when a two-hop `intermediateToken` is zero, or equals the debt or bStock token.
error InvalidIntermediate();

/// @notice Thrown when `executeOperation` is called by something other than the Comptroller.
error OnlyComptroller();

/// @notice Thrown when the flash-loan initiator is not this contract.
error BadInitiator(address initiator);

/// @notice Thrown when the flashed asset does not match `params.vDebt`.
error WrongFlashAsset();

/// @notice Thrown when `flashLiquidate` is called with a VAI debt. VAI is minted/burned by the
/// VAIController and has no vToken market to flash from — use `liquidate` (INVENTORY mode)
/// with pre-funded VAI instead.
error FlashNotSupportedForVai();

/// @notice Thrown when the call is submitted after `params.deadline`.
error DeadlineExpired(uint256 deadline, uint256 nowTs);

/// @notice Thrown when a native BNB transfer (the `sweepNative` payout) fails.
error NativeTransferFailed();

/// @notice Allow or disallow an address to trigger liquidations.
/// @param operator Address to allowlist or remove.
/// @param allowed True to allow, false to remove.
function setOperator(address operator, bool allowed) external;

/// @notice Allow or disallow a router as the swap target (e.g. the Native router).
/// @dev Removing a router also clears its `routerSpender` entry (emitting {RouterSpenderSet} with
/// `address(0)`), so a stale spender cannot silently reactivate on a later re-allowlist.
/// @param router Address to allowlist or remove.
/// @param allowed True to allow, false to remove.
function setRouter(address router, bool allowed) external;

/// @notice Set the token-approval target (spender) for a router whose settlement contract that pulls
/// the input token differs from the call target (e.g. Liquid Mesh). When unset, the approval
/// defaults to the router itself (Native behaviour). Setting `spender = address(0)` clears it.
/// @dev Reverts with {RouterNotAllowed} unless `router` is currently allowlisted, and with
/// {SpenderNotContract} when a non-zero `spender` has no code. The spender is an approval
/// target only — it is never called; the low-level call always targets the allowlisted router.
/// @param router The allowlisted swap target (call target).
/// @param spender The contract that pulls the input token via `transferFrom` during settlement.
function setRouterSpender(address router, address spender) external;

/// @notice Withdraw any token (profit, leftover inventory, stuck dust) to `to`.
/// @param token Token to withdraw.
/// @param to Recipient.
/// @param amount Amount to withdraw.
function sweep(address token, address to, uint256 amount) external;

/// @notice Withdraw stuck native BNB (a stray transfer, or a gate refund) to `to`.
/// @param to Recipient.
/// @param amount Amount of native BNB to withdraw.
function sweepNative(address to, uint256 amount) external;

/// @notice Liquidate using the contract's own debt-asset inventory.
/// @dev The contract must already hold >= `repayAmount` of `vDebt.underlying()`.
/// Profit (proceeds - repay) stays in the contract; withdraw it with `sweep`.
/// @param params Liquidation parameters (borrower, markets, repay, hop-1 router + calldata, final
/// minOut, `deadline`, and the optional hop-2 router/calldata/intermediate for non-USDT debt).
/// @return debtOut Debt-asset proceeds realized by the swap chain.
function liquidate(LiquidationParams calldata params) external returns (uint256 debtOut);

/// @notice Liquidate by flash-borrowing the repay amount from Venus, repaid (+ premium) in the same tx.
/// @dev Requires this contract to be `authorizedFlashLoan` in the Comptroller and `vDebt` flash-enabled.
/// Profit (proceeds - repay - premium) stays in the contract; withdraw it with `sweep`.
/// @param params Liquidation parameters (borrower, markets, repay, hop-1 router + calldata, final
/// minOut, `deadline`, and the optional hop-2 router/calldata/intermediate for non-USDT debt).
function flashLiquidate(LiquidationParams calldata params) external;
}
10 changes: 10 additions & 0 deletions contracts/InterfacesV8.sol
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,20 @@ interface IComptroller {

function _setActionsPaused(address[] calldata markets_, Action[] calldata actions_, bool paused_) external;

function executeFlashLoan(
address payable onBehalf,
address payable receiver,
IVToken[] calldata vTokens,
uint256[] calldata underlyingAmounts,
bytes calldata param
) external;

function vaiController() external view returns (IVAIController);

function liquidatorContract() external view returns (address);

function getAccountLiquidity(address account) external view returns (uint256, uint256, uint256);

function oracle() external view returns (ResilientOracleInterface);

function actionPaused(address market, Action action) external view returns (bool);
Expand Down
2 changes: 2 additions & 0 deletions contracts/external/IWBNB.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@ import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC

interface IWBNB is IERC20Upgradeable {
function deposit() external payable;

function withdraw(uint256 amount) external;
}
Loading
Loading